Skip to content

GET /attachments/:fileId error mapping: authenticated denials return 401, and every non-permission error collapses to 404 #48

Description

@cuibonobo

Server design-review finding.

Problem

The download handler wraps the whole fetch in a two-way catch (src/routes/attachments.ts:37-42):

} catch (e) {
  if (e instanceof StackPermissionError) return c.json({ error: 'Unauthorized' }, 401);
  return c.json({ error: 'Attachment not found' }, 404);
}

Two defects:

  1. 401 for authenticated requesters. The spec's error table is explicit: 401 = missing/invalid token; 403 = StackPermissionError, requester lacks access. An authenticated entity denied by the access rule gets 401 "Unauthorized," telling well-behaved clients their token is bad — an adapter-api that reacts to 401 by re-authing or dropping its session will do exactly the wrong thing. Should be 403 (permission code per Core sync: wire error contract — typed code vocabulary, structured bodies, 400/422 discipline (core #53) #33) when a token was presented, 401 only when the requester is anonymous.
  2. The catch-all eats real failures. Any other error — disk I/O failure, a corrupted blob directory, a bug in the access predicate — is reported as "Attachment not found" with a 404. Genuine server faults become invisible: no 500, no log line (this route bypasses the global error middleware that would have logged it). Only translate the errors we mean (StackNotFoundError/missing-file → 404); let everything else propagate to errorMiddleware.

One deliberate behavior to preserve while fixing this: for non-owner requesters, core's ScopedStack.getAttachment throws StackPermissionError for missing and inaccessible files alike, so both currently map to the same response. That indistinguishability is load-bearing — fileIds are content hashes, so distinguishing "not found" from "forbidden" is a confirmation oracle for whether the stack contains specific bytes (the exact oracle haverstack/core#51 documents). The fix should keep missing ≡ forbidden for non-owners (one status for both — 403 or 404, decide with the owner and pin it) while still separating both from transport-auth failures and internal errors.

Work items

Refs #33, #37, haverstack/core#51 (anti-oracle rule), spec §Error responses.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions