Skip to main content

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.
{
  "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.
TypeMatch condition
status_coderesponse.status === value
status_rangeresponse.status between min and max inclusive
body_containsresponse.body.includes(value)
header_presentresponse.headers has a key matching name (case-insensitive)
header_valueresponse.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.
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.
StatusWhen
verifiedProbe response matched the expected signal.
unverifiedProbe ran but the response did not match. The finding still posts, but certainty is lower.
failedProbe could not reach the preview because of DNS, timeout, refused origin, or malformed URL.
skippedNo 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.
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

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.

Inline findings

.qodex.yaml reference

Check Run and merge gating

Troubleshooting