Repository navigation
sec(api): RI exchange session approve still leaks missing (404) vs not-pending (409) to approve-own callers #1428
Description
Follow-up that cristim asked for in his review of #1175 (VERDICT: APPROVE 8999bc2, first nit): "Track both in one follow-up issue so the 404 half is not lost."
What happens
POST /api/ri-exchange/approve/{id} with a signed-in session that holds approve rights goes through fetchAndAuthorizeRIExchange (internal/api/handler_ri_exchange.go:2534-2565 on main at 6b586cc). That function decides two things before it checks whether the caller may approve this particular exchange:
- a missing id answers 404 "exchange record not found" (line 2554). On main the Postgres store currently returns a 500 for a missing id; fix(api): answer a missing RI exchange like a wrong token on approve and reject #1175 makes it a 404;
- an existing exchange that is not pending answers 409 with its current status in the message (line 2558).
Only after that does authorizeSessionApproveRIExchange (line 2561) refuse an approve-own caller who did not create the exchange. That refusal is a 403, which falls through to the email-token check when a token is present.
So a signed-in user who holds only approve-own on purchases can tell, for any exchange id, whether it exists and whether it is still pending, and read its status, even for exchanges they did not create. #1175 closes this oracle for callers using only the token (#601), but not for this session path.
The impact is low. The caller is authenticated and already holds approve rights, the ids are random UUIDs, and the same-account scope check (#93) runs first. It is the same leak #172 fixed for purchase executions.
Suggested fix
In fetchAndAuthorizeRIExchange, for a session without approve-any, give a missing record and another user's record the same answer as the ownership refusal, and check ownership before the status:
- fetch the record; on a missing record, return the same error as "not yours" (so it also falls through to the token, as another user's record does today);
- run
authorizeSessionApproveRIExchange; - only then answer 409 for a non-pending status.
Approve-any sessions and the admin key can keep the 404/409 distinction, since they may approve any exchange anyway.
Acceptance criteria
- For an approve-own session, a missing id, another user's pending exchange and another user's non-pending exchange produce the same status and body, with and without a token.
- The creator of the exchange still gets 409 for a non-pending exchange, and approve-any still gets 404/409.
- A table test through
HandleRequestpins this, in the style of fix(api): answer a missing RI exchange like a wrong token on approve and reject #1175's oracle test.
Refs #601, #1175, #1172, #172.
Filed by IAmJSD's agent at IAmJSD's request (acting owner while the maintainers are away).
Activity
IAmJSD commentedon Oct 26, 2026
Claiming: working on this