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-6p94-4f75-g2wq

    SQLVerifier certifies destructive and administrative SQL as VERIFIED/ADMIT (DML hidden in CTEs; incomplete statement-type denylist)

    highPublished 16 October 2026

    Impact

    SQLVerifier.verify_sql returns status=VERIFIED, developer_fields.is_valid=True and admission ADMIT for two classes of query that its own checks reject in plain form. A consumer that gates on the admission decision (the documented primary use, including POST /verify/sql) is told the query is "safe to execute".

    1. Data-modifying CTEs. _check_command_type classifies a query only by the type of the root AST node. A data-modifying statement wrapped in a CTE — WITH d AS (DELETE FROM t ... RETURNING *) SELECT * FROM d — has a Select root, so it is certified VERIFIED/ADMIT, while the bare DELETE is correctly BLOCKED. On PostgreSQL the CTE runs to completion even if never referenced. UPDATE and INSERT variants behave the same.

    2. Incomplete statement-type denylist. DESTRUCTIVE_COMMANDS/ADMIN_COMMANDS are two hand-maintained sets of root classes. Many statement families are in neither set and pass with zero issues and ADMIT, including COPY ... FROM PROGRAM, COPY ... FROM/TO <file>, ATTACH/DETACH (SQLite, the default dialect), PRAGMA, ANALYZE, CACHE/UNCACHE, LOAD DATA, EXPORT DATA. Depending on the consumer's database and privileges, these range from server-side command execution and arbitrary file read to settings changes.

    Affected

    All releases through 7.2.3. Reproduced at the verdict level on main @ a4d6714 (v7.2.3), across the sqlite, postgres and mysql dialects, with and without schema_ddl. The 7.2.3 schema_table_missing change blocks only a few sub-variants, and only when schema_ddl is supplied (/verify/sql requires it; /verify/batch defaults it to empty, skipping the check).

    Details

    • Root-type-only classification: src/qwed_new/core/sql_verifier.py _check_command_type (around lines 392-414), with the two denylist sets (around 103-127).
    • critical_count == 0 then yields is_valid=True/VERIFIED; admission_decision (src/qwed_new/core/diagnostics.py:1114-1136) maps that to ADMIT.
    • guards/sql_guard.py already walks the whole tree with find(), but the verification path does not use it.

    Fix

    Classify over the whole parse tree, not just the root node, and move from a denylist to an allowlist of read-only statement types (plus explicit handling of CTEs that contain DML and of statement families such as COPY/ATTACH/PRAGMA). Fail closed on any statement type not on the allowlist.

    Source

    OpenVuln external audit (2026-10); findings BUG-R2-C2-A2-H1 and the command-type policy finding. Reported privately; not previously disclosed.

    Affected and fixed

    Package
    qwed
    Affected
    <= 7.2.3
    Fixed in
    7.3.0
    Severity
    high
    Weakness
    CWE-184 Incomplete List of Disallowed Inputs
    CWE-693 Protection Mechanism Failure
    View on GitHub

    All advisories