carlos / platform Public

README: operator notes for the mail user boundary

What to set (CARLOS_MAIL_USER_BOUNDARY), the IAM the console role needs
(iam:PutUserPermissionsBoundary on user/carlos-mail/*, conditioned on
the boundary ARN), that the backfill is lazy, and when to condition
CreateUser and PutUserPolicy on iam:PermissionsBoundary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 9a8e54ac4f1c8eab4f8cb54ba8fbcd94b1eebd46 parent 45ffd7a
1 file changed, +9 −0
  • README.md +9 −0
diff --git a/README.md b/README.md
index 7a4b80895..940768627 100644
--- a/README.md
+++ b/README.md
@@ -496,6 +496,15 @@ This release stops every box and the console from reading the whole deployment b
- customer fleet boxes keep reading the way they do today.
- to see the effect, look at daily S3 ListBucket and GetObject requests for the deployment bucket in Cost Explorer. they should fall within a day of arming.
+### Email sending users get a permissions boundary (2026-10-23)
+
+This release creates every email sending user (IAM path `/carlos-mail/`) under a permissions boundary, the way store users already are. Without one, anything that can act as the console's role can create a user on that path, give it an administrator policy and mint it a key.
+
+- a console with `CARLOS_MAIL_REGION` set now refuses to start without `CARLOS_MAIL_USER_BOUNDARY`. set it to the ARN of an IAM managed policy that allows `ses:SendRawEmail` and nothing else.
+- create that policy and give the console role `iam:PutUserPermissionsBoundary` on `user/carlos-mail/*` before this rolls. condition it on `iam:PermissionsBoundary` equal to the boundary ARN, or the role can set any boundary it likes. never grant `iam:DeleteUserPermissionsBoundary`. nothing calls it.
+- existing users are fixed lazily. a user without the boundary, or with a different one, gets the configured one the next time the console ensures it: `carlos email enable` for the app, a new standalone credential, or a domain change that refreshes the app's policies. until then it stays unbounded. list the ones still missing it with `aws iam get-user` over `aws iam list-users --path-prefix /carlos-mail/`.
+- once every console runs this release and every existing user carries the boundary, condition the role's `iam:CreateUser` and `iam:PutUserPolicy` on `iam:PermissionsBoundary` too. the console sets the boundary before it writes the policy, so the condition does not block the backfill.
+
## App config & secrets
Per-app config lives in the deployment bucket