Skip to content

Conversation

@jamiepine
Copy link
Member

This PR introduces a mandatory user confirmation step on the initiator device during the pairing process. Previously, an initiator would automatically accept any incoming pairing request without user intervention, posing a significant security risk where an attacker could gain unauthorized access by simply scanning a QR code.

With this change, when a pairing request is received:

  1. The initiator device enters an AwaitingUserConfirmation state.
  2. A unique 2-digit confirmation code is generated.
  3. The user is prompted with a dialog (on Desktop/Mobile) or terminal prompt (on CLI) displaying the requesting device's name and the confirmation code.
  4. The user must manually enter the displayed code to accept the pairing.
  5. The request automatically times out after 60 seconds if no action is taken, or the user can explicitly reject it.

This enhancement ensures that pairing only proceeds with explicit user consent, significantly improving the security and trustworthiness of the Spacedrive network.

Closes #(issue)


Open in Cursor Open in Web

This commit introduces the pairing confirmation flow, allowing users to accept or reject pairing requests. It includes UI updates, event handling, and core protocol logic for handling confirmation requests, timeouts, and rejections.

Co-authored-by: ijamespine <ijamespine@me.com>
@cursor
Copy link

cursor bot commented Dec 23, 2025

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@jamiepine
Copy link
Member Author

@cursor, fix conflict with main, merge in

@cursor
Copy link

cursor bot commented Dec 23, 2025

Done! I've successfully merged main into the PR branch and resolved the conflict:

What was done:

  1. Fetched latest main branch
  2. Merged main into cursor/pairing-user-confirmation-flow-2b8d
  3. Resolved conflict in packages/ts-client/src/generated/types.ts by regenerating the TypeScript types (this is an auto-generated file, so regeneration ensures both main's changes and our new pairing confirmation types are included)
  4. Verified the code compiles successfully
  5. Committed and pushed the merge

Conflict resolved:

  • packages/ts-client/src/generated/types.ts - Regenerated to include all types from main plus the new pairing types (PairConfirmInput, PairConfirmOutput, AwaitingUserConfirmation state)

The PR is now up-to-date with main and includes all the latest changes from both branches.


Open in Cursor Open in Web

Learn more about Cursor Agents

Resolved conflict in packages/ts-client/src/generated/types.ts by
regenerating the TypeScript types to include both main's changes
and the new pairing confirmation types.
@jamiepine jamiepine marked this pull request as ready for review December 23, 2025 16:43
@cursor
Copy link

cursor bot commented Dec 23, 2025

PR Summary

Implements an explicit user-approval step for device pairing across core, protocol, CLI, and UIs.

  • Core/Protocol: Adds AwaitingUserConfirmation state with 60s timeout, 2‑digit code generation, and Rejected path; initiator defers challenge until confirmation. Introduces PairingConfirmationRequired and PairingRejected network/core events and bridges them. Updates pairing messages with Reject, joiner rejection handling, state machine timeout handling, and session persistence fields.
  • API/Types: New network.pair.confirm action (PairConfirmInput/Output), extended pair.status to expose confirmation code/expiry and remote device info; TS client types updated accordingly.
  • CLI: Adds pair confirm --accept/--reject and an interactive confirmation loop on generate; enhanced event summaries for pairing events.
  • UI (Mobile/Web): New confirmation dialogs showing device info, code entry, and countdown; wires to network.pair.confirm; hides normal pairing views while awaiting confirmation.
  • Wiring: Passes event sender into pairing handler; updates event bridge and status/query paths.

Written by Cursor Bugbot for commit 22beae7. Configure here.

reject,
} => {
// Default to accept if neither flag specified
let accepted = if *reject { false } else { *accept || true };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This always evaluates to true when not rejecting, making the --accept flag useless.

Suggested change
let accepted = if *reject { false } else { *accept || true };
let accepted = if *reject { false } else { *accept };

// Note: The actual Challenge will be sent when user confirms via confirm_pairing_request
let ack_response = PairingMessage::Complete {
session_id,
success: false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack_response is constructed but never used - can be removed.

reject,
} => {
// Default to accept if neither flag specified
let accepted = if *reject { false } else { *accept || true };
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accept flag is ignored due to tautology in boolean expression

The expression *accept || true always evaluates to true regardless of the value of *accept, making the --accept flag completely useless. When --reject is not specified, the pairing is always accepted whether or not the user explicitly passed --accept. This could lead to users unintentionally accepting pairing requests when they simply run the confirm command without any flags, thinking they need to explicitly specify --accept to proceed. The expression likely intended to be *accept alone, with a separate check for when neither flag is specified.

Fix in Cursor Fix in Web

Ok(PairConfirmOutput {
success: true,
error: None,
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pairing confirmation reports success when response message fails to send

When handle_user_confirmation returns response data (Challenge or Reject message), the code attempts to send it to the joiner through nested if let conditions. If any condition fails—session not found, remote_device_info is None, node ID parse fails, or endpoint unavailable—the code silently falls through to return success: true without sending the message. The joiner device remains waiting indefinitely for a response that never arrives, while the initiator's user believes the confirmation succeeded. This undermines the security feature since the confirmation flow appears complete but the other device is never notified.

Fix in Cursor Fix in Web

Co-authored-by: ijamespine <ijamespine@me.com>
@cursor
Copy link

cursor bot commented Dec 25, 2025

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants