Every engine, job and person should reach the bucket with its own key and only the access its task needs (Least Privilege as a Default). MinIO 30,943 speaks AWS 24 IAM's JSON grammar, in which an explicit Deny beats any Allow. BookNest's BI tools get the warehouse's tables except the two holding personal data:
{
"Version": "2012-10-17",
"Statement": [
{"Effect": "Allow", "Action": ["s3:ListBucket", "s3:GetObject"],
"Resource": ["arn:aws:s3:::warehouse", "arn:aws:s3:::warehouse/booknest/*"]},
{"Effect": "Deny", "Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3:::warehouse/booknest/customers/*",
"arn:aws:s3:::warehouse/booknest/support_tickets/*"]}
]
}security/iam.sh creates the policy and a user bi-reader with it attached (mc admin). As bi-reader, reading a books metadata file returned "format-version":2; reading a customers metadata file and writing into the warehouse failed with "Insufficient permissions", and listing booknest-raw with "Access Denied".
On AWS the same document attaches to a role the engine assumes, so no long-lived key exists, and a bucket policy can add conditions such as aws:SecureTransport (refuse plain HTTP). Grant prefixes rather than buckets, keep writers and readers apart, and review the policies whenever a table holding PII arrives, as customers did in Governance.