Skip to main content
Storage • 2 mins read

Object Lock (WORM)

Object Lock (WORM)

Object Lock makes a version immutable until a date, or until you release a legal hold. Nobody can permanently delete it before then — not you, not an admin, not Hippius. Use it for backups, audit logs, and compliance (S3 WORM).

The API matches AWS. Enforcement is in the storage layer, so it holds against internal cleanup too.

Versioning is required

Object Lock protects versions, not keys. --object-lock-enabled-for-bucket turns versioning on for you.

Quick start

aws s3api create-bucket --bucket my-vault --object-lock-enabled-for-bucket \
--endpoint-url https://s3.hippius.com

aws s3api put-object --bucket my-vault --key report.pdf --body report.pdf \
--object-lock-mode GOVERNANCE \
--object-lock-retain-until-date "$(date -u -d '+30 days' +%Y-%m-%dT%H:%M:%SZ)" \
--endpoint-url https://s3.hippius.com

A versioned delete of that object returns AccessDenied until the date.

Retention modes

ModeWho can remove it earlyUse for
GOVERNANCEBucket owner, with --bypass-governance-retentionAccidents, with an escape hatch
COMPLIANCENobody, until it expiresAbsolute regulatory retention

You can always extend a retention. You cannot shorten a live one.

COMPLIANCE cannot be undone

A ten-year COMPLIANCE object occupies storage — and bills — for ten years. There is no support path to remove it early. Test with a short window first. Prefer GOVERNANCE unless a regulator requires otherwise.

Set retention

At upload, or later on a version:

aws s3api put-object --bucket my-vault --key q4.csv --body q4.csv \
--object-lock-mode COMPLIANCE \
--object-lock-retain-until-date 2027-01-01T00:00:00Z \
--endpoint-url https://s3.hippius.com

aws s3api put-object-retention --bucket my-vault --key q4.csv \
--retention '{"Mode":"COMPLIANCE","RetainUntilDate":"2027-01-01T00:00:00Z"}' \
--endpoint-url https://s3.hippius.com

aws s3api get-object-retention --bucket my-vault --key q4.csv \
--endpoint-url https://s3.hippius.com

Retain-until dates beyond 10 years (3650 days) are rejected.

An indefinite lock with no expiry. Independent of retention — either one blocks deletion.

aws s3api put-object-legal-hold --bucket my-vault --key evidence.zip \
--legal-hold Status=ON --endpoint-url https://s3.hippius.com

aws s3api put-object-legal-hold --bucket my-vault --key evidence.zip \
--legal-hold Status=OFF --endpoint-url https://s3.hippius.com

Bucket default

Every new version inherits a duration, resolved to a date at write time. Applies to PUT, multipart, and copy. Per-object headers override it. Years works too.

aws s3api put-object-lock-configuration --bucket my-vault \
--object-lock-configuration '{
"ObjectLockEnabled": "Enabled",
"Rule": {"DefaultRetention": {"Mode": "GOVERNANCE", "Days": 90}}
}' --endpoint-url https://s3.hippius.com

How deletes behave

RequestOn a locked version
DELETE key?versionId=…403 AccessDenied
DELETE key (no version)Succeeds — writes a delete marker; the locked version stays

Overwrite is allowed: it creates a new version. Bulk DeleteObjects is per-key — a locked key returns AccessDenied and the rest of the batch continues.

Bypass GOVERNANCE

Owner and --bypass-governance-retention. Either alone is not enough. No effect on COMPLIANCE or a legal hold.

aws s3api delete-object --bucket my-vault --key report.pdf --version-id "$VID" \
--bypass-governance-retention \
--endpoint-url https://s3.hippius.com

AWS gates this on IAM. Hippius has no IAM, so only the bucket owner can bypass.

Enable on an existing bucket

Versioning first, then Object Lock. No x-amz-bucket-object-lock-token required. Future versions only — existing objects are not locked retroactively.

aws s3api put-bucket-versioning --bucket existing-bucket \
--versioning-configuration Status=Enabled --endpoint-url https://s3.hippius.com

aws s3api put-object-lock-configuration --bucket existing-bucket \
--object-lock-configuration '{"ObjectLockEnabled":"Enabled"}' \
--endpoint-url https://s3.hippius.com

Without versioning this returns 409 InvalidBucketState.

Set lock headers on CreateMultipartUpload, not CompleteMultipartUpload. Read them back with HeadObject (GetObject does not return them). Sending lock headers to a bucket without Object Lock returns 400 InvalidRequest, not a silent drop.

Not supported

Not supportedWorkaround
Lock headers on CompleteMultipartUploadSet them on CreateMultipartUpload
Lock headers on GetObject responsesUse HeadObject
Suspending versioning on a lock-enabled bucketNot possible in AWS either
S3 Batch OperationsApply per object
Replicating lock stateSet the lock on the destination
POST ObjectPresigned PutObject

Further reading