Open-source runtime verification for AI, licensed under Apache-2.0.
QWED
Contact

Type to search pages, engines and advisories.

    Looking for API details?Search the docs

    GHSA-j3x4-v3p2-4mq3

    Stats and consensus sandbox static gates can be bypassed to reach blocked primitives (incomplete fix of #336 / GHSA-6rxr-gq43-694g)

    highPublished 16 October 2026

    Impact

    The code that runs inside the Docker sandbox on the stats and consensus paths is screened by four static gates (a substring scan, an AST call-name matcher, a raw-pattern scan, and the executor's AST matchers). A family of constructs defeats these gates while evaluating, at runtime, to a primitive the gates are meant to block (process execution, or eval/exec via the interpreter namespace). All are certified "safe" by every static gate.

    Verified constructs (described at the gate level; harmless id/true placeholders, nothing executed):

    • A reference to a blocked callable stored through an assignment, instead of a direct call.
    • A reference to a reflection builtin that yields the builtins namespace.
    • Banned names assembled at runtime from split or repeated string literals, so no banned substring appears in the source.
    • Names carried as string constants into a runtime getattr, so no attribute node or blocked call-name appears.
    • The os/subprocess module re-acquired through sys.modules[...] after renaming sys, defeating the sys allowlist.
    • A call-name denylist gap (posix_spawn, posix_spawnp, Popen).
    • On the consensus expression path, chr()-synthesised names and vars()/dict.get() chains.

    Affected

    All releases through 7.2.3; reproduced at the gate level on main @ a4d6714. Exploitation requires the Docker executor to be enabled (stock deployments fail closed — without a Docker socket the path returns BLOCKED runtime_unavailable). In-container execution and any host-side impact were not reproduced here (no Docker); the control-integrity bypass is unconditional on this path.

    Details

    • src/qwed_new/core/secure_code_executor.py: the OS-call denylist and the AST matchers that key on call nodes / name-rooted chains only (around 50-193).
    • src/qwed_new/core/stats_verifier.py: the bundled validation and the alias/substring checks (around 333-382, 821-883).
    • src/qwed_new/core/code_verifier.py: the substring and AST process-execution checks (the latter resolves import aliases only). This is the same structural class as closed #336 (every added matcher still keys on identifier syntax) and the import-alias-only limit of GHSA-6rxr; it also covers the stats-path analogue of the split-string-literal bypass fixed for /verify/math in #329.

    Fix

    Stop extending name-set denylists. Isolate the wrapper namespace so the executed code has no access to eval/exec/vars/getattr/__import__/sys.modules/reflection builtins (a capability allowlist for builtins and modules), and reject non-call first-class references to blocked names. Add the missing posix_spawn/posix_spawnp/Popen names as a stopgap only.

    Source

    OpenVuln external audit (2026-10), stats and consensus sandbox-gate findings. Reported privately.

    Affected and fixed

    Package
    qwed
    Affected
    <= 7.2.3
    Fixed in
    7.3.0
    Severity
    high
    Weakness
    CWE-94 Improper Control of Generation of Code ('Code Injection')
    CWE-184 Incomplete List of Disallowed Inputs
    View on GitHub

    All advisories