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
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
Setmask_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.
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_scannedis0). - 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.
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.
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,setattrordelattrcall 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_countoutput. Warnings don’t fail the scan. In SARIF output they are results at levelwarning. - 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.
Outputs
Verification Context outputs
New in v7.1.0
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)
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:
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)
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.
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
- 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
Documentation
Support
- Issues: GitHub Issues
- PyPI: qwed
- Twitter: @rahuldass29
License
Apache 2.0 - See LICENSEMade with 💜 by QWED-AI