> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qwedai.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# QWED GitHub Action

> Integrate QWED neurosymbolic verification into CI/CD pipelines. Verify math calculations, logical reasoning, and code patterns in pull requests automatically.

**Neurosymbolic verification for your CI/CD pipeline**

> [!NOTE]
>
> [QWED Security](https://github.com/marketplace/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](https://docs.qwedai.tech/advanced/github-app.md) 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`:

```yaml theme={null}
name: Verify with QWED

on: [push, pull_request]

jobs:
  verify:
    runs-on: ubuntu-latest
    steps:
      - name: Verify Calculation
        uses: QWED-AI/qwed-verification@v9.1.0
        with:
          api_key: ${{ secrets.QWED_API_KEY }}
          action: verify
          engine: math
          query: "x**2 + 2*x + 1 = (x+1)**2"
```

## Extension GitHub Actions

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

| Icon | Action | Description |
| - | - | - |
| 💰 | [QWED Finance Guard](https://github.com/marketplace/actions/qwed-finance-guard) | Verify financial calculations and compliance signals before merging. |
| ⚖️ | [QWED Legal Verification](https://github.com/marketplace/actions/qwed-legal-verification) | Validate legal reasoning, deadlines, citations, and clause consistency. |
| 🧾 | [QWED Protocol Verification](https://github.com/marketplace/actions/qwed-protocol-verification) | Verify protocol-level logic and deterministic rule conformance. |
| 🛒 | [QWED Commerce Auditor](https://github.com/marketplace/actions/qwed-commerce-auditor) | Audit checkout math, pricing, and transaction integrity in commerce flows. |

## Inputs

| Input | Description | Required | Default |
| - | - | - | - |
| `api_key` | QWED API key (optional for local mode) | No | - |
| `action` | Action type: `verify`, `scan-secrets`, `scan-code`, `verify-shell` | No | `verify` |
| `provider` | LLM provider (`openai`, `anthropic`, `gemini`) | No | - |
| `model` | Model name (e.g., `gpt-4o`, `claude-sonnet-4-20250514`) | No | - |
| `mask_pii` | Mask PII in inputs and outputs | No | `false` |
| `query` | The user query (e.g., "Derivative of x^2"). Required for `math` and `logic` engines. | Conditional | - |
| `llm_output` | The LLM output to verify. Required for the `code` engine. | Conditional | - |
| `engine` | Verification engine: `math`, `logic`, `code`, `sql`, `shell` | No | `math` |
| `paths` | Comma-separated glob patterns for files to scan (e.g., `src/**/*.py`). Leave unset to use the mode default. See [Scan paths](#scan-paths) | No | `''` (mode default) |
| `output_format` | Output format: `text`, `json`, `sarif`, `verification-context` | No | `text` |
| `fail_on_findings` | Fail the action if security issues are found or a scan is `UNVERIFIABLE` | No | `true` |

> [!NOTE]
>
> 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

> [!NOTE]
>
> 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.

```yaml theme={null}
- name: Verify with Claude
  uses: QWED-AI/qwed-verification@v9.1.0
  with:
    api_key: ${{ secrets.ANTHROPIC_API_KEY }}
    provider: anthropic
    model: claude-sonnet-4-20250514
    action: verify
    engine: math
    query: "sqrt(144) = 12"
```

### 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.

```yaml theme={null}
- name: Verify with PII masking
  uses: QWED-AI/qwed-verification@v9.1.0
  with:
    api_key: ${{ secrets.QWED_API_KEY }}
    mask_pii: "true"
    action: verify
    engine: logic
    query: "User john@example.com claims order total is correct"
```

### 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:

| Mode | Default patterns |
| - | - |
| `scan-code` | `**/*.py` |
| `verify-shell` | `**/*.sh` |
| `scan-secrets` | `**/*.env`, `**/*.json`, `**/*.yaml`, `**/*.yml`, `**/*.toml`, `**/*.ini` |

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.

> [!WARNING]
>
> **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.

```yaml theme={null}
- name: Scan source code
  id: scan
  uses: QWED-AI/qwed-verification@v9.1.0  # files_scanned needs v8.0.0 or later
  with:
    action: scan-code
    paths: "src/**/*.py,scripts/**/*.py"

- name: Report coverage
  if: always()
  run: echo "Scanned ${{ steps.scan.outputs.files_scanned }} files, verdict ${{ steps.scan.outputs.verdict }}"
```

### What scan-code checks

> [!NOTE]
>
> Changed in v9.0.0 ([GHSA-hg73-j3qv-w4vh](https://github.com/QWED-AI/qwed-verification/security/advisories/GHSA-hg73-j3qv-w4vh))

`scan-code` matches dangerous calls by name and also runs [`CodeVerifier`](https://docs.qwedai.tech/engines/code#security-scanning-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

| Output | Description |
| - | - |
| `verified` | `true` if verification passed or no issues found |
| `explanation` | Detailed proof or error explanation |
| `findings_count` | Number of security issues found (for scan modes) |
| `files_scanned` | Number of files scanned (for scan modes, from v8.0.0). `0` means the scan was not admitted |
| `warnings_count` | Number of `scan-code` warnings: attribute access by a computed name, which is not checked and doesn't fail the scan (from v9.0.0) |
| `badge_url` | URL for QWED verified badge |
| `sarif_file` | Path to SARIF output file (if `output_format=sarif`) |
| `verdict` | [Verification Context v1.0](https://docs.qwedai.tech/specs/verification-context.md) verdict: `VERIFIED`, `UNVERIFIABLE`, or `BLOCKED` (v7.1.0) |
| `admission` | Safe-to-run decision: `ADMIT` or `DENY`. Gate downstream steps on this, not on `verified` alone (v7.1.0) |
| `proof_ref` | `sha256:<64-hex>` evidence commitment. Set only when `verdict` is `VERIFIED`; empty otherwise (v7.1.0) |
| `verification_context` | Full Verification Context v1.0 document as compact JSON. Emitted only when `output_format` is `json` or `verification-context` (v7.1.0) |

### Verification Context outputs

> [!NOTE]
>
> New in v7.1.0

Every action mode (`verify`, `scan-secrets`, `scan-code`, `verify-shell`) emits [Verification Context v1.0](https://docs.qwedai.tech/specs/verification-context.md) 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`.

```yaml theme={null}
- name: Verify math
  id: qwed
  uses: QWED-AI/qwed-verification@v9.1.0
  with:
    action: verify
    engine: math
    query: "sqrt(144) = 12"
    output_format: json

- name: Gate on admission
  if: steps.qwed.outputs.admission == 'ADMIT'
  run: echo "proof_ref=${{ steps.qwed.outputs.proof_ref }}"
```

### How verify mode decides

> [!NOTE]
>
> Changed in v8.0.0 ([GHSA-jppc-7r58-7pjh](https://github.com/QWED-AI/qwed-verification/security/advisories/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:

```text theme={null}
🔍 Verdict: VERIFIED · DENY
```

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

> [!NOTE]
>
> Changed in v8.0.0 ([GHSA-j788-44pc-8hp3](https://github.com/QWED-AI/qwed-verification/security/advisories/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.

> [!WARNING]
>
> 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

```yaml theme={null}
- name: Check Math
  uses: QWED-AI/qwed-verification@v9.1.0
  with:
    api_key: ${{ secrets.QWED_API_KEY }}
    action: verify
    engine: math
    query: "2**10 = 1024"
```

### Verify logic

```yaml theme={null}
- name: Check Logic
  uses: QWED-AI/qwed-verification@v9.1.0
  with:
    api_key: ${{ secrets.QWED_API_KEY }}
    action: verify
    engine: logic
    query: "(AND (GT x 5) (LT x 10))"
```

### Check code security

```yaml theme={null}
- name: Security Check
  uses: QWED-AI/qwed-verification@v9.1.0
  with:
    api_key: ${{ secrets.QWED_API_KEY }}
    action: verify
    engine: code
    llm_output: "eval(user_input)"
```

### Scan files with SARIF output

```yaml theme={null}
- name: Scan Code
  uses: QWED-AI/qwed-verification@v9.1.0
  with:
    api_key: ${{ secrets.QWED_API_KEY }}
    action: scan-code
    paths: "**/*.py"
    output_format: sarif
    fail_on_findings: "true"
```

## 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

```text theme={null}
Your Query
    ↓
LLM Response (GPT-4, Claude, etc.)
    ↓
Symbolic Verification (SymPy, Z3, AST)
    ↓
✅ Deterministic Proof or ❌ Verification Failure
```

**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

* [**Full Documentation**](https://github.com/QWED-AI/qwed-verification/blob/main/README.md)
* [**PII Masking Guide**](https://github.com/QWED-AI/qwed-verification/blob/main/docs/PII_MASKING.md)
* [**LLM Configuration**](https://github.com/QWED-AI/qwed-verification/blob/main/docs/LLM_CONFIGURATION.md)
* [**Python SDK**](https://github.com/QWED-AI/qwed-verification/blob/main/docs/QWED_LOCAL.md)

## Support

* **Issues**: [GitHub Issues](https://github.com/QWED-AI/qwed-verification/issues)
* **PyPI**: [qwed](https://pypi.org/project/qwed/)
* **Twitter**: [@rahuldass29](https://x.com/rahuldass29)

## License

Apache 2.0 - See [LICENSE](https://github.com/QWED-AI/qwed-verification/blob/main/LICENSE)

***

[**Made with 💜 by QWED-AI**](https://github.com/QWED-AI)


## Related topics

- [QWED Security GitHub App: deterministic PR verification](https://docs.qwedai.tech/advanced/github-app.md)
- [QWED deployment guide](https://docs.qwedai.tech/advanced/deployment.md)
- [Changelog](https://docs.qwedai.tech/changelog.md)
- [Installation](https://docs.qwedai.tech/getting-started/installation.md)
- [Self-hosting](https://docs.qwedai.tech/advanced/self-hosting.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.