fix: fail closed when a status comment author cannot be read - #1073
fix: fail closed when a status comment author cannot be read#1073dwin-gharibi wants to merge 12 commits into
Conversation
…mment author cannot be read
…ug fix on status comment author
…f for openclaw clawsweeper on bug fix
There was a problem hiding this comment.
Pull request overview
This PR fixes a fail-open condition in the repair lane’s “trusted status comment” guard by ensuring that an unreadable/absent comment author is not treated as trusted. It centralizes the trust check into a single exported helper in comment-router-core.ts, updates both prior call sites to use it, and adds focused regression coverage plus a proof contract.
Changes:
- Introduce
isTrustedStatusCommentAuthor(...)that trims/normalizes the login and fails closed when the author cannot be read. - Replace the two prior private predicate copies in
comment-router.tsandexecute-fix-artifact.tswith the shared helper (preserving the hardcoded repair trusted authors set). - Add unit tests and a reproducible proof package under
docs/proof/trusted-status-comment-fail-open/.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/repair/comment-router-core.ts | Adds the shared exported trust comparator that fails closed on missing authors. |
| src/repair/comment-router.ts | Switches status-comment trust to the shared comparator using trustedBots. |
| src/repair/execute-fix-artifact.ts | Switches status-comment trust to the shared comparator with a fixed trusted-author set. |
| test/repair/comment-router-core.test.ts | Adds behavioral coverage for unreadable authors and trusted logins. |
| docs/proof/trusted-status-comment-fail-open/run-proof.sh | Adds container proof harness to build, extract pre-fix source, and run proof + focused tests. |
| docs/proof/trusted-status-comment-fail-open/run-proof.mjs | Adds proof script validating fail-closed behavior, no-loss, and deduplication in dist/. |
| docs/proof/trusted-status-comment-fail-open/README.md | Documents the proof contract, claims, commands, and limits. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| const distCore = path.join(repoRoot, "dist", "repair", "comment-router-core.js"); | ||
|
|
||
| if (!fs.existsSync(distCore)) { | ||
| console.error(`missing build artifact: ${distCore}\nrun: pnpm run build:repair`); |
|
Codex review: needs real behavior proof before merge. Reviewed August 19, 2026, 2:18 AM ET / 06:18 UTC. ClawSweeper reviewWhat this changesThis PR centralizes status-comment author validation so missing or malformed GitHub comment authors are rejected before repair automation adopts or edits a status comment. Merge readiness⛔ Blocked until stronger real behavior proof is added - 5 items remain Keep open: the fail-closed guard is directionally correct, but the new Docker reproduction path cannot supply the required pre-fix fixture and the PR body records 25 checks while the current proof contract requires 28. Fresh current-head proof is needed after correcting that path. Priority: P3 Review scores
Verification
How this fits togetherThe repair lane reads GitHub issue comments to locate its own status records before updating them during review, repair, and automerge workflows. The changed guard decides whether a comment may cross that trust boundary; rejected comments are ignored. flowchart LR
A[GitHub issue comments] --> B[Status-comment author guard]
B --> C{Known automation author?}
C -->|Yes| D[Read status marker]
D --> E[Update status comment]
C -->|No or missing| F[Ignore comment]
Before merge
Findings
Agent review detailsSecurityNone. Review metrics
Root-cause clusterRelationship: Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge-risk optionsMaintainer options:
Copy recommended automerge instructionTechnical reviewBest possible solution: Keep the shared fail-closed comparator, amend the Docker recipe to stage the pre-fix fixture before copying the checkout, and publish a fresh current-head Node 24 proof showing all 28 checks plus the focused repair suites. Do we have a high-confidence way to reproduce the issue? Yes, from source: a null or missing login reaches the old empty-author default, while the new comparator returns false. A live GitHub response with that shape remains unproven. Is this the best way to solve the issue? Yes for the product code: a shared fail-closed comparator is the narrowest solution and preserves configured trusted authors. The proof packaging is not yet the best path because its Docker recipe cannot run as documented. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 02c930c53d1c. LabelsLabel justifications:
EvidenceWhat I checked:
Likely related people:
Rank-up movesOptional improvements that raise the rating; they are not merge blockers.
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (4 earlier review cycles)
|
…claw proofs with latest changes and reruning
9efcaa5 to
18d2a59
Compare
Closes #1072.
What Problem This Solves
Fixes an issue where a comment whose author could not be read was treated as
trusted.
isTrustedStatusCommentis the guard that decides whether ClawSweepermay adopt a comment as its own status comment — read durable state from it, and
PATCH its body — so an unknown author must never satisfy it.
Note the internal inconsistency: the author read is defensive (
?.and?? ""),so the code anticipates an absent author — and then trusts it. If an absent author
is impossible the defensive read is dead code; if it is possible this is a
fail-open. Either way the clause is wrong. Every other identity check in the repair
lane fails closed (
comment-router-core.ts:1990,:2053,:2106).Honest severity — please read before rating
I could not demonstrate a path where
user.loginis absent, and I looked hard:gh api /repos/{repo}/issues/{n}/commentsor
/issues/comments/{id}— full REST objects, no--jqprojection droppinguser;gh pr view --json comments, which would supplyauthorinstead;ghost, a real login the guard correctlyrejects on its own merits;
user.The one remaining vector is GitHub's own contract: its OpenAPI schema types
issue-comment.useras nullable — which is exactly what the defensive read waswritten for.
So this is a latent fail-open default, not a demonstrated exploit. I would rate it
P3. There is also defence in depth after the guard: an adopted comment must match
the issue number and already carry ClawSweeper's exact ack marker. The guard is the
first check, not the only one.
I am saying this plainly because a reviewer reading only "trust predicate fails
open" would over-rate it, and because the honest case for merging is "the cost is
one clause and the current behaviour is indefensible on its face", not "there is a
live incident".
Why This Change Was Made
Fail closed on an absent author, and collapse the two private copies into one
shared, exported, unit-testable comparator in
comment-router-core.ts— a moduleboth consumers already import.
The two copies had already drifted:
comment-router.tsconsulted theconfigurable
trustedBotsset whileexecute-fix-artifact.tshardcoded two botlogins. Deduplicating removes that drift risk; the hardcoded set is preserved
explicitly as
REPAIR_TRUSTED_STATUS_AUTHORSso this PR changes only theabsent-author case, not which bots each consumer trusts.
Non-goals: no change to the marker, issue-number, or ack checks that run after the
guard, nor to the configurable
trustedBotsset.Regression direction to weigh: if an absent author is reachable for a genuine
ClawSweeper comment, failing closed means ClawSweeper stops recognising its own
status comment and posts a duplicate instead of editing. Visible but harmless, and
it needs the same unproven precondition as the defect.
User Impact
No observable change in normal operation — by design, since no reachable path
produces an unreadable author today. A trust guard that could answer "yes" to an
unanswerable question no longer can.
No configuration change, no migration, no data-contract change.
OpenClaw Bay: not affected. No status, lifecycle, or telemetry shape Bay renders
changes. No Bay update or Bay proof is needed.
Release note:
fix: fail closed when a status comment author cannot be read.No
CHANGELOG.mdedit is included — happy to add one if this repo's release policywants it.
Evidence
Diff
Plus a new proof package under
docs/proof/trusted-status-comment-fail-open/.Focused tests
node --test test/repair/comment-router-core.test.ts→ 129/129 pass(127 pre-existing + 2 new). In-container across three repair suites: 201/201.
Neither private copy had a behavioural test before this. The existing suite only
used
"function isTrustedStatusComment"as a source-slice boundary marker, sonothing asserted the predicate's behaviour at all.
New cases:
user: null, nouserkey,userwithoutlogin, empty login, whitespace login, null comment, plusghost;whitespace, while untrusted humans and an empty trusted set stay rejected.
Red/green
Reverted only
src/repair/, rebuilt, re-ran: 2 fail, 127 pass. Restored:129 pass.
Pre-fix the failure is "the shared export does not exist", which is a weak signal on
its own — so the proof below measures the behavioural contrast directly.
Real Behavior Proof
Claim. An unreadable author is not trusted; genuine authors are unaffected; and
no private copy of the predicate remains.
Exercised surface.
isTrustedStatusCommentAuthorindist/repair/comment-router-core.js, plus a scan of both consumers' built output.Scenario / fixture. Three claims: fails-closed over seven unreadable shapes;
no-loss over six genuine spellings and three rejections; and a built-output scan
requiring both consumers to reference the shared comparator and to contain no
return !author ||shape.How the contrast is measured — and a security note. The pre-fix predicate was
module-private, so it cannot be imported. My first draft
eval'd the extractedsource via
new Function; a security hook flagged that as a code-injection pattern,and it was right — that does not belong in a committed script. The proof now:
return !author ||clause and thesame author read;
Step 2 is what makes step 3 trustworthy — the reimplementation is proven faithful
to the shipped code rather than assumed. This is both safer and stronger evidence
than the eval it replaced.
Command and environment. Node 24 in a Docker-backed Crabbox
local-containerlease:
crabbox run \ --provider local-container \ --local-container-image node:24 \ --no-hydrate \ --timing-json \ --artifact-glob '.artifacts/trusted-status-comment-proof/**' \ --script docs/proof/trusted-status-comment-fail-open/run-proof.shlocal-container(Docker/OrbStack)0.15.0node:24@sha256:934240a162082fd8b8a2f90cd5114446443f1eba1c5378f6687167ca405e6584v24.19.0(satisfiesengines.node >= 24)cbx_7a849d5a91ea(tidal-shrimp)run_691f050226af.crabbox/runs/run_691f050226af/run_691f050226af-artifacts.tgz0Observed result. In-container: 25/25 proof checks PASS, focused suites
201/201, exit 0. The lease verified both sides first
(
post-fix: 1 shared comparator reference,pre-fix fail-open clause present: 1).Artifact / trace.
docs/proof/trusted-status-comment-fail-open/holdsrun-proof.sh,run-proof.mjs, and the contract README. The lease artifact tarballholds
proof-output.txt,focused-tests.txt,install.log,build.log, andprefix-extract.log.Limits. Covers the trust predicate only. Claim 3 scans for the
return !author ||shape specifically; a differently-spelled fail-open would not be caught. No live
GitHub comment is read, adopted, or edited.
Repository test suite status
The proof and focused suites run on Node 24 inside the Crabbox lease recorded above
(
201/201in-container). The note below concerns only the full suite on the macOS host.Host full suite on this branch: 24 distinct failures, five not in the clean-tree
baseline. I verified every one rather than assuming:
comment webhook settles duplicate fast ack comments after dispatchandwebhook signature verification uses sha256 body hmac— known flakes; the filepasses 25/25 in isolation, and a module-graph walk shows nothing this PR
touches is reachable from
comment-webhook.pinned-base reproduction …cases intarget-validation.test.ts— thesepass in isolation and take 24–28 seconds each (heavy git operations), so
they lose under parallel full-suite load. A module-graph walk from
target-validation.jsconfirmscomment-router-core,comment-router, andexecute-fix-artifactare all unreachable from it.Green on this branch:
local-containerproofpnpm run build:allpnpm run format:checkpnpm run lint