fix(server): answer the generic forbidden error with 403 - #7291
Conversation
ErrForbidden was declared with the not-found code, so every refusal using it answered 404, such as confirming a re-auth approval. Cloud callers of the error pick up the new status without a change. Fixes: #7277
Confirm now refuses a re-auth approval with 403. Reject shares the same decision step, which already returned 403 from its role check, undocumented.
|
Claude finished @geovannewashington's task in 5m 14s —— View job Code Review CompleteReviewed 4 files. 2 inline issues posted. Standards: 0 violations, 0 judgement calls. Spec: 0. Correctness: 2. StandardsNo findings. The one-line sentinel fix, the new row in the handler test table and the two OpenAPI edits all follow the repo rules, CorrectnessThe worst issue is at The second, lower-impact issue: SpecEvery requirement of #7277 is met:
I checked every other caller ( If you've addressed the feedback and want a new review, tag |
|
/review |
|
Claude finished @gustavosbarreto's task in 5m 30s —— View job Code Review CompleteReviewed 7 files. 1 inline issue posted. Standards: 0 violations, 1 judgement call. Spec: 0. Correctness: 0. StandardsJudgement call at SpecThe PR does everything #7277 asks for:
The issue also wanted the Additional notes
If you've addressed the feedback and want a new review, tag |
The console reads a 403 from this route as a wrong password or code. With ErrForbidden now answering 403, a correct password on a teammate's key or a mismatched approval showed "Incorrect password.", so those refusals return not-found errors. A live code from another namespace now also answers 404, like an unknown one, instead of showing that it exists. A session with no tenant failed scope.NewBounded with ErrForbidden and hit the same wrong message. It has no identity for the key, so it answers the same not-found as any key that is not the caller's.
It already answered 404 for an unknown or expired approval code. The description now says a 403 means a wrong password or code, and which key and approval refusals answer 404.
c18bc95 to
784a8b7
Compare
|
/review |
|
Claude finished @otavio's task in 3m 17s —— View job Code Review CompleteReviewed 7 files. 1 inline issue posted. Standards: 0 violations, 1 judgement call. Spec: 0. Correctness: 0. StandardsJudgement call at SpecThe PR does everything #7277 asks for:
The Additional notes
If you've addressed the feedback and want a new review, tag |
The tables said whether a call fails twice, as expectedErr and wantErr, and the two could disagree: "fails when the user does not exist" set only the bool, so it passed on any error. wantErr now decides it alone, and that case pins ErrUserNotFound.
|
/review |
|
Claude finished @otavio's task in 4m 38s —— View job Code Review CompleteReviewed 7 files. 1 inline issue posted. Standards: 0 violations, 0 judgement calls. Spec: 0. Correctness: 1. StandardsNo findings. The new CorrectnessThe new description in SpecThe PR does everything #7277 asks for:
The Additional notes
If you've addressed the feedback and want a new review, tag |
decideSSHApproval refuses a reauth confirm and a role that cannot approve before SSHApprovalDecide, the only step that answers 404 for an already-decided code, and SSHApprovalGet does not filter on state. With ErrForbidden now answering 403, a used code within its TTL gets 403 on those paths, so confirm and reject now state the precedence.
|
/review |
|
Claude finished @otavio's task in 2m 30s —— View job Code Review CompleteReviewed 7 files on all three axes: Standards (repo rules, language conventions, tests, code smells), Spec (does it implement the issue?) and Correctness (security and runtime defects). No new issues found. The code looks good as-is. If you push additional changes and want a new review, tag |
Summary
services.ErrForbiddenwas declared with the not-found code, so every refusal that used it answered 404 with{"message":"forbidden"}. It now uses the forbidden code, like every other forbidden error in the file.The refusals that move from 404 to 403:
POST /api/ssh-approvals/:code/confirmon areauthapproval, the issue's exampleBoundTowith no tenant, a backstop behind the tenant guardThe web terminal re-auth step-up is the exception. The console reads a 403 from
POST /api/web-terminal/reauthas a wrong password or code, so its other refusals now use not-found errors and keep answering 404:This goes against the spec's line that the step-up's ownership checks answer 403. Keeping 403 there would show "Incorrect password." to a user who typed the correct one. It also applies to cloud's TOTP and SAML step-ups, which call
StampWebReauth.OpenAPI: confirm and reject now list 403, and web terminal re-auth now lists 404. Each description says which refusals answer which status. Reject already returned 403 from its role check, and re-auth already returned 404 for an unknown code, without documenting either.
Evidence
Before: the new error handler case fails.
The re-auth service cases for another member's key, an unknown key, and an approval for another key, of another kind, or from another namespace fail with
ErrForbidden.After: the handler answers 403 with
{"message":"forbidden"}, and those re-auth cases returnErrSSHIdentityNotFoundorErrSSHApprovalCodeNotFound. The full server suite passes.redocly lintreports the community, cloud and enterprise specs as valid.Merge Danger
Door: two-way
It changes the status code of existing errors, with no migration, no wire format and no agent contract. Reverting restores the previous statuses.
Blast Radius: API clients
A client that treated these 404s as "not found" now gets 403. On the re-auth step-up, the console keeps "Incorrect password." and "Invalid code" for a failed factor only, and shows "Re-authentication failed." for the other refusals, as before. Cloud's OpenAPI entries for the SAML flows are a follow-up.
Closes #7277