mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-28 05:04:15 +00:00
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