Enforcing WORM Retention with S3 Object Lock Governance Mode: An Architecture Note
S3 Object Lock governance mode enforces WORM retention while letting a tightly scoped admin role bypass it when needed. Here's the minimal design, trust boundaries, checks, and when to switch to compliance mode.
29 Nov 2025, 14:24 UTC

Your compliance team needs records that cannot be altered or deleted for a fixed period, but operations occasionally needs to correct a bad upload or extend a hold. S3 Object Lock in governance mode gives you exactly that: write-once-read-many (WORM) enforcement for everyone, with a narrow, auditable escape hatch for a dedicated admin role. This note covers the smallest design that satisfies that requirement, where the trust boundaries sit, and what would force you to change it.
Requirements
The design assumes:
- A defined class of data (audit logs, financial records, signed artifacts) must be immutable for a known retention period, e.g., 365 days.
- A small number of privileged users may legitimately need to shorten retention or delete an object (bad ingest, legal directive), and those actions must be logged.
- Reads happen through normal application roles; nobody outside the admin role can touch retention settings.
If your regulation forbids anyone from bypassing retention — including the account root — governance mode is the wrong choice. That condition is covered below.
The smallest suitable design
One bucket, Object Lock enabled at creation, a default retention rule, and one tightly scoped admin role. Object Lock cannot be enabled on an existing bucket retroactively (except via support in some cases), so decide before you create it.
Create the bucket with Object Lock enabled (run from any machine with AWS CLI credentials that have s3:CreateBucket and s3:PutBucketObjectLockConfiguration):
aws s3api create-bucket --bucket acme-worm-records --region us-east-1 \\\\\\n --object-lock-enabled-for-bucket\\n\\naws s3api put-object-lock-configuration --bucket acme-worm-records \\\\\\n --object-lock-configuration 'ObjectLockEnabled=Enabled,Rule={DefaultRetention={Mode=GOVERNANCE,Days=365}}'With a default retention rule, every object written to the bucket automatically gets a retain-until date 365 days out — writers do not need to send Object Lock headers. Enabling Object Lock also turns on versioning permanently; each overwrite creates a new version rather than replacing data.
Then scope the bypass permission. Governance mode blocks deletes and retention changes unless the caller has s3:BypassGovernanceRetention and sends the x-amz-bypass-governance-retention:true header. Grant that permission only to a dedicated role, e.g. worm-retention-admin, that requires MFA and is assumed by named individuals, never by applications.
Trust and data boundaries
The bucket is the trust boundary. Within it, Object Lock is enforced by S3 itself — no IAM policy mistake on the writer side can make a retained object deletable, because the service rejects the request regardless of s3:DeleteObject grants. The only exceptions are the bypass permission above and legal holds, which is why those two capabilities define your real attack surface.
Complement Object Lock with the usual perimeter controls: Block Public Access on the bucket, a bucket policy denying non-TLS requests, and a VPC endpoint policy if writers live in a VPC. These control who can read and write; Object Lock controls whether anything can be un-written.
Operational checks
- CloudTrail: alert on
PutObjectRetention,PutObjectLegalHold, and any call carrying the bypass header. These should be rare and attributable to the admin role. - S3 Inventory: enable daily inventory with the Object Lock fields so you can reconcile every object's retain-until date against the expected value with a scheduled script.
- Config/audit: periodically run
aws s3api get-object-lock-configuration --bucket acme-worm-recordsand diff the output against the intended rule. - Negative test: as a non-admin role, attempt
aws s3api delete-object --bucket acme-worm-records --key test.txton a retained object. ExpectAccessDenied. If it succeeds, a bypass grant has leaked — treat that as an incident.
Failure modes and design-change conditions
Bypass permission sprawl. The most common real-world failure is s3:BypassGovernanceRetention ending up in a broad admin policy, silently converting WORM storage into ordinary storage. Audit IAM policies for this action specifically.
Cost growth. Retained objects bill at standard S3 rates for the full retention period, and versioning means every rewrite adds a version. Apply lifecycle rules to transition objects to cheaper classes — but only after retention expires, and remember expired versions still incur storage cost until deleted.
Change the design when:
- The regulation prohibits any bypass → switch to Compliance mode and remove all bypass grants. This is a bucket-level default change, not a toggle on existing objects.
- You need cross-region copies → enable replication to a second Object Lock-enabled bucket; retention settings replicate with the objects.
- Retention periods change frequently → prefer a shorter default plus legal holds (which have no expiry and can be toggled by holders of
s3:PutObjectLegalHold) rather than rewriting retention on millions of objects.
Limitations: Object Lock protects object content, not bucket configuration — nothing stops someone with sufficient account rights from deleting the bucket's empty versions or the bucket itself after retention expires. For stronger isolation, put the bucket in a dedicated account behind an AWS Organizations SCP. Behavior described here reflects long-standing S3 semantics; verify flag names against your CLI version before scripting.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.