Skip to content

docs(review-on-done): peer sender identity is a claim, not an authentication - #112

Merged
androidand merged 4 commits into
devfrom
review-identity-finding
Oct 3, 2026
Merged

androidand merged 4 commits into
devfrom
review-identity-finding

Conversation

@androidand

Copy link
Copy Markdown
Owner

What this is

A finding, not a fix. review-on-done Phase 1b needs the review record written with "the authenticated session identity of the REVIEWER". This documents what the codebase actually provides today, so the choice between topologies is made on evidence rather than on a chat log.

The finding

packages/opencode/src/peer/claude/codec.ts:95-102 documents the rule correctly: from/fromName are display only, and "provenance for authorization purposes is whatever authenticated socket connection this arrived on, never these fields."

lifecycle.ts:122-123 then passes the envelope field to resolveOpencodeSender. There is no third path. Both branches of route.ts:203-208 are functions of a string the sender chose: opencodeSenderOf strips a prefix and returns the remainder, and the UDS branch looks up a socket path the sender also supplied.

So a peer that writes from = uds:opencode-skein:<another session> is attributed to that session, and verifyLead grants the lead's scopes off that match. Written up as a failing test rather than a complaint: no such test exists today, which is why it went unnoticed.

Two corrections to my own first draft

Peer credentials are not available in this runtime. I cited the absence of SO_PEERCRED/getpeereid as a gap in this codebase. Probed against Bun 1.3.14 over a live unix socket, Socket exposes no getPeerName, getPeerCredentials or getPeerCert. An unforgeable connection identity is unreachable here, not merely unimplemented — so any proposal resting on peer credentials is not implementable as written.

The server-attributed recommendation is withdrawn. My original draft recommended in-server attribution per project-server D5. The user rejected the project server, so the premise is gone. Recorded as withdrawn rather than deleted so the reasoning is not quietly rewritten.

What survives

Callback confirmation: the receiver asks the claimed socket whether it sent a given msg_id, and the real session answers from its own in-process sent-log, which a forger does not have. Roughly one frame type plus a sent-log, no server. It also hardens verifyLead independently of review gating.

The residual is stated rather than implied: against a same-user shell-capable agent, nothing short of OS separation makes sender identity unforgeable. Callback confirmation bounds the claim; it does not make it absolute.

Also recorded

  • feat(policy): merge driver refuses an author-written verdict (review-on-done 1b.3) #111 is inert as merged — nothing passes authorSessionID yet, so the driver check enforces nothing. Plumbing, not a control in force.
  • 1b.2 is unsound alone — denying edit/write of the record is not a control while scoped unattended bash can write the same path. A fence that holds in tests but is bypassable in practice is the more expensive failure.

Verification

Docs only; no code change. Every line citation spot-checked against explicit paths after finding that two files named lead.ts exist under packages/. fork:verify 250/250 owned, 117/117 patched.

🤖 Generated with Claude Code

…ication

Phase 1b.1 needs "the authenticated session identity of the REVIEWER". Before that
can be built, the two candidate sources have to be compared, and the comparison is
not close.

codec.ts documents the correct rule: from/fromName are display only, and
"provenance for authorization purposes is whatever authenticated socket connection
this arrived on, never these fields". lifecycle.ts then passes the envelope field to
resolveOpencodeSender. There is no third path, and no connection identity to fall
back on: SO_PEERCRED and getpeereid appear zero times in packages/, verified with a
control pattern (peerToken, 22 hits) so the zero cannot be a silently failing search.

The sidecar peerToken does not close the gap. It is presented by any sender to
deliver to a session, so it authenticates the destination, not the sender.

So socket-resolved identity is a registry-resolved claim. A peer that writes
from = uds:opencode-skein:<another session> is attributed to that session, and
verifyLead then compares it against grant.lead.sessionID and grants the scopes. No
test covers that, which is why this is written as the failing case rather than as a
complaint.

Recommendation: server-attributed. Not because it is better in degree, but because
socket-resolved cannot close the hole it appears to close. The cost is that
authenticity stays open longer while the project server lands — an unenforceable
check being worse than a visibly missing one.

Also flags that 1b.2 is not sound alone: denying edit/write of the record is not a
control while scoped unattended bash can write the same path, and a fence that
holds in tests but is bypassable in practice is the more expensive failure.

No code change. This is the input to the decision, not the decision; publishing is
the user's.
…ding

route.ts:203-208 and lead.ts:195, verified against d7360d4. The first pass
cited lines I had read before #110 moved codec.ts, and a finding about citations
drifting should not carry its own.
codec.ts:95-102 and lead.ts:195-201, each verified by reading the line at both
ends of the range. An earlier check appeared to show lead.ts:195 was wrong; that
was the check resolving src/cli/cmd/lead.ts instead of src/peer/lead.ts.
The user rejected the project server, so the premise of my recommendation is gone.
Recording that rather than quietly rewriting it: the reasoning was sound against D5,
and leaving it standing would point the decision at a destination that does not
exist.

Two corrections that came out of consulting the peer session that owns lead
authority, after it re-checked the code path itself:

1. I cited the absence of SO_PEERCRED/getpeereid as a gap in this codebase. It is a
   platform constraint: probed on Bun 1.3.14 over a live unix socket, Socket exposes
   no getPeerName, getPeerCredentials or getPeerCert. An unforgeable connection
   identity is not reachable here at all, so any proposal resting on peer credentials
   is not implementable as written.

2. #111 is inert as merged. Nothing passes authorSessionID yet, so the driver check
   enforces nothing. Recorded as driver-side plumbing rather than a control in force.

What survives is the conclusion, not the route: callback confirmation, where the
receiver asks the claimed socket whether it sent a given msg_id and the real session
answers from its own sent-log. Roughly one frame type plus a sent-log, and it hardens
verifyLead as well. The residual belongs in the document rather than in a chat log:
against a same-user shell-capable agent, nothing short of OS separation makes sender
identity unforgeable. That is why the controls are bounded claims, and why 1b.1 should
have the record written by a tool from a session id the tool holds, never parsed from
message text.
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

Please edit this PR description to address the above within 2 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

@androidand
androidand merged commit 9165160 into dev Oct 3, 2026
3 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant