Skip to content

sec(api): RI-exchange approve/reject distinguish a missing record (404) from an existing one (403 wrong token) for unauthenticated callers #601

Closed

Description

@cristim

Found while reconciling the RI-exchange approve spec (#600). Observed through HandleRequest with a pending record that has a hashed token: an unauthenticated caller sending a token gets 403 'invalid approval token' (approve) / 'invalid rejection token' (reject) for an existing record and 404 'exchange record not found' for a missing one. Without a token: approve answers 403 'CSRF validation failed' for both existing and missing ids (no oracle), reject answers 400 'rejection token is required' for both. The purchase routes closed the same class in #435/#584/#589/#594 by answering 401 before the lookup. UUIDs are hard to guess, so exposure is small, same class as #435 and #597.

Acceptance: decide whether RI-exchange approve/reject should return one status and body for 'missing' and 'wrong token' to unauthenticated callers (and whether a 401 before the lookup is the right shape here, since these token routes currently never answer 401), implement with a regression test through HandleRequest comparing missing vs existing for no-session callers with a wrong token, and update openapi.yaml. Related: #435, #597, #600.

Activity

  1. IAmJSD commented on Oct 20, 2026

    @IAmJSD
    Collaborator

    Claiming: working on this

  2. self-assigned this
    on Oct 20, 2026
  3. IAmJSD commented on Oct 26, 2026

    @IAmJSD
    Collaborator

    Decision (IAmJSD, acting owner, 2026-10-26): the emailed-token actions (approve, cancel, revoke, RI exchange approve/reject) all use one mapping:

    • 403 for a wrong or invalid token;
    • 410 for an expired link whose token matches;
    • 409 when the action loses a race (someone else already approved, rejected or cancelled it);
    • a missing RI exchange answers like a wrong token (403), not 404, 500 or 401. The emailed token alone authorizes these routes, so they cannot ask for a login first.

    This keeps the codes approve and cancel already return (the frontend already shows 410 as "link expired"). It is implemented by #1172 (revoke and RI exchange use 403/410/409) and #1175 (a missing exchange answers like a wrong token, for #601). Neither is merged yet; they go through the normal review and merge path. The remaining session-path leak cristim flagged on #1175 is tracked in #1428.

    Posted by IAmJSD's agent at IAmJSD's request (acting owner while the maintainers are away).

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions