Skip to content

Make .quilt/packages/ manifest writes create-only (If-None-Match) so content-addressed keys are immutable at the storage layer #5182

Description

@drernie

Summary

Objects under a package registry's .quilt/packages/ namespace are content-addressed: the key is the hash of the manifest, so a given key should only ever be written once and never change. Today nothing at the storage layer enforces that — a PutObject to an existing .quilt/packages/<hash> key overwrites it. Making manifest writes create-only (via S3 conditional writes / If-None-Match: *) would turn the immutability that content-addressing assumes into something the storage layer guarantees.

Motivation

Any workflow that grants PutObject on .quilt/packages/* — required today for a principal to push a package to a bucket — also grants the ability to replace other revisions' manifests, because IAM can't distinguish create from overwrite on a prefix. Combined with the client not verifying browsed manifest bytes against the requested hash (filed separately), this is a manifest-poisoning path. Create-only writes close it at the source: an existing content-addressed key becomes physically unwritable.

Proposal

When writing a manifest object to .quilt/packages/<hash>, send If-None-Match: * (S3 has supported conditional writes for new-object creation since 2024). Behavior:

  • New hash → key absent → write succeeds.
  • Existing hash → precondition fails (412) → treat as success/no-op, since by content-addressing the bytes are already identical (a colliding hash with different bytes is the thing we want to reject).

This is purely additive: re-pushing an identical revision is idempotent, and a genuine new revision has a new hash and thus a new key. Only in-place manifest replacement — which should never be legitimate — is blocked.

Optional companion: document a bucket-policy stanza denying PutObject on .quilt/packages/* without s3:if-none-match, so administrators can enforce create-only for writers whose client predates the change. That policy is only safe once the client reliably sends the header, so the client change should land first.

Scope / open questions

  • Confirm all manifest-write paths (push, build+push, the CLI, any lambda-side registry writers) route through one place where the header can be added.
  • Named-package pointers (.quilt/named_packages/<name>/<timestamp>, latest) are not content-addressed and must stay mutable — this applies only to .quilt/packages/.
  • Non-S3 / local registries: no-op or best-effort.

Related

Defense-in-depth companion to the browse-time manifest verification issue; and context in quiltdata/pearls, whose Scene-isolation model would prefer to grant workers a .quilt/packages/* write that is create-only by construction rather than rely on every writer being trusted not to overwrite.

🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions