Files
at-container-registry/pkg/hold/pds
Evan JarrettandClaude Opus 5 96b48f4b3d hold: gate presigned PUT behind write access, and validate blob identifiers
com.atproto.sync.getBlob took its S3 operation straight from ?method= with no
allowlist and passed it to GetPresignedURL, whose switch mints a presigned PUT.
The ATProto sub-handler is public per the ATProto spec, and the OCI one gates
on ValidateBlobReadAccess, which passes anonymous callers on a public hold by
design. So an anonymous caller could obtain a fifteen-minute presigned PUT into
the hold's own bucket, over the avatar, the OG-card thumbnails, stored SBOMs
and vulnerability reports, or the OCI layer space. Holds sharing a bucket
widened it.

The defect is that a read check was gating a write. PUT now requires captain or
crew with blob:write via the existing ValidateBlobWriteAccess, rather than a
new parallel gate. Reads are untouched: ATProto GET and HEAD stay public,
OCI GET and HEAD keep the read check. An unknown method is a 400 rather than
falling through to a 500 from the presign switch.

The identifiers reached object storage keys unvalidated, which the tests showed
was not theoretical: cid=../../../../etc/passwd presigned a key containing that
path, and an OCI digest of sha256:aa/bb presigned across a shard boundary. A
digest is now sha256 plus exactly 64 lowercase hex, shaped after the scanner's
ParseDigest, and a CID must decode and round-trip to its canonical encoding.
Validation runs before the auth gate, so a malformed identifier is a 400 even
for an authorised caller.

Verified before changing anything that no legitimate caller reaches PUT
presigning. Pushes upload through the multipart endpoints, which live inside
requireBlobWriteAccess and use PresignUploadPart; every getBlob caller in the
appview, the scanner and the handlers sends GET, HEAD or nothing, and the
distribution route can only produce GET or HEAD.

One existing test asserted that an unauthenticated PUT returns a presigned URL.
It was pinning the vulnerability as intended behaviour and is replaced by
coverage of both the refused and the authorised cases.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U1Km3N3uUmeGaj7VbaM8PF
2026-09-05 16:50:01 -05:00
..
2025-10-16 13:08:19 -05:00
2026-02-11 20:44:07 -06:00
2026-04-29 10:37:36 -05:00
2025-12-20 10:44:26 -06:00