Skip to main content
Neurosymbolic verification for your CI/CD pipeline
QWED Security is a Verified Publisher on GitHub Marketplace. Install the GitHub App to auto-verify every PR with deterministic math, logic, and security checks — no workflow file needed. See the GitHub App docs for details.

What is QWED?

QWED combines Neural Networks (LLMs) with Symbolic Reasoning (SymPy, Z3) to provide deterministic verification of AI outputs. Use cases:
  • ✅ Verify mathematical calculations in PRs
  • ✅ Check logical reasoning in documentation
  • ✅ Detect unsafe code patterns
  • ✅ Validate LLM outputs before deployment

Quick start

Add this to your .github/workflows/verify.yml:

Extension GitHub Actions

Use these extension-specific actions when you want domain-focused checks in your pipeline.

Inputs

Each engine requires different inputs:
  • math — requires query (passed as the expression to verify)
  • logic — requires query
  • code — requires llm_output (passed as the code to analyze)

Provider and model selection

New in v5.0.0
You can now specify which LLM provider and model the action uses. When you pass api_key, QWED automatically maps it to the correct provider-specific environment variable (OPENAI_API_KEY, ANTHROPIC_API_KEY, or GOOGLE_API_KEY) based on your provider selection.

PII masking

Set mask_pii: "true" to automatically redact personally identifiable information (email addresses, phone numbers, SSNs) from inputs and outputs before they reach the LLM.

Scan paths

The scan modes (scan-code, verify-shell, scan-secrets) read files from the workspace that match paths. When paths is unset, empty, ., or ./, the action uses the mode’s default patterns: A scan is VERIFIED with admission: ADMIT only when all of the following hold:
  • At least one file was scanned across all the patterns. A single pattern that matches nothing doesn’t deny the scan on its own.
  • Every pattern expanded without error.
  • Every matched file was read and, for scan-code, parsed as Python.
  • No findings were reported.
Otherwise the verdict is UNVERIFIABLE with admission: DENY and an empty proof_ref. The step fails unless fail_on_findings is "false". The following count as errors that deny the scan:
  • No file scanned at all: every pattern matched nothing (files_scanned is 0).
  • A matched file that can’t be read.
  • A Python file that doesn’t parse.
  • From v9.0.0, for scan-code, a Python file whose coding declaration isn’t UTF-8 or whose bytes aren’t valid UTF-8. Python decodes a file by its declaration, so a scan that read it as UTF-8 could check different code from what runs.
  • An absolute pattern, a pattern using .., or a match that resolves outside the workspace, including a symlink to a file elsewhere. The action names the offending file in the error.
Matches that aren’t regular files, such as a symlinked directory, are ignored, and so are files outside the mode’s type: scan-code keeps only .py files and verify-shell only .sh files. A file reached through more than one pattern or link is scanned once. scan-secrets scans only the files your paths match. It doesn’t fall back to the default patterns when your paths match nothing.
Behavior change in v8.0.0. Jobs that passed before can now fail. A paths value under which no file is scanned, or a scan-code run that includes Python files that don’t parse, now returns UNVERIFIABLE · DENY. Narrow paths to the files you want scanned.

What scan-code checks

Changed in v9.0.0 (GHSA-hg73-j3qv-w4vh)
scan-code matches dangerous calls by name and also runs CodeVerifier on each Python file. It takes CodeVerifier’s code- and process-execution findings, so calls reached through a from-import, an alias or importlib, such as from os import system; system(...), are findings. Before v9.0.0, these passed with a green badge. scan-code doesn’t take CodeVerifier’s file and network findings, because repository code normally reads files and calls the network.
  • A getattr, setattr or delattr call with a constant name is checked as that attribute.
  • A call whose attribute name is computed at run time can’t be checked. It is reported as a warning, counted in the warnings_count output. Warnings don’t fail the scan. In SARIF output they are results at level warning.
  • A Python file whose coding declaration is not UTF-8, or whose bytes aren’t valid UTF-8, is not read, and the scan is UNVERIFIABLE · DENY.
A passing scan means no known dangerous pattern was found. It doesn’t mean the code is safe.

Outputs

Verification Context outputs

New in v7.1.0
Every action mode (verify, scan-secrets, scan-code, verify-shell) emits Verification Context v1.0 outputs alongside the legacy verified flag. The invariants are fail-closed: proof_ref is set only on a VERIFIED verdict, and non-verified verdicts always carry admission: DENY.

How verify mode decides

Changed in v8.0.0 (GHSA-jppc-7r58-7pjh)
In verify mode, the Action gates its exit code and its verified and admission outputs on the Python SDK’s is_verified, which follows the server’s admission. The verdict output is the server’s status. A BLOCKED or UNVERIFIABLE result is never admitted. The log line reports both values, for example:
This line means the server reached a verdict but did not admit the content. With fail_on_findings: true (the default), the step fails. With the code engine, the Action always sends the code to the server’s CodeVerifier. If the server can’t be reached, the verdict is UNVERIFIABLE · DENY.

Output integrity

Changed in v8.0.0 (GHSA-j788-44pc-8hp3)
The Action writes each output to GITHUB_OUTPUT between random heredoc delimiters (name<<qwed_<random>). A newline in a value, such as the verifier’s explanation, can’t start another entry or overwrite verified. The Action also prints ::stop-commands::<random token> when it starts, so the runner ignores workflow commands in anything the Action prints for the rest of the step.
Pin the Action to QWED-AI/qwed-verification@v8.0.0 or later. In earlier versions, a newline in the explanation could set verified=true, and verify mode could admit a result the server had blocked. Gate downstream steps on steps.<id>.outputs.admission == 'ADMIT' and keep fail_on_findings: true.

Examples

Verify math in PRs

Verify logic

Check code security

Scan files with SARIF output

Privacy and security

  • 🔒 PII Masking: Automatically mask sensitive data (emails, SSNs, credit cards)
  • 🏠 Local Option: Use local LLMs (Ollama) for zero cloud exposure
  • 🔐 API Keys: Use GitHub Secrets for secure credential management
  • ✅ Open Source: Full transparency, no black boxes

How it works

Example:
  • Query: “What is the derivative of x^2?”
  • LLM says: “2x”
  • SymPy computes: diff(x**2, x) = 2*x
  • QWED: ✅ MATCH! Verified with 100% confidence

Requirements

API keys (select one):
  • OpenAI: OPENAI_API_KEY
  • Anthropic: ANTHROPIC_API_KEY
  • Google: GOOGLE_API_KEY
Or use local LLMs (Ollama) for free!

Documentation

Support

License

Apache 2.0 - See LICENSE
Made with 💜 by QWED-AI