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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions