[{"data":1,"prerenderedAt":1640},["ShallowReactive",2],{"post-\u002Fblog\u002Fabac-object-tags-vs-prefix-policies":3},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"author":11,"tags":12,"body":17,"_type":1634,"_id":1635,"_source":1636,"_file":1637,"_stem":1638,"_extension":1639},"\u002Fblog\u002Fabac-object-tags-vs-prefix-policies","blog",false,"","Object tags or prefixes: which access model survives growth","Prefixes decide what a principal can list, tags decide what it can read. Two AWS limits break tag-only designs, and prefix policies run out at about 60 tenants.","2026-11-03","Clement Baranger",[13,14,15,16],"s3","iam","governance","access-control",{"type":18,"children":19,"toc":1626},"root",[20,29,130,136,158,190,202,207,291,296,305,377,387,393,449,502,535,540,632,673,682,699,705,729,737,808,827,845,1090,1095,1170,1180,1203,1381,1391,1415,1421,1447,1462,1468,1592,1620],{"type":21,"tag":22,"props":23,"children":25},"element","h2",{"id":24},"the-short-version",[26],{"type":27,"value":28},"text","The short version",{"type":21,"tag":30,"props":31,"children":32},"ul",{},[33,71,76,88,100,110],{"type":21,"tag":34,"props":35,"children":36},"li",{},[37,39,45,47,54,56,61,63,69],{"type":27,"value":38},"A ",{"type":21,"tag":40,"props":41,"children":42},"strong",{},[43],{"type":27,"value":44},"prefix",{"type":27,"value":46}," is part of the file name: ",{"type":21,"tag":48,"props":49,"children":51},"code",{"className":50},[],[52],{"type":27,"value":53},"invoices\u002Facme\u002F2026\u002F",{"type":27,"value":55},". A ",{"type":21,"tag":40,"props":57,"children":58},{},[59],{"type":27,"value":60},"tag",{"type":27,"value":62}," is a label stuck to the file itself: ",{"type":21,"tag":48,"props":64,"children":66},{"className":65},[],[67],{"type":27,"value":68},"tenant=acme",{"type":27,"value":70},".",{"type":21,"tag":34,"props":72,"children":73},{},[74],{"type":27,"value":75},"AWS can check a tag only when someone asks for one specific file. When a program lists a folder, the tags are not returned at all — so a tag-only design hands people a file list full of things they cannot open.",{"type":21,"tag":34,"props":77,"children":78},{},[79,81,86],{"type":27,"value":80},"Storing tags is cheap: ",{"type":21,"tag":40,"props":82,"children":83},{},[84],{"type":27,"value":85},"$0.0065 per 10,000 tags per month",{"type":27,"value":87}," on AWS (list price, us-east-1, 3 November 2026). Three tags on a million files is about $2 a month. Labelling the files you already have, and reading those labels back, is where the money and the hours go.",{"type":21,"tag":34,"props":89,"children":90},{},[91,93,98],{"type":27,"value":92},"A bucket policy cannot exceed ",{"type":21,"tag":40,"props":94,"children":95},{},[96],{"type":27,"value":97},"20 KB",{"type":27,"value":99},". One realistic tenant statement is about 330 bytes, so roughly 60 tenants fit before someone starts splitting buckets.",{"type":21,"tag":34,"props":101,"children":102},{},[103,108],{"type":21,"tag":40,"props":104,"children":105},{},[106],{"type":27,"value":107},"Tags cannot stop a delete.",{"type":27,"value":109}," AWS explicitly does not support the tag condition on PUT or DELETE requests. Deletion control has to come from a prefix or from Object Lock.",{"type":21,"tag":34,"props":111,"children":112},{},[113,115,121,123,128],{"type":27,"value":114},"Verdict: prefixes for ",{"type":21,"tag":116,"props":117,"children":118},"em",{},[119],{"type":27,"value":120},"navigation",{"type":27,"value":122},", tags for ",{"type":21,"tag":116,"props":124,"children":125},{},[126],{"type":27,"value":127},"authorisation on reads",{"type":27,"value":129},". Not one or the other.",{"type":21,"tag":22,"props":131,"children":133},{"id":132},"what-actually-happens",[134],{"type":27,"value":135},"What actually happens",{"type":21,"tag":137,"props":138,"children":139},"p",{},[140,142,148,150,156],{"type":27,"value":141},"S3 has no folders. A key is one string — ",{"type":21,"tag":48,"props":143,"children":145},{"className":144},[],[146],{"type":27,"value":147},"invoices\u002Facme\u002F2026\u002FINV-1042.pdf",{"type":27,"value":149}," — and the slashes are a convention that the API calls a prefix when you pass it as a filter. A policy matches keys with wildcards on the ARN, so ",{"type":21,"tag":48,"props":151,"children":153},{"className":152},[],[154],{"type":27,"value":155},"arn:aws:s3:::acme-docs\u002Finvoices\u002F*",{"type":27,"value":157}," is a prefix rule whether or not anyone calls it that.",{"type":21,"tag":137,"props":159,"children":160},{},[161,163,172,174,180,182,188],{"type":27,"value":162},"The consequence is structural: a prefix gives you exactly one dimension. AWS says so in its own ",{"type":21,"tag":164,"props":165,"children":169},"a",{"href":166,"rel":167},"https:\u002F\u002Fdocs.aws.amazon.com\u002FAmazonS3\u002Flatest\u002Fuserguide\u002Fobject-tagging.html",[168],"nofollow",[170],{"type":27,"value":171},"object tagging documentation",{"type":27,"value":173}," — prefix-based categorisation \"is one-dimensional\". Everything under ",{"type":21,"tag":48,"props":175,"children":177},{"className":176},[],[178],{"type":27,"value":179},"invoices\u002Facme\u002F",{"type":27,"value":181}," is one category, and the only way to express a second distinction is to encode it into the key itself, where it becomes permanent: ",{"type":21,"tag":48,"props":183,"children":185},{"className":184},[],[186],{"type":27,"value":187},"invoices\u002Facme\u002F2026\u002Fproduction\u002F",{"type":27,"value":189},". Renaming later means copying every object.",{"type":21,"tag":137,"props":191,"children":192},{},[193,195,200],{"type":27,"value":194},"Tags add the missing dimension. Each object carries up to ",{"type":21,"tag":40,"props":196,"children":197},{},[198],{"type":27,"value":199},"10 tags",{"type":27,"value":201},", keys up to 128 Unicode characters, values up to 256, and both are case-sensitive. They live beside the object, not in its name, so they can be changed without rewriting anything.",{"type":21,"tag":137,"props":203,"children":204},{},[205],{"type":27,"value":206},"Policies then have two families of condition key, and mixing them up is the first mistake:",{"type":21,"tag":30,"props":208,"children":209},{},[210,250],{"type":21,"tag":34,"props":211,"children":212},{},[213,219,221,226,228,234,236,242,243,249],{"type":21,"tag":48,"props":214,"children":216},{"className":215},[],[217],{"type":27,"value":218},"s3:ExistingObjectTag\u002F\u003Ckey>",{"type":27,"value":220}," inspects the tags an object ",{"type":21,"tag":40,"props":222,"children":223},{},[224],{"type":27,"value":225},"already has",{"type":27,"value":227},". It applies to operations on existing objects — ",{"type":21,"tag":48,"props":229,"children":231},{"className":230},[],[232],{"type":27,"value":233},"GetObject",{"type":27,"value":235},", ",{"type":21,"tag":48,"props":237,"children":239},{"className":238},[],[240],{"type":27,"value":241},"GetObjectVersion",{"type":27,"value":235},{"type":21,"tag":48,"props":244,"children":246},{"className":245},[],[247],{"type":27,"value":248},"HeadObject",{"type":27,"value":70},{"type":21,"tag":34,"props":251,"children":252},{},[253,259,261,267,269,274,276,282,283,289],{"type":21,"tag":48,"props":254,"children":256},{"className":255},[],[257],{"type":27,"value":258},"s3:RequestObjectTag\u002F\u003Ckey>",{"type":27,"value":260}," and ",{"type":21,"tag":48,"props":262,"children":264},{"className":263},[],[265],{"type":27,"value":266},"s3:RequestObjectTagKeys",{"type":27,"value":268}," inspect the tags ",{"type":21,"tag":40,"props":270,"children":271},{},[272],{"type":27,"value":273},"arriving in the request",{"type":27,"value":275},". They are what you use on ",{"type":21,"tag":48,"props":277,"children":279},{"className":278},[],[280],{"type":27,"value":281},"PutObject",{"type":27,"value":235},{"type":21,"tag":48,"props":284,"children":286},{"className":285},[],[287],{"type":27,"value":288},"PutObjectTagging",{"type":27,"value":290}," and POST uploads.",{"type":21,"tag":137,"props":292,"children":293},{},[294],{"type":27,"value":295},"So the tag condition is evaluated by S3 while authorising a read, against the stored tag set, before a byte of the object is returned. It costs nothing extra per request and cannot be forged by the client — unless the client can write tags, which is the trap in the next section.",{"type":21,"tag":137,"props":297,"children":298},{},[299],{"type":21,"tag":300,"props":301,"children":304},"img",{"alt":302,"src":303},"Two questions about one S3 object: ListObjectsV2 matches the key prefix and returns no tags, while GetObject evaluates the s3:ExistingObjectTag condition before returning bytes","\u002Fdiagrams\u002Fabac-object-tags-vs-prefix-policies.svg",[],{"type":21,"tag":137,"props":306,"children":307},{},[308,310,316,318,324,325,331,332,338,339,345,346,352,353,359,361,367,369,375],{"type":27,"value":309},"And the listing side is the part that decides the whole design. ",{"type":21,"tag":48,"props":311,"children":313},{"className":312},[],[314],{"type":27,"value":315},"ListObjectsV2",{"type":27,"value":317}," returns ",{"type":21,"tag":48,"props":319,"children":321},{"className":320},[],[322],{"type":27,"value":323},"Key",{"type":27,"value":235},{"type":21,"tag":48,"props":326,"children":328},{"className":327},[],[329],{"type":27,"value":330},"LastModified",{"type":27,"value":235},{"type":21,"tag":48,"props":333,"children":335},{"className":334},[],[336],{"type":27,"value":337},"ETag",{"type":27,"value":235},{"type":21,"tag":48,"props":340,"children":342},{"className":341},[],[343],{"type":27,"value":344},"Size",{"type":27,"value":235},{"type":21,"tag":48,"props":347,"children":349},{"className":348},[],[350],{"type":27,"value":351},"StorageClass",{"type":27,"value":260},{"type":21,"tag":48,"props":354,"children":356},{"className":355},[],[357],{"type":27,"value":358},"Owner",{"type":27,"value":360},". There is no tag field in the response, and no request parameter to filter by one. Tags are only visible through ",{"type":21,"tag":48,"props":362,"children":364},{"className":363},[],[365],{"type":27,"value":366},"GetObjectTagging",{"type":27,"value":368},", one object at a time. A principal with ",{"type":21,"tag":48,"props":370,"children":372},{"className":371},[],[373],{"type":27,"value":374},"s3:ListBucket",{"type":27,"value":376}," on the whole bucket therefore sees every key name, including the tenants it cannot read.",{"type":21,"tag":137,"props":378,"children":379},{},[380,382],{"type":27,"value":381},"That asymmetry is not a bug to work around. It is the shape of the system: ",{"type":21,"tag":40,"props":383,"children":384},{},[385],{"type":27,"value":386},"prefix answers \"what is here\", tag answers \"may I have this one\".",{"type":21,"tag":22,"props":388,"children":390},{"id":389},"where-it-bites",[391],{"type":27,"value":392},"Where it bites",{"type":21,"tag":137,"props":394,"children":395},{},[396,401,403,409,410,416,418,424,426,431,433,439,441,447],{"type":21,"tag":40,"props":397,"children":398},{},[399],{"type":27,"value":400},"1. Anyone who can tag can grant themselves access.",{"type":27,"value":402}," A role with ",{"type":21,"tag":48,"props":404,"children":406},{"className":405},[],[407],{"type":27,"value":408},"s3:PutObject",{"type":27,"value":260},{"type":21,"tag":48,"props":411,"children":413},{"className":412},[],[414],{"type":27,"value":415},"s3:PutObjectTagging",{"type":27,"value":417}," on ",{"type":21,"tag":48,"props":419,"children":421},{"className":420},[],[422],{"type":27,"value":423},"acme-docs\u002F*",{"type":27,"value":425}," can upload a file, tag it ",{"type":21,"tag":48,"props":427,"children":429},{"className":428},[],[430],{"type":27,"value":68},{"type":27,"value":432},", and read it back — or re-tag an existing file it owns and read that. The tag condition is a trust boundary only if tag-writing is restricted. AWS provides ",{"type":21,"tag":48,"props":434,"children":436},{"className":435},[],[437],{"type":27,"value":438},"s3:RequestObjectTag\u002Fenvironment",{"type":27,"value":440}," precisely for this: every write must carry an approved tag set, so ",{"type":21,"tag":48,"props":442,"children":444},{"className":443},[],[445],{"type":27,"value":446},"tenant",{"type":27,"value":448}," cannot be arbitrary.",{"type":21,"tag":137,"props":450,"children":451},{},[452,457,459,465,467,472,474,479,480,486,488,493,494,500],{"type":21,"tag":40,"props":453,"children":454},{},[455],{"type":27,"value":456},"2. The delete gap is real and surprising.",{"type":27,"value":458}," The AWS documentation is explicit that ",{"type":21,"tag":48,"props":460,"children":462},{"className":461},[],[463],{"type":27,"value":464},"s3:ExistingObjectTag",{"type":27,"value":466}," is ",{"type":21,"tag":40,"props":468,"children":469},{},[470],{"type":27,"value":471},"not supported",{"type":27,"value":473}," for ",{"type":21,"tag":48,"props":475,"children":477},{"className":476},[],[478],{"type":27,"value":281},{"type":27,"value":260},{"type":21,"tag":48,"props":481,"children":483},{"className":482},[],[484],{"type":27,"value":485},"DeleteObject",{"type":27,"value":487},": \"you cannot create a policy to grant or deny a user permissions to delete or overwrite an object based on its existing tags.\" A policy written as \"tenants may delete only their own objects, by tag\" does not do that. It does nothing on the delete path, and ",{"type":21,"tag":48,"props":489,"children":491},{"className":490},[],[492],{"type":27,"value":485},{"type":27,"value":317},{"type":21,"tag":48,"props":495,"children":497},{"className":496},[],[498],{"type":27,"value":499},"204 No Content",{"type":27,"value":501}," while the data goes. Deletion control is prefix-based, or it is Object Lock, or it does not exist.",{"type":21,"tag":137,"props":503,"children":504},{},[505,510,512,518,520,525,527,533],{"type":21,"tag":40,"props":506,"children":507},{},[508],{"type":27,"value":509},"3. Prefix policies die at the 20 KB wall.",{"type":27,"value":511}," A bucket policy is limited to ",{"type":21,"tag":164,"props":513,"children":516},{"href":514,"rel":515},"https:\u002F\u002Fdocs.aws.amazon.com\u002FAmazonS3\u002Flatest\u002Fuserguide\u002Fadd-bucket-policy.html",[168],[517],{"type":27,"value":97},{"type":27,"value":519},". A single realistic tenant statement — Sid, Principal, two actions, one resource ARN, two tag conditions — measures 333 bytes when minified. That is about ",{"type":21,"tag":40,"props":521,"children":522},{},[523],{"type":27,"value":524},"61 statements",{"type":27,"value":526}," before the policy will not save, and you still need the bucket-level statements for listing, uploads and the admin role. The symptom is not an error at design time; it is a ",{"type":21,"tag":48,"props":528,"children":530},{"className":529},[],[531],{"type":27,"value":532},"MalformedPolicy: Policy has invalid resource",{"type":27,"value":534}," or a silent truncation during a Terraform apply, on the day the sixty-second customer signs.",{"type":21,"tag":137,"props":536,"children":537},{},[538],{"type":27,"value":539},"The usual reaction is one bucket per tenant. That works, costs you cross-bucket reporting and a bucket quota per account, and multiplies every automation by N.",{"type":21,"tag":137,"props":541,"children":542},{},[543,548,550,556,558,564,566,571,573,579,581,586,587,592,593,599,601,608,610,616,618,623,625,630],{"type":21,"tag":40,"props":544,"children":545},{},[546],{"type":27,"value":547},"4. Tags do not travel everywhere.",{"type":27,"value":549}," ",{"type":21,"tag":48,"props":551,"children":553},{"className":552},[],[554],{"type":27,"value":555},"CopyObject",{"type":27,"value":557}," defaults to ",{"type":21,"tag":48,"props":559,"children":561},{"className":560},[],[562],{"type":27,"value":563},"x-amz-tagging-directive: COPY",{"type":27,"value":565},", so an ordinary copy keeps the tag set — good. But S3 ",{"type":21,"tag":40,"props":567,"children":568},{},[569],{"type":27,"value":570},"directory buckets",{"type":27,"value":572}," (Express One Zone) support only an empty tag set and return ",{"type":21,"tag":48,"props":574,"children":576},{"className":575},[],[577],{"type":27,"value":578},"501 Not Implemented",{"type":27,"value":580}," when a copy would bring non-empty tags across. And Cloudflare R2 implements no tagging at all: ",{"type":21,"tag":48,"props":582,"children":584},{"className":583},[],[585],{"type":27,"value":366},{"type":27,"value":235},{"type":21,"tag":48,"props":588,"children":590},{"className":589},[],[591],{"type":27,"value":288},{"type":27,"value":260},{"type":21,"tag":48,"props":594,"children":596},{"className":595},[],[597],{"type":27,"value":598},"DeleteObjectTagging",{"type":27,"value":600}," are listed as unimplemented in the ",{"type":21,"tag":164,"props":602,"children":605},{"href":603,"rel":604},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fr2\u002Fapi\u002Fs3\u002Fapi\u002F",[168],[606],{"type":27,"value":607},"R2 S3 compatibility matrix",{"type":27,"value":609},", and ",{"type":21,"tag":48,"props":611,"children":613},{"className":612},[],[614],{"type":27,"value":615},"x-amz-tagging",{"type":27,"value":617}," is rejected on ",{"type":21,"tag":48,"props":619,"children":621},{"className":620},[],[622],{"type":27,"value":281},{"type":27,"value":624},". An access model built on ",{"type":21,"tag":48,"props":626,"children":628},{"className":627},[],[629],{"type":27,"value":464},{"type":27,"value":631}," is an AWS general-purpose-bucket model, and migrating to R2 later means rewriting the authorisation layer and re-keying the namespace.",{"type":21,"tag":137,"props":633,"children":634},{},[635,640,642,648,650,655,657,663,665,671],{"type":21,"tag":40,"props":636,"children":637},{},[638],{"type":27,"value":639},"5. An untagged object becomes invisible, not just unreadable.",{"type":27,"value":641}," Every write path must tag: the SDK upload, the Lambda, the Airflow task, the CSV someone dropped in with ",{"type":21,"tag":48,"props":643,"children":645},{"className":644},[],[646],{"type":27,"value":647},"aws s3 cp",{"type":27,"value":649},". Miss one and those objects fail every tag condition, which after a policy cleanup reads as \"the file disappeared\". Worse, ",{"type":21,"tag":48,"props":651,"children":653},{"className":652},[],[654],{"type":27,"value":233},{"type":27,"value":656}," on a denied object returns ",{"type":21,"tag":48,"props":658,"children":660},{"className":659},[],[661],{"type":27,"value":662},"403 AccessDenied",{"type":27,"value":664}," — identical to the message for a key that does not exist, and one of the several ways the ",{"type":21,"tag":164,"props":666,"children":668},{"href":667},"\u002Fblog\u002Fbucket-policy-allow-still-denied",[669],{"type":27,"value":670},"five-gate evaluation order",{"type":27,"value":672}," can refuse a request while reporting nothing useful — so the first hour of the incident is usually spent proving the file is still there:",{"type":21,"tag":674,"props":675,"children":677},"pre",{"code":676},"An error occurred (AccessDenied) when calling the GetObject operation: Access Denied\n",[678],{"type":21,"tag":48,"props":679,"children":680},{"__ignoreMap":7},[681],{"type":27,"value":676},{"type":21,"tag":137,"props":683,"children":684},{},[685,690,692,697],{"type":21,"tag":40,"props":686,"children":687},{},[688],{"type":27,"value":689},"6. Enumerating tags is a request-per-object job.",{"type":27,"value":691}," Filtering a listing client-side means one ",{"type":21,"tag":48,"props":693,"children":695},{"className":694},[],[696],{"type":27,"value":366},{"type":27,"value":698}," call per key. On a million objects that is a million GET requests — $0.40 in request charges at $0.0004 per 1,000, which is trivial, and roughly 5.5 hours of wall clock at 50 requests per second, which is not. Worked example: your own latency per call sets the real number, and S3 does not batch tag reads.",{"type":21,"tag":22,"props":700,"children":702},{"id":701},"doing-it-right",[703],{"type":27,"value":704},"Doing it right",{"type":21,"tag":137,"props":706,"children":707},{},[708,713,715,720,722,727],{"type":21,"tag":40,"props":709,"children":710},{},[711],{"type":27,"value":712},"Step 1 — split the two jobs on paper.",{"type":27,"value":714}," Write one sentence and keep it: prefixes name the namespace a principal may ",{"type":21,"tag":116,"props":716,"children":717},{},[718],{"type":27,"value":719},"list",{"type":27,"value":721},"; tags carry the attributes that decide what it may ",{"type":21,"tag":116,"props":723,"children":724},{},[725],{"type":27,"value":726},"read",{"type":27,"value":728},". Any key that encodes a security attribute is a key you can never rename.",{"type":21,"tag":137,"props":730,"children":731},{},[732],{"type":21,"tag":40,"props":733,"children":734},{},[735],{"type":27,"value":736},"Step 2 — tag at write time, from the first object.",{"type":21,"tag":674,"props":738,"children":742},{"code":739,"language":740,"meta":7,"className":741,"style":7},"aws s3 cp invoice-1042.pdf \\\n  s3:\u002F\u002Facme-docs\u002Finvoices\u002Facme\u002F2026\u002FINV-1042.pdf \\\n  --tagging \"tenant=acme&environment=production\"\n","bash","language-bash shiki shiki-themes github-light-high-contrast github-dark-high-contrast",[743],{"type":21,"tag":48,"props":744,"children":745},{"__ignoreMap":7},[746,780,793],{"type":21,"tag":747,"props":748,"children":751},"span",{"class":749,"line":750},"line",1,[752,758,764,769,774],{"type":21,"tag":747,"props":753,"children":755},{"style":754},"--shiki-default:#702C00;--shiki-dark:#FFB757",[756],{"type":27,"value":757},"aws",{"type":21,"tag":747,"props":759,"children":761},{"style":760},"--shiki-default:#032563;--shiki-dark:#ADDCFF",[762],{"type":27,"value":763}," s3",{"type":21,"tag":747,"props":765,"children":766},{"style":760},[767],{"type":27,"value":768}," cp",{"type":21,"tag":747,"props":770,"children":771},{"style":760},[772],{"type":27,"value":773}," invoice-1042.pdf",{"type":21,"tag":747,"props":775,"children":777},{"style":776},"--shiki-default:#A0111F;--shiki-dark:#FF9492",[778],{"type":27,"value":779}," \\\n",{"type":21,"tag":747,"props":781,"children":783},{"class":749,"line":782},2,[784,789],{"type":21,"tag":747,"props":785,"children":786},{"style":760},[787],{"type":27,"value":788},"  s3:\u002F\u002Facme-docs\u002Finvoices\u002Facme\u002F2026\u002FINV-1042.pdf",{"type":21,"tag":747,"props":790,"children":791},{"style":776},[792],{"type":27,"value":779},{"type":21,"tag":747,"props":794,"children":796},{"class":749,"line":795},3,[797,803],{"type":21,"tag":747,"props":798,"children":800},{"style":799},"--shiki-default:#023B95;--shiki-dark:#91CBFF",[801],{"type":27,"value":802},"  --tagging",{"type":21,"tag":747,"props":804,"children":805},{"style":760},[806],{"type":27,"value":807}," \"tenant=acme&environment=production\"\n",{"type":21,"tag":137,"props":809,"children":810},{},[811,813,818,820,825],{"type":27,"value":812},"The caller needs ",{"type":21,"tag":48,"props":814,"children":816},{"className":815},[],[817],{"type":27,"value":415},{"type":27,"value":819}," in addition to ",{"type":21,"tag":48,"props":821,"children":823},{"className":822},[],[824],{"type":27,"value":408},{"type":27,"value":826},". Grant both, or neither.",{"type":21,"tag":137,"props":828,"children":829},{},[830,835,837,843],{"type":21,"tag":40,"props":831,"children":832},{},[833],{"type":27,"value":834},"Step 3 — write one statement, not one per tenant.",{"type":27,"value":836}," Pair the object tag with the caller's own identity tag. ",{"type":21,"tag":48,"props":838,"children":840},{"className":839},[],[841],{"type":27,"value":842},"aws:PrincipalTag\u002Ftenant",{"type":27,"value":844}," is filled from the role session, so a single statement serves every tenant:",{"type":21,"tag":674,"props":846,"children":850},{"code":847,"language":848,"meta":7,"className":849,"style":7},"{\n  \"Sid\": \"ReadOwnTenantObjects\",\n  \"Effect\": \"Allow\",\n  \"Principal\": {\"AWS\": \"arn:aws:iam::111122223333:role\u002FSupportReadOnly\"},\n  \"Action\": [\"s3:GetObject\", \"s3:GetObjectVersion\"],\n  \"Resource\": \"arn:aws:s3:::acme-docs\u002Finvoices\u002F*\",\n  \"Condition\": {\n    \"StringEquals\": {\n      \"s3:ExistingObjectTag\u002Ftenant\": \"${aws:PrincipalTag\u002Ftenant}\",\n      \"s3:ExistingObjectTag\u002Fenvironment\": \"production\"\n    }\n  }\n}\n","json","language-json shiki shiki-themes github-light-high-contrast github-dark-high-contrast",[851],{"type":21,"tag":48,"props":852,"children":853},{"__ignoreMap":7},[854,863,887,908,941,974,996,1010,1023,1045,1063,1072,1081],{"type":21,"tag":747,"props":855,"children":856},{"class":749,"line":750},[857],{"type":21,"tag":747,"props":858,"children":860},{"style":859},"--shiki-default:#0E1116;--shiki-dark:#F0F3F6",[861],{"type":27,"value":862},"{\n",{"type":21,"tag":747,"props":864,"children":865},{"class":749,"line":782},[866,872,877,882],{"type":21,"tag":747,"props":867,"children":869},{"style":868},"--shiki-default:#024C1A;--shiki-dark:#72F088",[870],{"type":27,"value":871},"  \"Sid\"",{"type":21,"tag":747,"props":873,"children":874},{"style":859},[875],{"type":27,"value":876},": ",{"type":21,"tag":747,"props":878,"children":879},{"style":760},[880],{"type":27,"value":881},"\"ReadOwnTenantObjects\"",{"type":21,"tag":747,"props":883,"children":884},{"style":859},[885],{"type":27,"value":886},",\n",{"type":21,"tag":747,"props":888,"children":889},{"class":749,"line":795},[890,895,899,904],{"type":21,"tag":747,"props":891,"children":892},{"style":868},[893],{"type":27,"value":894},"  \"Effect\"",{"type":21,"tag":747,"props":896,"children":897},{"style":859},[898],{"type":27,"value":876},{"type":21,"tag":747,"props":900,"children":901},{"style":760},[902],{"type":27,"value":903},"\"Allow\"",{"type":21,"tag":747,"props":905,"children":906},{"style":859},[907],{"type":27,"value":886},{"type":21,"tag":747,"props":909,"children":911},{"class":749,"line":910},4,[912,917,922,927,931,936],{"type":21,"tag":747,"props":913,"children":914},{"style":868},[915],{"type":27,"value":916},"  \"Principal\"",{"type":21,"tag":747,"props":918,"children":919},{"style":859},[920],{"type":27,"value":921},": {",{"type":21,"tag":747,"props":923,"children":924},{"style":868},[925],{"type":27,"value":926},"\"AWS\"",{"type":21,"tag":747,"props":928,"children":929},{"style":859},[930],{"type":27,"value":876},{"type":21,"tag":747,"props":932,"children":933},{"style":760},[934],{"type":27,"value":935},"\"arn:aws:iam::111122223333:role\u002FSupportReadOnly\"",{"type":21,"tag":747,"props":937,"children":938},{"style":859},[939],{"type":27,"value":940},"},\n",{"type":21,"tag":747,"props":942,"children":944},{"class":749,"line":943},5,[945,950,955,960,964,969],{"type":21,"tag":747,"props":946,"children":947},{"style":868},[948],{"type":27,"value":949},"  \"Action\"",{"type":21,"tag":747,"props":951,"children":952},{"style":859},[953],{"type":27,"value":954},": [",{"type":21,"tag":747,"props":956,"children":957},{"style":760},[958],{"type":27,"value":959},"\"s3:GetObject\"",{"type":21,"tag":747,"props":961,"children":962},{"style":859},[963],{"type":27,"value":235},{"type":21,"tag":747,"props":965,"children":966},{"style":760},[967],{"type":27,"value":968},"\"s3:GetObjectVersion\"",{"type":21,"tag":747,"props":970,"children":971},{"style":859},[972],{"type":27,"value":973},"],\n",{"type":21,"tag":747,"props":975,"children":977},{"class":749,"line":976},6,[978,983,987,992],{"type":21,"tag":747,"props":979,"children":980},{"style":868},[981],{"type":27,"value":982},"  \"Resource\"",{"type":21,"tag":747,"props":984,"children":985},{"style":859},[986],{"type":27,"value":876},{"type":21,"tag":747,"props":988,"children":989},{"style":760},[990],{"type":27,"value":991},"\"arn:aws:s3:::acme-docs\u002Finvoices\u002F*\"",{"type":21,"tag":747,"props":993,"children":994},{"style":859},[995],{"type":27,"value":886},{"type":21,"tag":747,"props":997,"children":999},{"class":749,"line":998},7,[1000,1005],{"type":21,"tag":747,"props":1001,"children":1002},{"style":868},[1003],{"type":27,"value":1004},"  \"Condition\"",{"type":21,"tag":747,"props":1006,"children":1007},{"style":859},[1008],{"type":27,"value":1009},": {\n",{"type":21,"tag":747,"props":1011,"children":1013},{"class":749,"line":1012},8,[1014,1019],{"type":21,"tag":747,"props":1015,"children":1016},{"style":868},[1017],{"type":27,"value":1018},"    \"StringEquals\"",{"type":21,"tag":747,"props":1020,"children":1021},{"style":859},[1022],{"type":27,"value":1009},{"type":21,"tag":747,"props":1024,"children":1026},{"class":749,"line":1025},9,[1027,1032,1036,1041],{"type":21,"tag":747,"props":1028,"children":1029},{"style":868},[1030],{"type":27,"value":1031},"      \"s3:ExistingObjectTag\u002Ftenant\"",{"type":21,"tag":747,"props":1033,"children":1034},{"style":859},[1035],{"type":27,"value":876},{"type":21,"tag":747,"props":1037,"children":1038},{"style":760},[1039],{"type":27,"value":1040},"\"${aws:PrincipalTag\u002Ftenant}\"",{"type":21,"tag":747,"props":1042,"children":1043},{"style":859},[1044],{"type":27,"value":886},{"type":21,"tag":747,"props":1046,"children":1048},{"class":749,"line":1047},10,[1049,1054,1058],{"type":21,"tag":747,"props":1050,"children":1051},{"style":868},[1052],{"type":27,"value":1053},"      \"s3:ExistingObjectTag\u002Fenvironment\"",{"type":21,"tag":747,"props":1055,"children":1056},{"style":859},[1057],{"type":27,"value":876},{"type":21,"tag":747,"props":1059,"children":1060},{"style":760},[1061],{"type":27,"value":1062},"\"production\"\n",{"type":21,"tag":747,"props":1064,"children":1066},{"class":749,"line":1065},11,[1067],{"type":21,"tag":747,"props":1068,"children":1069},{"style":859},[1070],{"type":27,"value":1071},"    }\n",{"type":21,"tag":747,"props":1073,"children":1075},{"class":749,"line":1074},12,[1076],{"type":21,"tag":747,"props":1077,"children":1078},{"style":859},[1079],{"type":27,"value":1080},"  }\n",{"type":21,"tag":747,"props":1082,"children":1084},{"class":749,"line":1083},13,[1085],{"type":21,"tag":747,"props":1086,"children":1087},{"style":859},[1088],{"type":27,"value":1089},"}\n",{"type":21,"tag":137,"props":1091,"children":1092},{},[1093],{"type":27,"value":1094},"This is the whole reason ABAC scales: the policy size is constant, whether you have 10 tenants or 10,000. Set the principal tag where the session is minted, and never let a caller supply it:",{"type":21,"tag":674,"props":1096,"children":1098},{"code":1097,"language":740,"meta":7,"className":741,"style":7},"aws sts assume-role \\\n  --role-arn arn:aws:iam::111122223333:role\u002FSupportReadOnly \\\n  --role-session-name support-acme \\\n  --tags Key=tenant,Value=acme\n",[1099],{"type":21,"tag":48,"props":1100,"children":1101},{"__ignoreMap":7},[1102,1123,1140,1157],{"type":21,"tag":747,"props":1103,"children":1104},{"class":749,"line":750},[1105,1109,1114,1119],{"type":21,"tag":747,"props":1106,"children":1107},{"style":754},[1108],{"type":27,"value":757},{"type":21,"tag":747,"props":1110,"children":1111},{"style":760},[1112],{"type":27,"value":1113}," sts",{"type":21,"tag":747,"props":1115,"children":1116},{"style":760},[1117],{"type":27,"value":1118}," assume-role",{"type":21,"tag":747,"props":1120,"children":1121},{"style":776},[1122],{"type":27,"value":779},{"type":21,"tag":747,"props":1124,"children":1125},{"class":749,"line":782},[1126,1131,1136],{"type":21,"tag":747,"props":1127,"children":1128},{"style":799},[1129],{"type":27,"value":1130},"  --role-arn",{"type":21,"tag":747,"props":1132,"children":1133},{"style":760},[1134],{"type":27,"value":1135}," arn:aws:iam::111122223333:role\u002FSupportReadOnly",{"type":21,"tag":747,"props":1137,"children":1138},{"style":776},[1139],{"type":27,"value":779},{"type":21,"tag":747,"props":1141,"children":1142},{"class":749,"line":795},[1143,1148,1153],{"type":21,"tag":747,"props":1144,"children":1145},{"style":799},[1146],{"type":27,"value":1147},"  --role-session-name",{"type":21,"tag":747,"props":1149,"children":1150},{"style":760},[1151],{"type":27,"value":1152}," support-acme",{"type":21,"tag":747,"props":1154,"children":1155},{"style":776},[1156],{"type":27,"value":779},{"type":21,"tag":747,"props":1158,"children":1159},{"class":749,"line":910},[1160,1165],{"type":21,"tag":747,"props":1161,"children":1162},{"style":799},[1163],{"type":27,"value":1164},"  --tags",{"type":21,"tag":747,"props":1166,"children":1167},{"style":760},[1168],{"type":27,"value":1169}," Key=tenant,Value=acme\n",{"type":21,"tag":137,"props":1171,"children":1172},{},[1173,1178],{"type":21,"tag":40,"props":1174,"children":1175},{},[1176],{"type":27,"value":1177},"Step 4 — label what already exists before you switch the policy on.",{"type":27,"value":1179}," S3 Batch Operations runs a tagging job over an inventory: $0.25 per job plus $1 per million objects. Ten million objects is $10.25 and one afternoon, not a rewrite. Until the job completes, keep the old prefix policy in place — the two can coexist.",{"type":21,"tag":137,"props":1181,"children":1182},{},[1183,1188,1190,1195,1196,1201],{"type":21,"tag":40,"props":1184,"children":1185},{},[1186],{"type":27,"value":1187},"Step 5 — lock the tag-writing path.",{"type":27,"value":1189}," Deny ",{"type":21,"tag":48,"props":1191,"children":1193},{"className":1192},[],[1194],{"type":27,"value":415},{"type":27,"value":260},{"type":21,"tag":48,"props":1197,"children":1199},{"className":1198},[],[1200],{"type":27,"value":408},{"type":27,"value":1202}," without an approved tag set, so nobody can mint access:",{"type":21,"tag":674,"props":1204,"children":1206},{"code":1205,"language":848,"meta":7,"className":849,"style":7},"{\n  \"Sid\": \"RequireApprovedTagsOnWrite\",\n  \"Effect\": \"Deny\",\n  \"Principal\": \"*\",\n  \"Action\": [\"s3:PutObject\", \"s3:PutObjectTagging\"],\n  \"Resource\": \"arn:aws:s3:::acme-docs\u002F*\",\n  \"Condition\": {\n    \"Null\": {\"s3:RequestObjectTag\u002Ftenant\": \"true\"}\n  }\n}\n",[1207],{"type":21,"tag":48,"props":1208,"children":1209},{"__ignoreMap":7},[1210,1217,1237,1257,1277,1306,1326,1337,1367,1374],{"type":21,"tag":747,"props":1211,"children":1212},{"class":749,"line":750},[1213],{"type":21,"tag":747,"props":1214,"children":1215},{"style":859},[1216],{"type":27,"value":862},{"type":21,"tag":747,"props":1218,"children":1219},{"class":749,"line":782},[1220,1224,1228,1233],{"type":21,"tag":747,"props":1221,"children":1222},{"style":868},[1223],{"type":27,"value":871},{"type":21,"tag":747,"props":1225,"children":1226},{"style":859},[1227],{"type":27,"value":876},{"type":21,"tag":747,"props":1229,"children":1230},{"style":760},[1231],{"type":27,"value":1232},"\"RequireApprovedTagsOnWrite\"",{"type":21,"tag":747,"props":1234,"children":1235},{"style":859},[1236],{"type":27,"value":886},{"type":21,"tag":747,"props":1238,"children":1239},{"class":749,"line":795},[1240,1244,1248,1253],{"type":21,"tag":747,"props":1241,"children":1242},{"style":868},[1243],{"type":27,"value":894},{"type":21,"tag":747,"props":1245,"children":1246},{"style":859},[1247],{"type":27,"value":876},{"type":21,"tag":747,"props":1249,"children":1250},{"style":760},[1251],{"type":27,"value":1252},"\"Deny\"",{"type":21,"tag":747,"props":1254,"children":1255},{"style":859},[1256],{"type":27,"value":886},{"type":21,"tag":747,"props":1258,"children":1259},{"class":749,"line":910},[1260,1264,1268,1273],{"type":21,"tag":747,"props":1261,"children":1262},{"style":868},[1263],{"type":27,"value":916},{"type":21,"tag":747,"props":1265,"children":1266},{"style":859},[1267],{"type":27,"value":876},{"type":21,"tag":747,"props":1269,"children":1270},{"style":760},[1271],{"type":27,"value":1272},"\"*\"",{"type":21,"tag":747,"props":1274,"children":1275},{"style":859},[1276],{"type":27,"value":886},{"type":21,"tag":747,"props":1278,"children":1279},{"class":749,"line":943},[1280,1284,1288,1293,1297,1302],{"type":21,"tag":747,"props":1281,"children":1282},{"style":868},[1283],{"type":27,"value":949},{"type":21,"tag":747,"props":1285,"children":1286},{"style":859},[1287],{"type":27,"value":954},{"type":21,"tag":747,"props":1289,"children":1290},{"style":760},[1291],{"type":27,"value":1292},"\"s3:PutObject\"",{"type":21,"tag":747,"props":1294,"children":1295},{"style":859},[1296],{"type":27,"value":235},{"type":21,"tag":747,"props":1298,"children":1299},{"style":760},[1300],{"type":27,"value":1301},"\"s3:PutObjectTagging\"",{"type":21,"tag":747,"props":1303,"children":1304},{"style":859},[1305],{"type":27,"value":973},{"type":21,"tag":747,"props":1307,"children":1308},{"class":749,"line":976},[1309,1313,1317,1322],{"type":21,"tag":747,"props":1310,"children":1311},{"style":868},[1312],{"type":27,"value":982},{"type":21,"tag":747,"props":1314,"children":1315},{"style":859},[1316],{"type":27,"value":876},{"type":21,"tag":747,"props":1318,"children":1319},{"style":760},[1320],{"type":27,"value":1321},"\"arn:aws:s3:::acme-docs\u002F*\"",{"type":21,"tag":747,"props":1323,"children":1324},{"style":859},[1325],{"type":27,"value":886},{"type":21,"tag":747,"props":1327,"children":1328},{"class":749,"line":998},[1329,1333],{"type":21,"tag":747,"props":1330,"children":1331},{"style":868},[1332],{"type":27,"value":1004},{"type":21,"tag":747,"props":1334,"children":1335},{"style":859},[1336],{"type":27,"value":1009},{"type":21,"tag":747,"props":1338,"children":1339},{"class":749,"line":1012},[1340,1345,1349,1354,1358,1363],{"type":21,"tag":747,"props":1341,"children":1342},{"style":868},[1343],{"type":27,"value":1344},"    \"Null\"",{"type":21,"tag":747,"props":1346,"children":1347},{"style":859},[1348],{"type":27,"value":921},{"type":21,"tag":747,"props":1350,"children":1351},{"style":868},[1352],{"type":27,"value":1353},"\"s3:RequestObjectTag\u002Ftenant\"",{"type":21,"tag":747,"props":1355,"children":1356},{"style":859},[1357],{"type":27,"value":876},{"type":21,"tag":747,"props":1359,"children":1360},{"style":760},[1361],{"type":27,"value":1362},"\"true\"",{"type":21,"tag":747,"props":1364,"children":1365},{"style":859},[1366],{"type":27,"value":1089},{"type":21,"tag":747,"props":1368,"children":1369},{"class":749,"line":1025},[1370],{"type":21,"tag":747,"props":1371,"children":1372},{"style":859},[1373],{"type":27,"value":1080},{"type":21,"tag":747,"props":1375,"children":1376},{"class":749,"line":1047},[1377],{"type":21,"tag":747,"props":1378,"children":1379},{"style":859},[1380],{"type":27,"value":1089},{"type":21,"tag":137,"props":1382,"children":1383},{},[1384,1389],{"type":21,"tag":40,"props":1385,"children":1386},{},[1387],{"type":27,"value":1388},"Step 6 — keep the delete control where it works.",{"type":27,"value":1390}," A prefix deny or Object Lock retention, depending on whether the requirement is \"cannot delete\" or \"cannot delete yet\". Verify with a real delete attempt from a test principal, not by reading the policy.",{"type":21,"tag":137,"props":1392,"children":1393},{},[1394,1399,1400,1406,1408,1413],{"type":21,"tag":40,"props":1395,"children":1396},{},[1397],{"type":27,"value":1398},"Step 7 — verify the read path per tenant.",{"type":27,"value":549},{"type":21,"tag":48,"props":1401,"children":1403},{"className":1402},[],[1404],{"type":27,"value":1405},"aws s3api get-object-tagging",{"type":27,"value":1407}," confirms what is stored; a ",{"type":21,"tag":48,"props":1409,"children":1411},{"className":1410},[],[1412],{"type":27,"value":233},{"type":27,"value":1414}," with the tenant's role confirms what is authorised. Both, on the same key, before you call it done.",{"type":21,"tag":22,"props":1416,"children":1418},{"id":1417},"how-s3-explorer-fits",[1419],{"type":27,"value":1420},"How S3 Explorer fits",{"type":21,"tag":137,"props":1422,"children":1423},{},[1424,1426,1431,1433,1438,1440,1445],{"type":27,"value":1425},"Tag-based authorisation produces exactly the confusion this article describes: a key that is listed and then refuses to open. S3 Explorer lets you look at both halves of that from one window — the ",{"type":21,"tag":40,"props":1427,"children":1428},{},[1429],{"type":27,"value":1430},"folder navigation",{"type":27,"value":1432}," shows the prefix tree a principal actually sees, and the object's ",{"type":21,"tag":40,"props":1434,"children":1435},{},[1436],{"type":27,"value":1437},"Metadata",{"type":27,"value":1439}," tab shows what the bucket stores for that key, so you can tell a mis-tagged object from a mis-scoped policy before you edit anything. The ",{"type":21,"tag":40,"props":1441,"children":1442},{},[1443],{"type":27,"value":1444},"connection test",{"type":27,"value":1446}," on the connection form reports how many buckets the credentials can see before you save the profile, which is the fastest way to check that a support role has the namespace you think it has. It connects to 25 S3-compatible providers; a read-only key is enough for everything above.",{"type":21,"tag":137,"props":1448,"children":1449},{},[1450,1460],{"type":21,"tag":40,"props":1451,"children":1452},{},[1453],{"type":21,"tag":164,"props":1454,"children":1457},{"href":1455,"rel":1456},"https:\u002F\u002Fs3explorer.accessdevops.com",[168],[1458],{"type":27,"value":1459},"S3 Explorer",{"type":27,"value":1461}," — desktop client for Windows, macOS and Linux.",{"type":21,"tag":22,"props":1463,"children":1465},{"id":1464},"checklist",[1466],{"type":27,"value":1467},"Checklist",{"type":21,"tag":30,"props":1469,"children":1472},{"className":1470},[1471],"contains-task-list",[1473,1494,1511,1526,1556,1565,1574,1583],{"type":21,"tag":34,"props":1474,"children":1477},{"className":1475},[1476],"task-list-item",[1478,1484,1486,1492],{"type":21,"tag":1479,"props":1480,"children":1483},"input",{"disabled":1481,"type":1482},true,"checkbox",[],{"type":27,"value":1485}," Every write path in the account tags objects — SDK uploads, functions, batch jobs and human ",{"type":21,"tag":48,"props":1487,"children":1489},{"className":1488},[],[1490],{"type":27,"value":1491},"s3 cp",{"type":27,"value":1493}," runs.",{"type":21,"tag":34,"props":1495,"children":1497},{"className":1496},[1476],[1498,1501,1503,1509],{"type":21,"tag":1479,"props":1499,"children":1500},{"disabled":1481,"type":1482},[],{"type":27,"value":1502}," Tag values come from an allow-list, enforced by ",{"type":21,"tag":48,"props":1504,"children":1506},{"className":1505},[],[1507],{"type":27,"value":1508},"s3:RequestObjectTag",{"type":27,"value":1510},", not from client input.",{"type":21,"tag":34,"props":1512,"children":1514},{"className":1513},[1476],[1515,1518,1519,1524],{"type":21,"tag":1479,"props":1516,"children":1517},{"disabled":1481,"type":1482},[],{"type":27,"value":549},{"type":21,"tag":48,"props":1520,"children":1522},{"className":1521},[],[1523],{"type":27,"value":288},{"type":27,"value":1525}," is denied to every principal that does not need to re-tag, so tagging cannot grant access.",{"type":21,"tag":34,"props":1527,"children":1529},{"className":1528},[1476],[1530,1533,1535,1540,1542,1547,1549,1554],{"type":21,"tag":1479,"props":1531,"children":1532},{"disabled":1481,"type":1482},[],{"type":27,"value":1534}," No policy claims to restrict ",{"type":21,"tag":48,"props":1536,"children":1538},{"className":1537},[],[1539],{"type":27,"value":281},{"type":27,"value":1541}," or ",{"type":21,"tag":48,"props":1543,"children":1545},{"className":1544},[],[1546],{"type":27,"value":485},{"type":27,"value":1548}," by ",{"type":21,"tag":48,"props":1550,"children":1552},{"className":1551},[],[1553],{"type":27,"value":464},{"type":27,"value":1555}," — AWS does not evaluate it there.",{"type":21,"tag":34,"props":1557,"children":1559},{"className":1558},[1476],[1560,1563],{"type":21,"tag":1479,"props":1561,"children":1562},{"disabled":1481,"type":1482},[],{"type":27,"value":1564}," The bucket policy is measured in bytes, and someone knows how many tenants remain before 20 KB.",{"type":21,"tag":34,"props":1566,"children":1568},{"className":1567},[1476],[1569,1572],{"type":21,"tag":1479,"props":1570,"children":1571},{"disabled":1481,"type":1482},[],{"type":27,"value":1573}," A listing from a tenant principal shows filenames only for prefixes that tenant may see.",{"type":21,"tag":34,"props":1575,"children":1577},{"className":1576},[1476],[1578,1581],{"type":21,"tag":1479,"props":1579,"children":1580},{"disabled":1481,"type":1482},[],{"type":27,"value":1582}," A delete attempt from a tenant principal is tested, not assumed.",{"type":21,"tag":34,"props":1584,"children":1586},{"className":1585},[1476],[1587,1590],{"type":21,"tag":1479,"props":1588,"children":1589},{"disabled":1481,"type":1482},[],{"type":27,"value":1591}," The chosen provider implements object tagging: verified for R2, directory buckets and any non-AWS endpoint before the design is approved.",{"type":21,"tag":137,"props":1593,"children":1594},{},[1595,1597,1604,1606,1618],{"type":27,"value":1596},"Prices and limits checked on 3 November 2026 against the AWS S3 documentation and pricing pages, the AWS ",{"type":21,"tag":164,"props":1598,"children":1601},{"href":1599,"rel":1600},"https:\u002F\u002Faws.amazon.com\u002Fabout-aws\u002Fwhats-new\u002F2025\u002F03\u002Famazon-s3-reduces-pricing-object-tagging\u002F",[168],[1602],{"type":27,"value":1603},"tagging pricing announcement",{"type":27,"value":1605}," ($0.0065 per 10,000 tags per month), the S3 ",{"type":21,"tag":164,"props":1607,"children":1610},{"href":1608,"rel":1609},"https:\u002F\u002Fdocs.aws.amazon.com\u002FAmazonS3\u002Flatest\u002FAPI\u002FAPI_CopyObject.html",[168],[1611,1613],{"type":27,"value":1612},"API reference for ",{"type":21,"tag":48,"props":1614,"children":1616},{"className":1615},[],[1617],{"type":27,"value":555},{"type":27,"value":1619},", and the Cloudflare R2 compatibility matrix. Request prices are the us-east-1 list rates from the S3 pricing page.",{"type":21,"tag":1621,"props":1622,"children":1623},"style",{},[1624],{"type":27,"value":1625},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":7,"searchDepth":782,"depth":782,"links":1627},[1628,1629,1630,1631,1632,1633],{"id":24,"depth":782,"text":28},{"id":132,"depth":782,"text":135},{"id":389,"depth":782,"text":392},{"id":701,"depth":782,"text":704},{"id":1417,"depth":782,"text":1420},{"id":1464,"depth":782,"text":1467},"markdown","content:blog:abac-object-tags-vs-prefix-policies.md","content","blog\u002Fabac-object-tags-vs-prefix-policies.md","blog\u002Fabac-object-tags-vs-prefix-policies","md",1793704322769]