Pairing: forward caller scopes during approval#55950
Conversation
Greptile SummaryThis PR forwards gateway caller scopes into Confidence Score: 4/5The fail-close logic is correct everywhere except the silent-pairing gate in message-handler.ts, where a forbidden result's truthiness lets a rejected device bypass the connection-close path. One P1 defect: in the silent pairing path the old truthiness guard on src/gateway/server/ws-connection/message-handler.ts — line 844 truthiness guard on
|
| Filename | Overview |
|---|---|
| src/gateway/server/ws-connection/message-handler.ts | Silent-pairing auto-approval updated to pass callerScopes and check approved?.status === "approved", but the downstream pairing-required gate at line 844 still uses a bare truthiness check on approved, which is now truthy for forbidden results too — allowing a scope-forbidden silent pairing to bypass rejection. |
| src/infra/device-pairing.ts | New fail-close logic for operator approvals when callerScopes are absent, overloads updated to reflect broader return type — logic looks correct. |
| extensions/device-pair/index.ts | Passes gatewayClientScopes to approveDevicePairing and handles the new forbidden status cleanly. |
| src/cli/devices-cli.ts | Local CLI fallback hardcodes operator.admin as caller scopes with good justification; forbidden path re-throws with the original catch-block error as cause. |
Comments Outside Diff (1)
-
src/gateway/server/ws-connection/message-handler.ts, line 844 (link)Forbidden result bypasses pairing-required gate
approvedis now typed asApproveDevicePairingResult, which means it can be{ status: "forbidden", missingScope: "..." }— a truthy non-null object. When a silent pairing attempt is rejected due to a scope mismatch, this truthy value makes the guard evaluate as!(true && true)→false, so the function skipssetHandshakeState("failed")and returnstrueinstead of rejecting the connection.In practice: a device whose silent pairing was forbidden (operator scope missing from
scopes) will pass the pairing check and continue to session establishment as if it were successfully paired.The condition should narrow to the
"approved"discriminant rather than relying on truthiness:Prompt To Fix With AI
This is a comment left during a code review. Path: src/gateway/server/ws-connection/message-handler.ts Line: 844 Comment: **Forbidden result bypasses pairing-required gate** `approved` is now typed as `ApproveDevicePairingResult`, which means it can be `{ status: "forbidden", missingScope: "..." }` — a truthy non-null object. When a silent pairing attempt is rejected due to a scope mismatch, this truthy value makes the guard evaluate as `!(true && true)` → `false`, so the function skips `setHandshakeState("failed")` and returns `true` instead of rejecting the connection. In practice: a device whose silent pairing was **forbidden** (operator scope missing from `scopes`) will pass the pairing check and continue to session establishment as if it were successfully paired. The condition should narrow to the `"approved"` discriminant rather than relying on truthiness: How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/gateway/server/ws-connection/message-handler.ts
Line: 844
Comment:
**Forbidden result bypasses pairing-required gate**
`approved` is now typed as `ApproveDevicePairingResult`, which means it can be `{ status: "forbidden", missingScope: "..." }` — a truthy non-null object. When a silent pairing attempt is rejected due to a scope mismatch, this truthy value makes the guard evaluate as `!(true && true)` → `false`, so the function skips `setHandshakeState("failed")` and returns `true` instead of rejecting the connection.
In practice: a device whose silent pairing was **forbidden** (operator scope missing from `scopes`) will pass the pairing check and continue to session establishment as if it were successfully paired.
The condition should narrow to the `"approved"` discriminant rather than relying on truthiness:
```suggestion
if (!(pairing.request.silent === true && (approved?.status === "approved" || resolvedByConcurrentApproval))) {
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "Pairing: require caller scopes on approv..." | Re-trigger Greptile
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bb39afc1af
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
Addressed the Greptile finding in commit 0ba8c456a4 by narrowing the silent-pairing gate to approved?.status === "approved" before allowing the handshake to proceed. Re-ran pnpm test -- src/gateway/server.silent-scope-upgrade-reconnect.poc.test.ts and pnpm check. |
0ba8c45 to
44feeae
Compare
* Pairing: require caller scopes on approvals * Gateway: reject forbidden silent pairing results
* Pairing: require caller scopes on approvals * Gateway: reject forbidden silent pairing results
* Pairing: require caller scopes on approvals * Gateway: reject forbidden silent pairing results
* Pairing: require caller scopes on approvals * Gateway: reject forbidden silent pairing results
* Pairing: require caller scopes on approvals * Gateway: reject forbidden silent pairing results
* Pairing: require caller scopes on approvals * Gateway: reject forbidden silent pairing results
* Pairing: require caller scopes on approvals * Gateway: reject forbidden silent pairing results
Summary
/pair approveChanges
approveDevicePairing()to reject operator approvals that do not carry caller scopesValidation
pnpm test -- src/infra/device-pairing.test.tspnpm test -- extensions/device-pair/index.test.tspnpm test -- src/cli/devices-cli.test.tspnpm test -- src/gateway/server.silent-scope-upgrade-reconnect.poc.test.tspnpm buildpnpm checkclaude -p "/review"; the command stopped immediately because the local Claude CLI requested interactive approval forgh pr list, so I covered the change with the repo verification gates above insteadNotes
src/gateway/server.sessions-send.test.tscurrently fails in this worktree on an unrelated harness path (gatewayCallDeps.loadConfig is not a function), so I did not use it as a gate for this change