Skip to content

Commit 21fa23b

Browse files
1 parent c00a4e4 commit 21fa23b

8 files changed

Lines changed: 563 additions & 56 deletions

File tree

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-228v-wc5r-j8m7",
4+
"modified": "2026-03-12T14:20:17Z",
5+
"published": "2026-03-12T14:20:16Z",
6+
"aliases": [
7+
"CVE-2026-32102"
8+
],
9+
"summary": "OliveTin Vulnerable to Unauthorized Action Output Disclosure via EventStream",
10+
"details": "### Summary\n\n OliveTin’s live EventStream broadcasts execution events and action output to authenticated dashboard subscribers without enforcing per-action authorization. A low-privileged authenticated user can receive output from actions they are\n not allowed to view, resulting in broken access control and sensitive information disclosure. I validated this on OliveTin 3000.10.2.\n\n\n\n\n### Details\nThe issue is in the live event streaming path.\n\n EventStream() only checks whether the caller may access the dashboard, then registers the user as a stream subscriber:\n\n - service/internal/api/api.go:776\n\n After subscription, execution events are broadcast to all connected clients without checking whether each recipient is authorized to view logs for the action:\n\n - service/internal/api/api.go:846 OnExecutionStarted\n - service/internal/api/api.go:869 OnExecutionFinished\n - service/internal/api/api.go:1047 OnOutputChunk\n\n The event payload includes action output through:\n\n - service/internal/api/api.go:295 internalLogEntryToPb\n - service/internal/api/api.go:302 Output\n\n By contrast, the normal log APIs do apply per-action authorization checks:\n\n - service/internal/api/api.go:518 GetLogs\n - service/internal/api/api.go:585 GetActionLogs\n - service/internal/api/api.go:544 isLogEntryAllowed\n\n Root cause:\n\n - the subscription path enforces only coarse dashboard access\n - execution callbacks broadcast to every connected client\n - no per-recipient ACL check is applied before sending action metadata or output\n\n I validated the issue using:\n\n - an admin user with full ACLs\n - an alice user with no ACLs\n - a protected action that outputs TOPSECRET=alpha-bravo-charlie\n\n Despite having no relevant ACLs, alice still receives the ExecutionFinished event for the privileged action, including the protected output.\n\n\n\n### PoC\nTested version:\n```\n - 3000.10.2\n```\n 1. Fetch and check out 3000.10.2 in a clean worktree:\n```bash\n git -C OliveTin fetch origin tag 3000.10.2\n git -C OliveTin worktree add /home/kali/CVE/OliveTin-3000.10.2 3000.10.2\n```\n 2. Copy the PoC test into the clean tree:\n```bash\n cp OliveTin/service/internal/api/event_stream_leak_test.go \\\n OliveTin-3000.10.2/service/internal/api/\n```\n 3. Run the targeted PoC test:\n```bash\n cd OliveTin-3000.10.2/service\n go test ./internal/api -run TestEventStreamLeaksUnauthorizedExecutionOutput -count=1 -timeout 30s -v\n```\n 4. Optional: save validation output:\n```bash\n go test ./internal/api -run TestEventStreamLeaksUnauthorizedExecutionOutput -count=1 -timeout 30s -v \\\n 2>&1 | tee /tmp/olivetin_eventstream_3000.10.2.log\n```\n Observed validation output:\n```bash\n === RUN TestEventStreamLeaksUnauthorizedExecutionOutput\n time=\"2026-03-01T04:44:59-05:00\" level=info msg=\"Action requested\" actionTitle=secret-action tags=\"[]\"\n time=\"2026-03-01T04:44:59-05:00\" level=info msg=\"Action parse args - Before\" actionTitle=secret-action cmd=\"echo 'TOPSECRET=alpha-bravo-charlie'\"\n time=\"2026-03-01T04:44:59-05:00\" level=info msg=\"Action parse args - After\" actionTitle=secret-action cmd=\"echo 'TOPSECRET=alpha-bravo-charlie'\"\n time=\"2026-03-01T04:44:59-05:00\" level=info msg=\"Action started\" actionTitle=secret-action timeout=1\n time=\"2026-03-01T04:44:59-05:00\" level=info msg=\"Action finished\" actionTitle=secret-action exit=0 outputLength=30 timedOut=false\n --- PASS: TestEventStreamLeaksUnauthorizedExecutionOutput (0.00s)\n PASS\n ok github.com/OliveTin/OliveTin/internal/api 0.025s\n```\n What this proves:\n\n - admin can execute the protected action\n - alice has no ACLs\n - alice still receives the streamed completion event for the protected action\n - protected action output is exposed through the event stream\n\n\n### Impact\n This is an authenticated broken access control / information disclosure vulnerability.\n\n A low-privileged authenticated user can subscribe to EventStream and receive:\n\n - action execution metadata\n - execution tracking IDs\n - initiating username\n - live output chunks\n - final command output\n\n Who is impacted:\n\n - multi-user OliveTin deployments\n - environments where privileged actions produce secrets, tokens, internal system details, or other sensitive operational output\n - deployments where lower-privileged authenticated users can access the dashboard and subscribe to live events\n\n This bypasses intended per-action log/view restrictions for protected actions.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V4",
14+
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "Go",
21+
"name": "github.com/OliveTin/OliveTin"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "3000.10.2"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
38+
"references": [
39+
{
40+
"type": "WEB",
41+
"url": "https://github.com/OliveTin/OliveTin/security/advisories/GHSA-228v-wc5r-j8m7"
42+
},
43+
{
44+
"type": "ADVISORY",
45+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-32102"
46+
},
47+
{
48+
"type": "PACKAGE",
49+
"url": "https://github.com/OliveTin/OliveTin"
50+
}
51+
],
52+
"database_specific": {
53+
"cwe_ids": [
54+
"CWE-284",
55+
"CWE-863"
56+
],
57+
"severity": "HIGH",
58+
"github_reviewed": true,
59+
"github_reviewed_at": "2026-03-12T14:20:16Z",
60+
"nvd_published_at": "2026-03-11T21:16:16Z"
61+
}
62+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-5339-hvwr-7582",
4+
"modified": "2026-03-12T14:19:25Z",
5+
"published": "2026-03-12T14:19:25Z",
6+
"aliases": [
7+
"CVE-2026-31873"
8+
],
9+
"summary": "Unhead Vulnerable to Bypass of URI Scheme Sanitization in makeTagSafe via Case-Sensitivity",
10+
"details": "The `link.href` check in `makeTagSafe` (safe.ts, line 68-71) uses `String.includes()`, which is case-sensitive:\n\n```typescript\nif (key === 'href') {\n if (val.includes('javascript:') || val.includes('data:')) {\n return\n }\n next[key] = val\n}\n```\n\nBrowsers treat URI schemes case-insensitively. `DATA:text/css,...` is the same as `data:text/css,...` to the browser, but `'DATA:...'.includes('data:')` returns `false`.\n\n### PoC\n\n```javascript\nuseHeadSafe({\n link: [{\n rel: 'stylesheet',\n href: 'DATA:text/css,body{display:none}'\n }]\n})\n```\n\nSSR output:\n\n```html\n<link rel=\"stylesheet\" href=\"DATA:text/css,body{display:none}\">\n```\n\nThe browser loads this as a CSS stylesheet. An attacker can inject arbitrary CSS for UI redressing or data exfiltration via CSS attribute selectors with background-image callbacks.\n\nAny case variation works: `DATA:`, `Data:`, `dAtA:`, `JAVASCRIPT:`, etc.\n\n## Suggested fix\n\n```typescript\nif (key === 'href') {\n const lower = val.toLowerCase()\n if (lower.includes('javascript:') || lower.includes('data:')) {\n return\n }\n next[key] = val\n}\n```",
11+
"severity": [
12+
{
13+
"type": "CVSS_V3",
14+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "npm",
21+
"name": "unhead"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "2.1.11"
32+
}
33+
]
34+
}
35+
],
36+
"database_specific": {
37+
"last_known_affected_version_range": "<= 2.1.10"
38+
}
39+
}
40+
],
41+
"references": [
42+
{
43+
"type": "WEB",
44+
"url": "https://github.com/unjs/unhead/security/advisories/GHSA-5339-hvwr-7582"
45+
},
46+
{
47+
"type": "PACKAGE",
48+
"url": "https://github.com/unjs/unhead"
49+
},
50+
{
51+
"type": "WEB",
52+
"url": "https://github.com/unjs/unhead/releases/tag/v2.1.11"
53+
}
54+
],
55+
"database_specific": {
56+
"cwe_ids": [
57+
"CWE-79"
58+
],
59+
"severity": "LOW",
60+
"github_reviewed": true,
61+
"github_reviewed_at": "2026-03-12T14:19:25Z",
62+
"nvd_published_at": null
63+
}
64+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-8jrh-7jg8-fvmv",
4+
"modified": "2026-03-12T14:19:41Z",
5+
"published": "2026-03-10T18:31:22Z",
6+
"aliases": [
7+
"CVE-2026-2741"
8+
],
9+
"summary": "Vaadin: Specially crafted ZIP archives can escape the intended extraction directory",
10+
"details": "Specially crafted ZIP archives can escape the intended extraction directory during Node.js download and extraction in Vaadin 14.2.0 through 14.14.0, 23.0.0 through 23.6.6, 24.0.0 through 24.9.8, and 25.0.0 through 25.0.2. \n\nVaadin’s build process can automatically download and extract Node.js if it is not installed locally. If an attacker can intercept or control this download via DNS hijacking, a MITM attack, a compromised mirror, or a supply chain attack, they can serve a malicious archive containing path traversal sequences that write files outside the intended extraction directory.\n\n\nUsers of affected versions should use a globally preinstalled Node.js version compatible with their Vaadin version, or upgrade as follows: 14.2.0-14.14.0 to 14.14.1, 23.0.0-23.6.6 to 23.6.7, 24.0.0-24.9.8 to 24.9.9, and 25.0.0-25.0.2 to 25.0.3 or newer.\n\nPlease note that Vaadin versions 10-13 and 15-22 are no longer supported and you should update either to the latest 14, 23, 24, 25 version.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V4",
14+
"score": "CVSS:4.0/AV:N/AC:H/AT:P/PR:L/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "Maven",
21+
"name": "com.vaadin:flow-project"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "14.2.0"
29+
},
30+
{
31+
"fixed": "14.14.1"
32+
}
33+
]
34+
}
35+
],
36+
"database_specific": {
37+
"last_known_affected_version_range": "< 14.14.0"
38+
}
39+
},
40+
{
41+
"package": {
42+
"ecosystem": "Maven",
43+
"name": "com.vaadin:flow-project"
44+
},
45+
"ranges": [
46+
{
47+
"type": "ECOSYSTEM",
48+
"events": [
49+
{
50+
"introduced": "23.0.0"
51+
},
52+
{
53+
"fixed": "23.6.7"
54+
}
55+
]
56+
}
57+
],
58+
"database_specific": {
59+
"last_known_affected_version_range": "< 23.6.6"
60+
}
61+
},
62+
{
63+
"package": {
64+
"ecosystem": "Maven",
65+
"name": "com.vaadin:flow-project"
66+
},
67+
"ranges": [
68+
{
69+
"type": "ECOSYSTEM",
70+
"events": [
71+
{
72+
"introduced": "24.0.0"
73+
},
74+
{
75+
"fixed": "24.9.9"
76+
}
77+
]
78+
}
79+
],
80+
"database_specific": {
81+
"last_known_affected_version_range": "< 24.9.8"
82+
}
83+
},
84+
{
85+
"package": {
86+
"ecosystem": "Maven",
87+
"name": "com.vaadin:flow-project"
88+
},
89+
"ranges": [
90+
{
91+
"type": "ECOSYSTEM",
92+
"events": [
93+
{
94+
"introduced": "25.0.0"
95+
},
96+
{
97+
"fixed": "25.0.3"
98+
}
99+
]
100+
}
101+
],
102+
"database_specific": {
103+
"last_known_affected_version_range": "< 25.0.2"
104+
}
105+
}
106+
],
107+
"references": [
108+
{
109+
"type": "ADVISORY",
110+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-2741"
111+
},
112+
{
113+
"type": "WEB",
114+
"url": "https://github.com/vaadin/flow/pull/23125"
115+
},
116+
{
117+
"type": "WEB",
118+
"url": "https://github.com/vaadin/flow/pull/23130"
119+
},
120+
{
121+
"type": "WEB",
122+
"url": "https://github.com/vaadin/flow/pull/23131"
123+
},
124+
{
125+
"type": "WEB",
126+
"url": "https://github.com/vaadin/flow/pull/23133"
127+
},
128+
{
129+
"type": "WEB",
130+
"url": "https://github.com/vaadin/flow/pull/23135"
131+
},
132+
{
133+
"type": "PACKAGE",
134+
"url": "https://github.com/vaadin/flow"
135+
},
136+
{
137+
"type": "WEB",
138+
"url": "https://vaadin.com/security/cve-2026-2741"
139+
}
140+
],
141+
"database_specific": {
142+
"cwe_ids": [
143+
"CWE-22"
144+
],
145+
"severity": "LOW",
146+
"github_reviewed": true,
147+
"github_reviewed_at": "2026-03-12T14:19:41Z",
148+
"nvd_published_at": "2026-03-10T18:18:48Z"
149+
}
150+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-g5xx-pwrp-g3fv",
4+
"modified": "2026-03-12T14:19:15Z",
5+
"published": "2026-03-12T14:19:15Z",
6+
"aliases": [
7+
"CVE-2026-31860"
8+
],
9+
"summary": "Unhead has XSS bypass in `useHeadSafe` via attribute name injection and case-sensitive protocol check",
10+
"details": "## Summary\n\n`useHeadSafe()` can be bypassed to inject arbitrary HTML attributes, including event handlers, into SSR-rendered `<head>` tags. This is the composable that Nuxt docs recommend for safely handling user-generated content.\n\n## Details\n\n**XSS via `data-*` attribute name injection**\n\nThe `acceptDataAttrs` function (safe.ts, line 16-20) allows any property key starting with `data-` through to the final HTML. It only checks the prefix, not whether the key contains spaces or other characters that break HTML attribute parsing.\n\n```typescript\nfunction acceptDataAttrs(value: Record<string, string>) {\n return Object.fromEntries(\n Object.entries(value || {}).filter(([key]) => key === 'id' || key.startsWith('data-')),\n )\n}\n```\n\nThis result gets merged into every tag's props at line 114:\n\n```typescript\ntag.props = { ...acceptDataAttrs(prev), ...next }\n```\n\nThen `propsToString` (propsToString.ts, line 26) interpolates property keys directly into the HTML string with no sanitization:\n\n```typescript\nattrs += value === true ? ` ${key}` : ` ${key}=\"${encodeAttribute(value)}\"`\n```\n\nA space in the key breaks out of the attribute name. Everything after the space becomes separate HTML attributes.\n\n### PoC\n\nThe most practical vector uses a `link` tag. `<link rel=\"stylesheet\">` fires `onload` once the stylesheet loads, giving reliable script execution:\n\n```javascript\nuseHeadSafe({\n link: [{\n rel: 'stylesheet',\n href: '/valid-stylesheet.css',\n 'data-x onload=alert(document.domain) y': 'z'\n }]\n})\n```\n\nSSR output:\n\n```html\n<link data-x onload=alert(document.domain) y=\"z\" rel=\"stylesheet\" href=\"/valid-stylesheet.css\">\n```\n\nThe browser parses `onload=alert(document.domain)` as its own attribute. Once the stylesheet loads, the handler fires.\n\nThe same injection works on any tag type since `acceptDataAttrs` is applied to all of them at line 114. Here's the same thing on a `meta` tag (the injected attributes render, though `onclick` doesn't fire on non-interactive `<meta>` elements):\n\n```javascript\nuseHeadSafe({\n meta: [{\n name: 'description',\n content: 'legitimate content',\n 'data-x onclick=alert(document.domain) y': 'z'\n }]\n})\n```\n\n### Realistic scenario\n\nA Nuxt app accepts SEO metadata from a CMS or user profile. The developer uses `useHeadSafe()` as the docs recommend. An attacker puts a `data-*` key with spaces and an event handler into their input. The payload renders into the HTML on every page load.\n\n## Suggested fix\n\nFor vulnerability 1, validate that attribute names only contain characters legal in HTML attributes:\n\n```typescript\nconst SAFE_ATTR_RE = /^[a-zA-Z][a-zA-Z0-9\\-]*$/\n\nfunction acceptDataAttrs(value: Record<string, string>) {\n return Object.fromEntries(\n Object.entries(value || {}).filter(\n ([key]) => (key === 'id' || key.startsWith('data-')) && SAFE_ATTR_RE.test(key)\n ),\n )\n}\n```",
11+
"severity": [],
12+
"affected": [
13+
{
14+
"package": {
15+
"ecosystem": "npm",
16+
"name": "unhead"
17+
},
18+
"ranges": [
19+
{
20+
"type": "ECOSYSTEM",
21+
"events": [
22+
{
23+
"introduced": "0"
24+
},
25+
{
26+
"fixed": "2.1.11"
27+
}
28+
]
29+
}
30+
],
31+
"database_specific": {
32+
"last_known_affected_version_range": "<= 2.1.10"
33+
}
34+
}
35+
],
36+
"references": [
37+
{
38+
"type": "WEB",
39+
"url": "https://github.com/unjs/unhead/security/advisories/GHSA-g5xx-pwrp-g3fv"
40+
},
41+
{
42+
"type": "WEB",
43+
"url": "https://github.com/unjs/unhead/commit/9ecc4f9568b0e23938f36d4b23fcfa4a18a89045"
44+
},
45+
{
46+
"type": "PACKAGE",
47+
"url": "https://github.com/unjs/unhead"
48+
},
49+
{
50+
"type": "WEB",
51+
"url": "https://github.com/unjs/unhead/releases/tag/v2.1.11"
52+
}
53+
],
54+
"database_specific": {
55+
"cwe_ids": [
56+
"CWE-79"
57+
],
58+
"severity": "HIGH",
59+
"github_reviewed": true,
60+
"github_reviewed_at": "2026-03-12T14:19:15Z",
61+
"nvd_published_at": null
62+
}
63+
}

0 commit comments

Comments
 (0)