Skip to content

feat(server): add guarded attachment download - #11

Merged
plusky merged 1 commit into
mainfrom
feat/attachment-download
Jul 27, 2026
Merged

feat(server): add guarded attachment download#11
plusky merged 1 commit into
mainfrom
feat/attachment-download

Conversation

@plusky

@plusky plusky commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Adds download_attachment, the last non-packaging roadmap item. Metadata is fetched first — never the blob — so guard assessment on the owning bug (I8) and a new pre-download gate both run before any content is pulled.

Policy: new global.max_attachment_bytes (default 2 MiB, 0 removes the cap) bounds what may be embedded in a tool result, enforced on the reported size and re-checked against the actual base64 payload. Private attachments need the same double opt-in as private comments (I5), and on download a missing privacy flag counts as private (I4).

Hardened per pre-PR adversarial review (findings against DESIGN.md invariants, all addressed in this branch):

Finding Fix
Existence oracle via upstream request count/latency (1 request for a missing id vs 2 for a real one) constant request count on every path — a metadata miss still runs one classify call
Blob-fetch failure returned the raw upstream status/message, distinguishable from an unknown id uniform attachment denial (I2) on that path too
TOCTOU: gate ran on the metadata response, bytes came from a second request gate and owning-bug check re-run on the blob response
Size refusal disclosed both the attachment size and the configured cap refusal names neither (I1 limits what policy a client may learn)
Uploader-chosen file_name interpolated into the resource uri (../ traversal for clients persisting by uri) uri carries only the attachment id; the name stays in the inert JSON summary
Any image/* went to the model's image channel, incl. script-bearing image/svg+xml strict raster allowlist; everything else travels as a blob resource

Verified: cargo fmt --check, clippy --workspace --all-targets -D warnings, cargo test --workspace (105 tests, incl. new gate and media-type cases), typos.

@plusky
plusky force-pushed the feat/attachment-download branch from 094ef91 to 9d40009 Compare July 27, 2026 14:41
Expose download_attachment: the attachment metadata is fetched first,
never the blob, so guard assessment on the owning bug (I8) and the new
pre-download gate both run before any content is pulled. Private
attachments need the same double opt-in as private comments, and on
download a missing privacy flag counts as private (I4) — the blob is
the payload the guard protects. A new global.max_attachment_bytes
policy key (default 2 MiB, 0 removes it) bounds what may be embedded in
a tool result; it is enforced on the reported size and re-checked
against the decoded length of the payload that actually arrived.

Hardened per pre-PR adversarial review: every refusal is the uniform
attachment denial (I2) including blob-fetch failures, the upstream
request count is constant so latency cannot reveal which attachment
ids exist, the gate and owning-bug check re-run on the blob response to
close the TOCTOU between the two fetches, size refusals disclose
neither the size nor the configured cap (I1), the resource uri carries
only the attachment id instead of the uploader-chosen file name, and
only allowlisted raster types reach the model's image channel so
image/svg+xml cannot smuggle script-bearing markup into client
renderers.

Also rework examples/policy.toml to the documented scenario — embargoed
bugs invisible at any age, security-labelled bugs invisible only while
fresher than five days, everything else fully visible — and align the
capability and private-content documentation with the widened
attachments capability.
@plusky
plusky force-pushed the feat/attachment-download branch from 9d40009 to eb9f82c Compare July 27, 2026 14:49
@plusky
plusky merged commit d6ec8b7 into main Jul 27, 2026
10 checks passed
@plusky
plusky deleted the feat/attachment-download branch July 27, 2026 14:51
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