You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Compare core's own gate: ScopedStack.putAttachment() refuses anonymous requesters and requires a create grant on _attachment@1 before storing bytes (packages/core/src/stack.ts:993-1000 in haverstack/core). The server skips that layer entirely, so an entity with a token and zero grants — someone who cannot create a single record — can still:
write arbitrary bytes to the owner's disk, 50 MB per request, unbounded in total (the per-request maxAttachmentBytes cap is the only limit; nothing meters cumulative storage per entity);
do so invisibly: bytes with no metadata record don't appear in any query, and until core #64's GC exists they persist forever.
The grant check would bite at step 2 of the spec's two-step flow (creating the _attachment@1 record via POST /records) — but step 2 is optional, and the bytes land at step 1.
Why the fix isn't one line
The wire contract is two-step (POST /attachments stores bytes only, spec §Attachments), but core's scoped surface is one-step (putAttachment = bytes + metadata). There is no scoped bytes-only entry point — putAttachmentBytes exists only on the unscoped Stack. So the server is forced under the trust layer to implement the spec'd endpoint. Options:
Core exposes ScopedStack.putAttachmentBytes() running the same anonymous + create-grant checks (smallest change; keeps the wire contract; my lean). Needs a small core-side addition — flagging for the owner rather than filing cross-repo.
Respec upload as one-shot (bytes + metadata in one multipart request) — bigger wire change, probably not worth it given the SDK already automates the two steps.
Whichever lands, the upload path should also be what the sync work builds on: core #65's mimeType-conflict validation and core #64's grace-period orphan handling both assume upload is attributable to a requester.
Work items
Decide the mechanism with the owner (core ScopedStack.putAttachmentBytes vs local check)
Server design-review finding.
Problem
POST /attachmentsrequires only a valid token, then writes straight to the blob adapter (src/routes/attachments.ts:16-28):Compare core's own gate:
ScopedStack.putAttachment()refuses anonymous requesters and requires a create grant on_attachment@1before storing bytes (packages/core/src/stack.ts:993-1000in haverstack/core). The server skips that layer entirely, so an entity with a token and zero grants — someone who cannot create a single record — can still:maxAttachmentBytescap is the only limit; nothing meters cumulative storage per entity);The grant check would bite at step 2 of the spec's two-step flow (creating the
_attachment@1record viaPOST /records) — but step 2 is optional, and the bytes land at step 1.Why the fix isn't one line
The wire contract is two-step (
POST /attachmentsstores bytes only, spec §Attachments), but core's scoped surface is one-step (putAttachment= bytes + metadata). There is no scoped bytes-only entry point —putAttachmentBytesexists only on the unscopedStack. So the server is forced under the trust layer to implement the spec'd endpoint. Options:ScopedStack.putAttachmentBytes()running the same anonymous + create-grant checks (smallest change; keeps the wire contract; my lean). Needs a small core-side addition — flagging for the owner rather than filing cross-repo.Whichever lands, the upload path should also be what the sync work builds on: core #65's mimeType-conflict validation and core #64's grace-period orphan handling both assume upload is attributable to a requester.
Work items
ScopedStack.putAttachmentBytesvs local check)POST /attachmentson the_attachment@1create grant; denial → 403 per Core sync: wire error contract — typedcodevocabulary, structured bodies, 400/422 discipline (core #53) #33Refs haverstack/core#51 (reference-implies-access threat model), haverstack/core#64 (orphan cleanup is the backstop, not the fence), #41, #33.