Skip to content

fix(api): answer 404 for a public key outside the caller's namespace - #6925

Merged
otavio merged 1 commit into
masterfrom
fix/public-key-cross-namespace-404
Aug 19, 2026
Merged

fix(api): answer 404 for a public key outside the caller's namespace#6925
otavio merged 1 commit into
masterfrom
fix/public-key-cross-namespace-404

Conversation

@otavio

@otavio otavio commented Aug 19, 2026

Copy link
Copy Markdown
Member

What

GET /api/sshkeys/public-keys/{fingerprint} answers 404 instead of 500 when the key belongs to
another namespace.

Why

GetPublicKey resolves the key bounded to the caller's namespace, so a foreign fingerprint
matches no row and the store returns ErrNoDocuments. The method returned that error untouched,
and server/api/pkg/echo/handlers/errors.go maps a bare store-layer error to 500 — the handler
documents that case as one the service must fix.

UpdatePublicKey and DeletePublicKey already wrapped the identical read correctly, so this was
an inconsistency inside one file rather than a design gap.

Found while auditing an enterprise report of the same defect class on the firewall rule endpoint
(shellhub-io/team#220, private).

Changes

  • sshkeys service: GetPublicKey maps ErrNoDocuments to ErrPublicKeyNotFound. 404 rather
    than 403 is deliberate: it does not disclose that the key exists in another namespace.
  • tests: a case in TestGetPublicKeys pins the mapping.

Audit scope

I read every namespace-scoped service path in server/api/services. This was the only other
client-reachable instance. Already correct: devices, sessions, tags, access policies, SSH
identities, namespaces, members, API keys — each resolves the row and wraps the error before it
mutates. The remaining raw store returns are read-backs after a successful write, where 500 is
the right answer.

Testing

Create a public key in namespace A, then GET /api/sshkeys/public-keys/{fingerprint} with
credentials for namespace B. Expect 404, not 500.

GetPublicKey resolves the key bounded to the caller's namespace, so a
fingerprint that belongs to another namespace resolves to no row and the
store returns ErrNoDocuments. The method handed that error back untouched,
and the route error handler maps a bare store-layer error to 500. So
GET /api/sshkeys/public-keys/{fingerprint} answered 500 where 404 belongs.

It now maps ErrNoDocuments to ErrPublicKeyNotFound, matching what
UpdatePublicKey and DeletePublicKey already do on the same read. 404 is
also the right answer for the cross-namespace case specifically: it does
not disclose that the key exists elsewhere.

Issue: shellhub-io/team#220
@otavio
otavio requested a review from a team as a code owner August 19, 2026 13:11
@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown

Claude finished @otavio's task in 49s —— View job


Code Review Complete

Reviewed 2 files across code quality, security, testing, language patterns, and architecture — no new issues found. The code looks good as-is.

A few notes worth recording (not blocking):

  • The mapping is correct: NewErrPublicKeyNotFound carries ErrCodeNotFound, so the handler answers 404, and choosing 404 over 403 (no cross-namespace disclosure) is the right call.
  • This implementation is actually more precise than the sibling reads it aligns with. UpdatePublicKey (sshkeys.go:199) and DeletePublicKey (sshkeys.go:254) map any PublicKeyResolve error to ErrPublicKeyNotFound, so a genuine store failure there would surface as 404. GetPublicKey now only maps store.ErrNoDocuments and lets real errors fall through to 500 — the correct distinction. Not something to change in this PR, but the two update/delete paths could later adopt the same errors.Is guard for consistency.
  • The added test correctly pins the mapping and matches the existing mock conventions in TestGetPublicKeys.

To request another review round, comment /review.

@otavio
otavio merged commit 5ffe518 into master Aug 19, 2026
40 checks passed
@otavio
otavio deleted the fix/public-key-cross-namespace-404 branch August 19, 2026 13:25
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.

1 participant