---
title: "SQLVerifier certifies destructive and administrative SQL as VERIFIED/ADMIT (DML hidden in CTEs; incomplete statement-type denylist)"
description: "GHSA-6p94-4f75-g2wq: SQLVerifier certifies destructive and administrative SQL as VERIFIED/ADMIT (DML hidden in CTEs; incomplete statement-type denylist). Affected and fixed versions of qwed."
url: https://qwedai.com/security/advisories/ghsa-6p94-4f75-g2wq
---

GHSA-6p94-4f75-g2wq

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

high Published 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](https://github.com/QWED-AI/qwed-verification/security/advisories/GHSA-6p94-4f75-g2wq)

[All advisories](https://qwedai.com/security/advisories)
