{"id":"GHSA-5m6c-38r8-5w76","summary":"Dagu MCP DAG specification read bypasses path containment and workspace isolation","details":"## Summary\n\nThe 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).\n\n## The defect\n\nThe 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).\n\nThe MCP service does not go through that chain. internal/service/mcp/server.go calls the API method directly as an in-process Go call:\n\n```go\nfunc (svc *Service) getDAGSpec(ctx context.Context, name string) (map[string]any, error) {   // line 307\n    resp, err := svc.api.GetDAGSpec(ctx, daguapi.GetDAGSpecRequestObject{\n        FileName: daguapi.DAGFileName(name),                                                 // line 309\n    })\n```\n\nNo HTTP request is constructed, so no middleware executes, and the underlying resolver is still uncontained:\n\n```go\nfunc (store *Storage) locateDAG(nameOrPath string) (string, error) {                 // line 1041\n    if strings.Contains(nameOrPath, string(filepath.Separator)) {                    // line 1042\n        foundPath, err := findDAGFile(nameOrPath)\n        if err == nil {\n            return foundPath, nil                                                    // line 1045, no containment check\n        }\n    }\n```\n\nAn 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.\n\n## Verification\n\nVerified end to end using the project's own conformance/mcptest harness against a real running HTTP server, not by source reading:\n\n```\ndagu_read {\"target\":\"dag_spec\",\"name\":\"/abs/outside/secret.yaml\"}\n-\u003e HTTP 200, full file contents returned in data.spec\n```\n\nThis 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.\n\nBounded 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.\n\n## Suggested fix\n\nMove 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.\n\n## Tooling\n\nAI 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.","aliases":["CVE-2026-102943"],"modified":"2026-11-10T16:15:05.459026361Z","published":"2026-11-10T15:56:49Z","database_specific":{"github_reviewed_at":"2026-11-10T15:56:49Z","nvd_published_at":"2026-11-05T23:16:49Z","cwe_ids":["CWE-22","CWE-863"],"severity":"MODERATE","github_reviewed":true},"references":[{"type":"WEB","url":"https://github.com/dagucloud/dagu/security/advisories/GHSA-5m6c-38r8-5w76"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-102943"},{"type":"WEB","url":"https://github.com/dagucloud/dagu/pull/2424"},{"type":"WEB","url":"https://github.com/dagucloud/dagu/commit/c15c5406bb7b9551f94ab276705f9fd1cebbf2d2"},{"type":"PACKAGE","url":"https://github.com/dagucloud/dagu"},{"type":"WEB","url":"https://github.com/dagucloud/dagu/releases/tag/v2.11.0"}],"affected":[{"package":{"name":"github.com/dagu-org/dagu/v2","ecosystem":"Go","purl":"pkg:golang/github.com/dagu-org/dagu/v2"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"2.11.0"}]}],"database_specific":{"last_known_affected_version_range":"\u003c= 2.10.7","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/11/GHSA-5m6c-38r8-5w76/GHSA-5m6c-38r8-5w76.json"}}],"schema_version":"1.9.2","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N"}]}