Skip to content

docs(cloud-setup): split §4.4 bucket policy into ListBucket + GetObject statements#67

Open
hanwencheng wants to merge 1 commit intomainfrom
docs/fix-cloud-setup-s4.4-policy-split
Open

docs(cloud-setup): split §4.4 bucket policy into ListBucket + GetObject statements#67
hanwencheng wants to merge 1 commit intomainfrom
docs/fix-cloud-setup-s4.4-policy-split

Conversation

@hanwencheng
Copy link
Copy Markdown
Member

Summary

docs/cloud-setup.md §4.4's put-bucket-policy snippet is rejected by AWS at policy-save time:

MalformedPolicy: Conditions do not apply to combination of actions and resources in statement

Two bugs in the original snippet:

  1. Combined actions under one s3:prefix condition. s3:prefix is a request-time condition that only exists for s3:ListBucket (the prefix filter on listings). s3:GetObject targets a specific key and has no prefix parameter, so combining the two actions under one statement with an s3:prefix condition is semantically incoherent — AWS rejects it at validation. Fix: split into two statements (one per action), per the standard AWS pattern.

  2. StringEquals "${tag}/" would only allow listing the exact root prefix (e.g. 0xABC/), not sub-prefixes like 0xABC/inbox/ or 0xABC/sent/2026-05/. The daemon needs to list deeper paths, so this would break the read path even after fixing bug docs: human-readable keychain metadata in manual tests + field-name translation design note #1. Fix: use StringLike "${tag}/*".

After this change, aws s3api put-bucket-policy accepts the policy and the daemon can list+get its own prefix as intended.

Why this matches the rest of the repo

The split-statement + StringLike "${tag}/*" shape is already what the spec and wiki describe:

  • docs/spec/ses-email-architecture.md §10.4 (lines 307–319) — AllowListOwnPrefix (ListBucket + s3:prefix StringLike) + AllowCrudOwnPrefix (GetObject/PutObject/DeleteObject, resource-ARN-scoped)
  • wiki/tag-based-access.md lines 154–174 — same shape

The runbook author tried to be DRY by collapsing both into a single statement; the spec docs got the shape right. This PR brings the runbook in line with the spec.

Defense-in-depth preserved

Action How tenant boundary is enforced
s3:ListBucket s3:prefix condition forces every list call to filter by ${PrincipalTag}/*. Session tagged wallet=A can only list A/... keys.
s3:GetObject Resource ARN is bucket/${PrincipalTag}/*, expanded at request time. Session tagged wallet=A can only GetObject on A/foo, A/bar/baz, etc.

Same defense-in-depth as the broken version, just in two statements that AWS will actually accept.

Verification

Validated the new jq snippet locally:

BUCKET=agentkeys-mail-429071895007 ACCOUNT_ID=429071895007 jq -n --arg bucket "$BUCKET" --arg acct "$ACCOUNT_ID" '{...}'
# Produces well-formed JSON; ${aws:PrincipalTag/...} placeholders preserved for AWS to expand at request time

Will run end-to-end §4.5 against this policy as part of bringing up Stage 7 (#62).

Test plan

  • Run the §4.4 aws s3api put-bucket-policy snippet against a fresh AWS account — should succeed (no MalformedPolicy).
  • Mint an OIDC JWT with agentkeys_user_wallet=<wallet_a>, exchange for STS creds, confirm aws s3 ls s3://$BUCKET/<wallet_a>/inbox/ succeeds.
  • Same session: aws s3 ls s3://$BUCKET/<wallet_b>/inbox/ should fail with AccessDenied.
  • Same session: aws s3 cp s3://$BUCKET/<wallet_b>/foo.eml - should fail with AccessDenied (resource-ARN check).

🤖 Generated with Claude Code

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant