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

# Verification probes

> How Qodex safely checks whether a PR finding is reproducible on a preview deployment.

# Verification probes

Verification probes are safe requests Qodex runs against a PR preview deployment to check whether a finding is reproducible.

They help Qodex move beyond static diff review. If a preview URL exists and the issue can be checked safely, Qodex can run the code and attach request and response evidence.

## How probes work

The agent adds a probe to a finding only when the bug can be checked with one safe GET request against the preview build. Qodex discovers the preview URL through the GitHub Deployments API, checks the URL against safety rules and the repo allowlist, runs the probe, and attaches the result to the inline comment.

If the response matches the expected signal, the finding is `verified`. If the probe runs but does not match, the finding is `unverified`. If Qodex cannot run a probe, the finding is `skipped` or `failed` depending on why.

Only verified findings can block a merge when merge gating is enabled.

## Probe spec

A probe spec belongs to a single finding in the review output. The shape is intentionally narrow.

```json theme={null}
{
  "method": "GET",
  "path": "/api/users/9999",
  "expected_signal": { "type": "status_code", "value": 200 }
}
```

## Method

GET is the only supported method today. Non-idempotent verbs are gated behind an explicit `.qodex.yaml` opt-in (`probes.allow_non_get`, defaults to `false`) and are on the roadmap.

## Path

The path must resolve against the discovered preview URL on the same origin. Qodex refuses paths that try to break the origin, including `//`, `http://`, `javascript:`, `..` outside the origin, or control characters.

## Expected signal

The expected signal describes what the bug produces, not what the correct behavior produces. A match means the bug is reproducible.

| Type             | Match condition                                                 |
| ---------------- | --------------------------------------------------------------- |
| `status_code`    | `response.status === value`                                     |
| `status_range`   | `response.status` between `min` and `max` inclusive             |
| `body_contains`  | `response.body.includes(value)`                                 |
| `header_present` | `response.headers` has a key matching `name` (case-insensitive) |
| `header_value`   | `response.headers[name] === value` (case-insensitive key)       |

## Preview URL discovery

For each PR, Qodex reads the GitHub Deployments API for the head SHA and picks the first successful deployment with a public `environment_url` or `target_url`.

The candidate URL must pass the global safe-URL check. If the repo defines `probes.preview_host_allowlist` in `.qodex.yaml`, the host must match that allowlist too. If no deployment qualifies, the review still runs but probes are skipped.

## Security guards

The probe is agent-controlled, and the agent reads PR content, so Qodex treats every probe as untrusted.

Qodex refuses probes that target unsafe or private destinations: HTTP, `localhost`, private or reserved IP ranges, IPv6 literals, and obfuscated IP encodings are all rejected before any request is made.

## Per-repo allowlist

`.qodex.yaml` can restrict preview probes to known hosts.

```yaml theme={null}
probes:
  preview_host_allowlist:
    - "*.vercel.app"
    - "preview.acme-staging.com"
```

Patterns may start with `*.` to allow a domain and its subdomains. A `null` allowlist, the default, is permissive, but the global SSRF guard still applies.

When the allowlist is set and the discovered host does not match, the review proceeds without probes and the walkthrough explains that the preview deployment was skipped.

## Probe execution

Once a probe URL is resolved and origin-checked:

* Headers are Qodex-controlled only. Any headers suggested by the agent are dropped.
* Body is empty. Any body suggested by the agent is ignored.
* Redirects are not followed.
* Timeout is 8 seconds.
* Response body is captured up to 8 KB and then truncated.

The response body is escaped before it is embedded in the inline comment so hostile payloads cannot break out of the markdown fence.

## Probe results

Each finding gets one probe status.

| Status       | When                                                                                             |
| ------------ | ------------------------------------------------------------------------------------------------ |
| `verified`   | Probe response matched the expected signal.                                                      |
| `unverified` | Probe ran but the response did not match. The finding still posts, but certainty is lower.       |
| `failed`     | Probe could not reach the preview because of DNS, timeout, refused origin, or malformed URL.     |
| `skipped`    | No probe was emitted, no preview was discoverable, or the allowlist blocked the discovered host. |

## Inline render

When a probe runs, the inline comment includes a probe badge and a collapsible "Probe evidence" block.

````markdown theme={null}
Critical **Missing auth on profile endpoint** - _security_ - Verified

The endpoint reads the ID from the URL and queries the row without
checking the session user.

```suggestion
if (id !== req.session.userId) return res.status(403).end();
```

<details><summary>Probe evidence</summary>

**Request** `GET https://pr-42.preview.acme.app/api/profile/9999`

**Response** `200 OK` in 142ms

~~~
{"id":"9999","email":"victim@example.com"}
~~~

**Verdict** response status 200 matched expected 200
</details>
````

## On the roadmap

<Tip>
  Opt-in non-GET probes (`POST`, `PUT`, `PATCH`, `DELETE`) are on the roadmap, gated by `.qodex.yaml`'s `probes.allow_non_get: true`. They are useful for verifying state-mutating bugs on preview environments you are willing to mutate.
</Tip>

## Related

<CardGroup cols={2}>
  <Card title="Inline findings" icon="message-square-text" href="/pr-review-inline-findings" />

  <Card title=".qodex.yaml reference" icon="file-code" href="/qodex-yaml-reference" />

  <Card title="Check Run and merge gating" icon="shield-check" href="/pr-review-check-run" />

  <Card title="Troubleshooting" icon="life-buoy" href="/pr-review-troubleshooting" />
</CardGroup>
