CVE-2026-102943 is a medium-severity (CVSS 6.5) Path Traversal vulnerability in github.com/dagu-org/dagu/v2. A fix is available for github.com/dagu-org/dagu/v2 — see the affected versions and patch details below.
Dagu MCP DAG specification read bypasses path containment and workspace isolation
Exploitation Status
No confirmed exploitation observed yet
- CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-102943.
EPSS Exploitation Probability
Probability of exploitation in the next 30 days, from FIRST.org EPSS.
How urgent is this, really
Where this sits among everything scored
Real-World Exposure
github.com/dagu-org/dagu/v2Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.
Description
Summary
The traversal fix for CVE-2026-33344 was implemented as HTTP middleware installed only in the chi handler chain, but the MCP service calls the same API method in process, so arbitrary YAML file read is reachable again through the MCP tool surface with a viewer-role key. Read-only, limited to .yaml/.yml targets. Confirmed at HEAD f6c039f (post v2.10.7).
The defect
The fix for CVE-2026-33344 is a reflection-based HTTP middleware (internal/frontend/.../api.go, roughly lines 553-597) that inspects a request field named FileName and rejects anything that is not a plain name. It is installed in exactly one place: the chi strict-handler chain (api.go around line 419).
The MCP service does not go through that chain. internal/service/mcp/server.go calls the API method directly as an in-process Go call:
func (svc *Service) getDAGSpec(ctx context.Context, name string) (map[string]any, error) { // line 307
resp, err := svc.api.GetDAGSpec(ctx, daguapi.GetDAGSpecRequestObject{
FileName: daguapi.DAGFileName(name), // line 309
})
No HTTP request is constructed, so no middleware executes, and the underlying resolver is still uncontained:
func (store *Storage) locateDAG(nameOrPath string) (string, error) { // line 1041
if strings.Contains(nameOrPath, string(filepath.Separator)) { // line 1042
foundPath, err := findDAGFile(nameOrPath)
if err == nil {
return foundPath, nil // line 1045, no containment check
}
}
An absolute path contains a separator, so it takes the first branch and is returned as-is, with no check that it falls under store.baseDir or any entry in store.searchPaths.
Verification
Verified end to end using the project's own conformance/mcptest harness against a real running HTTP server, not by source reading:
dagu_read {"target":"dag_spec","name":"/abs/outside/secret.yaml"}
-> HTTP 200, full file contents returned in data.spec
This also succeeded with a viewer-role API key on an auth-enabled server (auth.mode: builtin), so it is not admin-only. Control test that makes this a residual: the equivalent REST request is correctly blocked (400, "name must only contain alphanumeric characters..."). The HTTP lane is fixed; the MCP lane is not.
Bounded impact: read only (dagu_change and dagu_execute are both blocked by spec-level name validation; I tested both); only .yaml/.yml targets. Secondary: GetDAGSpec skips the requireWorkspaceVisible check its sibling handlers run, so this also crosses workspace isolation.
Suggested fix
Move the containment check out of HTTP middleware and into locateDAG itself, rejecting any resolved path that does not fall under baseDir or a configured search path. A middleware that runs on only one of two transports into the same API cannot hold the boundary; the resolver is the correct chokepoint since both transports converge there. Separately, add requireWorkspaceVisible to GetDAGSpec to match its siblings.
Tooling
AI assistance was used while investigating. The traversal was reproduced against a real running server through the project's own MCP conformance harness, including with a viewer-role API key, and the REST control test confirms the HTTP lane is blocked. Confirmed in the uncontained locateDAG branch and the direct in-process GetDAGSpec call at HEAD.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐹Go | github.com/dagu-org/dagu/v2 | all versions | 2.11.0go get github.com/dagu-org/dagu/v2@v2.11.0 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/dagu-org/dagu/v2, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update github.com/dagu-org/dagu/v2 to 2.11.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-102943 is resolved across your whole dependency graph.
Workarounds
Resolve every user-supplied path to its canonical form and reject anything that escapes the intended directory, and run the component under an account that has no read or write access outside the directory it legitimately serves.
Frequently Asked Questions
Is CVE-2026-102943 in your dependencies?
Find it across Go, including transitive dependencies.