No signup · under 60 seconds
Run a real PR through VeriFlow
Paste a public GitHub PR URL and get a line-anchored second-pass security review back in under a minute. No account, email, or demo call required. Then keep reading the static sample below to see the full shape of the output.
The sample review covers veriflow/veriflow#142 — a real PR running the second-pass PR security review before a human opens the diff.
The sample below is one of dozens — every linkable review was minted from a real public pull request.
Synthetic review walkthrough
A fictional code change and one illustrative review finding
src/access/roles.ts21−return canAccess(user, parseRole(payload.role));21+return canAccess(user, payload.role ?? 'viewer');Unvalidated role fallback reaches the access decision
Illustratively, defaulting an absent role to viewer handles missing input but does not validate arbitrary values before they reach canAccess.
Suggested fix
const parsedRole = RoleSchema.safeParse(payload.role);
const role = parsedRole.success ? parsedRole.data : 'viewer';
return canAccess(user, role);Try it now
Paste a PR. Get the review back.
Choose a curated public PR or paste your own. VeriFlow streams the review stages and anchors every finding to the file and line that needs attention.
Example output
src/lib/auth.ts:L14Session cookie decoded without TTL guard
Sample review
A VeriFlow review on a real PR, top to bottom
The diff, three inline review comments, and the security-pass findings panel — synthesized from a real run, statically rendered so you can move at your own pace.
38− if (!verifyWebhookSignature(rawBody, headers)) {39− return new Response("invalid signature", { status: 401 });40− }41− const payload = JSON.parse(rawBody);42− const installationId = payload.installation.id;38 if (!verifyWebhookSignature(rawBody, headers)) {39 return new Response("invalid signature", { status: 401 });40 }41 const payload = JSON.parse(rawBody);42+ const installationId = payload.installation?.id ?? null;43+ if (installationId === null) {44+ return new Response("missing installation", { status: 400 });45 }46+ await dispatchToPipeline({ installationId, payload });12−export function requireUser(req: Request) {13− const session = readSessionCookie(req);14− if (!session) throw new Error("unauthenticated");15− return session.user;16−}12+export async function requireUser(req: Request) {13+ const session = await readSessionCookie(req);14 if (!session) throw new Error("unauthenticated");15+ const fresh = await refreshSessionIfStale(session);16+ if (!fresh) throw new Error("session expired");17+ return fresh.user;18 }21− "dependencies": {22− "next": "16.0.0",23− "react": "19.0.0"24− }21 "dependencies": {22 "next": "16.0.0",23+ "react": "19.0.0",24+ "yaml": "^2.4.0"25 }Security pass
Findings surfaced on this PR
Auth, secrets, injection, dependency risk — the four surfaces the second pass audits. Top-level severity aggregates to critical so the security team reads the same vocabulary here as on /security.
Security review
The PR aggregates to Critical — one reachable auth-path finding and one injection finding on a public-facing loader. Two advisory findings round out secrets hygiene and dependency risk.
Forged webhook payload reaches the review pipeline
After signature verification, the handler forwards the full payload to the review pipeline with no shape validation. A request that passes the HMAC but drops `pull_request.number` runs a review job against an undefined diff and surfaces an error in the audit log instead of a 400.
Session cookie decoded without TTL guard
`readSessionCookie` returns a session whose `iat` is tracked but never enforced against `exp`. A cookie minted under the previous rotation window still validates; pin the cookie secret to per-tenant HMAC keys so a leaked cookie does not survive rotation.
YAML loader accepts merge keys on reviewer content
`YAML.parse(reviewerConfig, { merge: true })` is invoked on content the LLM emits. A merge-key payload evaluates anchors against the host process — the gadget chain is small but well-known and the run target is the agent.
Added `yaml@^2.4.0` floats across the minor range
The caret allows any 2.x.y, which silently upgrades into a release that reintroduces a CVE closed earlier this quarter. Pin to a 2.4.x commit or switch to JSON / `YAML.parse` with explicit schema validation.
Transitive dep upgrade dropped, lockfile drift not flagged
The diff updates `next` and `react` together; if the `package-lock.json` regen landed but the review runner cached the prior lockfile hash, CI may resolve against stale versions on the next cold start. The CI preview step verifies the lockfile but the prod runner does not — propagate the check to the deploy gate.
Inline review
Three inline comments the reviewer left
Each comment is anchored to a path:line, ships a suggested fix, and reads with the same severity vocabulary surfaced in the security-pass panel above.
Webhook payload accepted without shape guard
`payload.installation?.id` now defends against a missing key, but the rest of the payload still lands at `dispatchToPipeline` with no schema check. A forged webhook that passes the HMAC but lacks `repository`, `pull_request`, or `sender` will silently reach the review runner and surface confusing failures downstream.
Suggested fix
const parsed = WebhookPayloadSchema.safeParse(payload);
if (!parsed.success) {
return new Response("invalid payload shape", { status: 400 });
}
await dispatchToPipeline({ installationId, payload: parsed.data });`readSessionCookie` awaited at the wrong layer
Switching to `await readSessionCookie(req)` was the right call, but the deeper fix is to drop the helper from the per-request path entirely — every webhook route imports `@/lib/auth` and now blocks on the session store even when there is no session to read. Hoist the cookie check or move the lookup behind the `X-GitHub-Event` branch so anonymous webhooks do not pay the cost.
Suggested fix
if (req.headers.get("x-github-event")) {
// webhook path — session lookup is unnecessary work here
return handleWebhook(req);
}
const user = await requireUser(req);`yaml` added without an explicit SafeLoader constraint
`yaml@^2.4.0` is fine in isolation but the review surface below imports `yaml.load` into a path that parses reviewer-supplied content. v2 deprecated unsafe load by default, but a transitive bump could regress. Pin the version to a known-safe range and document the loader choice in `src/lib/yaml.ts`.
Suggested fix
"yaml": "2.4.5"
// and at the call site: YAML.parse(str, { merge: false })Review summary
Request changes. Two critical findings (auth boundary leak, YAML merge-key gadget) block the merge until the author ships the suggested fix; the two advisory findings can land in a follow-up PR.
Estimated review time: ~45 seconds across the full diff. The second pass ran asynchronously and posted within 6 seconds of the webhook signature verifying.
More samples
From real public PRs
The walkthrough below is one of dozens — every linkable review was minted from a real public pull request. Same shape, same severity vocabulary, different code.
From the gallery
Reviewed against real PRs, not canned samples
The walkthrough above is one of dozens — every linkable review was minted from a real public pull request. Browse recent runs, filter by language or stack, and click through to the full review.
Recent gallery
Stack-filtered
Get on the early access list
Run the second-pass review on your own PRs
Two ways in. Drop your email and we’ll send the security-review rollout cadence. Or start free — run a review on a public repo and see the second pass in action.
Early access
Submissions land in the waitlist tagged source=‘demo’.
Self-serve
Already running reviews? Open the public gallery for live evidence.