Skip to content

Correctness batch: blocklist case/base64 bypasses, verified-warning conflation, forgeable VerificationResult, vacuous verify_structured_output, model-supplied budgets, VerifiedOpenAI guards=None default, README overclaims, streaming fail-open mode #31

Description

Summary

Medium-severity findings from the 2026-08-23 adversarial audit of v0.4.0 @ b460d70, batched; each independent. All CONFIRMED BY EXECUTION unless noted.

1. Blocklist/pattern bypasses (CONFIRMED)

Tool names are exact-match against the blocked list — "Bash", "sh", "powershell" (as a name with clean arguments) all pass; the arguments pattern scan is defeated by base64-encoded payloads. Blocklist normalization (casefold) + common-shell coverage needed; document that pattern scanning is a heuristic, not a boundary.

2. verified semantics conflated in both directions (CONFIRMED)

  • Zero guards ⇒ verified=True (tracked in the empty-verifier P1).
  • PII-only warning ⇒ verified=False while blocked=False — a warning fails "verified" even though nothing blocked. Executed: email-PII response → verified=False, guards_failed=1, blocked=False.
  • warn_result docstring says "passes but with warning" while returning passed=False (base.py:100-109).

verified currently means neither "checks ran" nor "nothing bad found." Suggested: verified = all checks passed; warnings carried as a separate visible state; fix the docstring lie.

3. Verification results are freely forgeable (CONFIRMED)

VerificationResult(verified=True, response={...}) is a public constructible dataclass with no binding to any input, no request/tool-call ID, no hash, no attestation. to_dict() emits JSON any downstream can mint, replay, or attach to a different action. Anyone who did not run a verification can manufacture the identical object. (Deeper binding design ties to the ecosystem attestation conversation — qwed-verification #319 et al.)

4. verify_structured_output(output, schema=None, guards=[]) verifies nothing (CONFIRMED)

No schema and no guards ⇒ verified=True for arbitrary output — the empty-verifier semantics again, via a convenience API that appears to promise structure checking.

5. Budget check trusts model-supplied accounting (STATIC)

_check_budget reads response["usage"]["cost"] / total_tokens — fields supplied by the very output under verification. A malicious/compromised model under-reports usage and defeats the cap. Budget should be enforced from trusted-side accounting or signed provider reports.

6. VerifiedOpenAI(guards=None) default (STATIC)

The wrapper whose README line says "Automatically verified before returning" defaults to zero guards — silently inheriting the empty-verifier semantics. Require guards at construction or warn loudly.

7. README claims outrun the implementation (STATIC)

"Deterministic — uses symbolic logic and formal verification rules", "100% Deterministic", "prevents dangerous tool calls" (README.md:16,43,45,68): implementation is configurable regex blocklists + arithmetic checks with the shape/case gaps above. Per the audit standard: either harden the code or state the precise claim boundary.

8. Streaming block_on_failure=False passes failed items through unmodified (STATIC)

Documented warn-only mode — acceptable as an explicit opt-in, but should be labeled as disabling the trust boundary (the blocked-mode replacement item also relies on the consumer honoring system_intervention).

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2P2 — hardening / semantic correctnessbugSomething isn't workingsecuritysecuritytrust-boundaryVerification trust-boundary integrity

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions