Claim Check
IBM Bob 2.0 Hackathon · built inside Bob IDE

Four rounds of review read this sentence. It was false.

KeeperHub PR #2476 — the pull request description

“…fetched through safeFetch behind an always-on assertUrlIsPublic SSRF check…”

Claim Check splits a pull request’s description and review thread into every claim a reader could falsify, then gives each claim to its own IBM Bob subagent, all running in parallel. Each one comes back with a verdict and the line of code that settles it. On this PR, one of 29 came back false — and it was the one the reviewers had signed off on.

29claims extracted
28hold, with file:line
1does not hold
0left unchecked
corpus: 10 files · +1,752 lines · 7 reviews · 44,718 characters of review prose · frozen at the commit that was reviewed
The finding

The connection test skipped the guard the description promised

The signal path does call assertUrlIsPublic. The Test button in the credentials dialog did not: it called the raw fetch on a URL the operator types, and reported the HTTP status back — a probe of any host the operator can name, including private ranges.

✕ does not hold

Every outbound request passes through assertUrlIsPublic as an always-on SSRF check.

claim
c02 · universal (“every”)
source
PR description, verbatim
evidence
pr-2476.diff:1031 → plugins/predge/test.ts:31
reviewers
Four rounds. The final pass says “every behavioural claim in the body holds”.
1025    const rawUrl =
1026      credentials.PREDGE_SIGNAL_URL?.trim() || DEFAULT_PREDGE_SIGNAL_URL;
1027    const baseUrl = stripTrailingSlashes(rawUrl);
1028
1029    // Read-only: the published keyset confirms the signal service is reachable
1030    // and is serving Predge signing keys.
1031    const response = await fetch(`${baseUrl}/.well-known/predge-keys.json`, {1032      method: "GET",
1033      headers: { Accept: "application/json" },
1034    });

No assertUrlIsPublic, no safeFetch. The URL comes straight from operator credentials. We wrote this code, fixed it the same night with six tests, and disclosed it in the PR thread ourselves.

The ledger

All 29 claims, and the code that settles each

Open any row for the author’s original words, the subagent’s reasoning, and — where we re-ran it live through Bob Shell — the live result. Evidence links open the exact line of the captured diff.

Against the reviewer

The human pass was thorough, and partial

The maintainer’s final review names four conclusions under “Does it match the description”. Those four cover 14 of the 29 claims, and the tool agrees with him on all 14. He did not address the other 15 one by one — and the failing claim is among them.

covered by a reviewer conclusion · tool agrees (14) not addressed individually (15) the claim that does not hold
Where it breaks

Two limits we found by trying to break our own tool

false holds

A cheaper verifier missed the same bug

To save budget, one build of the live executor ran each subagent on the diff alone. It returned holds for c02: it found the guarded path, and stopped.

“Every request is guarded” is a universal claim. One example cannot confirm it; only a counterexample can refute it. Universal claims now get a different instruction: search every call site, list what was checked, and answer cannot tell if coverage isn’t proven. With that rule the live run finds the bug again.

live c02does not hold · test.ts:31checked4 locationstime15.1 s
out of reach

It only checks what someone claimed

The worst defect we know of in this codebase is a mismatch between two canonicalisation functions: the signer drops undefined keys, the verifier does not. Identical bytes today; incompatible the day a field becomes optional.

No sentence in the PR says anything about it, so no claim was extracted and no subagent looked. That is a boundary of the method, not a bug in Bob — and it is the same reason four rounds of review walked past it.