GHSA-5M6C-38R8-5W76
Vulnerability from github – Published: 2026-11-10 15:56 – Updated: 2026-11-10 15:56Summary
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": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 2.10.7"
},
"package": {
"ecosystem": "Go",
"name": "github.com/dagu-org/dagu/v2"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.11.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-102943"
],
"database_specific": {
"cwe_ids": [
"CWE-22",
"CWE-863"
],
"github_reviewed": true,
"github_reviewed_at": "2026-11-10T15:56:49Z",
"nvd_published_at": "2026-11-05T23:16:49Z",
"severity": "MODERATE"
},
"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\u0027s 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\u0027s 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.",
"id": "GHSA-5m6c-38r8-5w76",
"modified": "2026-11-10T15:56:49Z",
"published": "2026-11-10T15:56:49Z",
"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"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "Dagu MCP DAG specification read bypasses path containment and workspace isolation"
}
Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline
Sightings and recorded dates, grouped by day · UTC
How durations are calculated
Following the VTEM framework (Black Hat USA 2015), time to patch starts when the vendor or a coordinator was informed, total time to patch at discovery, and disclosure to patch at public disclosure; vendor response time runs from the report to its acknowledgement. Time to exploit and time unpatched start at the earliest discovery or report, falling back to public disclosure. Unpatched time ends at the first recorded patch, or the calculation time when no patch is recorded. Exploitation to patch shows the interval between those two events; a published proof of concept is a public exploit, not exploitation.
Each result shows its source dates. A negative interval means the end event came first; when date-only inputs leave the order open, the result says so. Date-only inputs are approximate. The earliest evidence wins: KEV report dates and sightings are labelled as such. Records describing another vulnerability are listed but never calculated from, and a PoC alone does not establish exploitation. Missing patch evidence does not prove that no patch exists. Filters below change the event list only.
Loading timeline…
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.