# Who can read `adr-intake-submissions` — verification extract **Why this file exists.** `/legal/privacy/` makes a statement to the public about who can see the contents of the intake table. `AGENTS.md` §4 admits no factual claim that cannot be traced, and `CLAUDE.md`'s R14 rule is that anything a spec claims about must be reachable from the repository — *"if the artefact lives only in a console, no reviewer can compare the claim against it and the claim is unverifiable by construction."* Until this file existed, that sentence was the one claim on the site whose subject was entirely outside the repo. Raised by `claims-auditor` in the D20 cutover audit, 2026-09-01, finding 8. **Provenance.** Figures in the ORIGINAL sections were read from AWS on **2026-09-01**; everything in the **2026-09-02 addendum** at the foot of this file was read on 2026-09-02, and it supersedes the original role screen. Both were run read-only as `arn:aws:iam::327082975128:user/pouya` with the commands listed at the end. No command in this file creates or changes anything. Re-run them rather than trusting this file; it is dated for that reason. --- ## ✅ RULED AND APPLIED — 2026-09-02 **Pouya ruled `state the truth`, not `remove the access`** (§9 Q62, ruled 2026-09-01, applied 2026-09-02). Option 2 below is the one taken; option 1 was declined. `lars`'s membership of `admins` is **unchanged**. ⚠️ **AND THEN RULED AGAIN THE SAME DAY — §9 Q63, and the second ruling is the one this file most needs to carry, because THIS FILE SUPPLIED THE ERROR.** The first shipped sentence was *"Two people can"*, and it was read straight off the enumeration below. **Pouya's attestation, 2026-09-02:** *"two people is an exaggeration… a handful is accurate — the simulation counts identities, not humans, and the two are not the same claim."* **The enumeration is exhaustive and the inference off it was not.** Every read path terminates at `user/pouya` or `user/lars`; how many **people** can reach those two credentials is not something `simulate-principal-policy` can see, so the identity count is a **lower bound on people** and the page published it as an exact count. **No numeric human headcount may ship.** The identity counts in this file are unaffected and stay exactly as measured — this is a correction to what may be *concluded* from them, not to any of them. ⚠️ **AND RULED A THIRD TIME, LATER THE SAME DAY: THE PAGE STATES WHO, AND THIS FILE HOLDS THE METHOD.** Pouya, 2026-09-02: *"the page stays generic. It over-explains technical mechanics that belong in the evidence file, not in front of an inquirer."* §Who can see it is now **four short statements**. **Deleted from the page:** the measurement paragraph, the root-credential sentence, the single-sign-on and federated-login enumeration, the resource-policy clause, the *"company that runs a database"* aside, the deploy-credential sentence and the three-copies summary. ⚠️ **THE SHARED-ACCOUNT CLAUSE WAS CUT WITH THEM AND THEN RESTORED — to §Where it is stored, where it belongs.** It is a storage disclosure rather than mechanics, the ruling did not name it, and without it no page told a reader their intake sits in an account that also runs unrelated systems (`adversarial-reviewer`, round 1). **These lists must stay identical — there were four of them and they named four different sets.** None of that was retracted and none of it is lost — it is all still below, unchanged, and **that is now this file's job rather than a supporting role.** ⚠️ **THE PAGE NO LONGER CITES THIS FILE'S CONTENT, SO THE COMPARISON BELOW IS THE ONLY THING TYING THE TWO TOGETHER. Keep it in sync, and do not restore a deleted sentence to the page on the strength of finding it here** — the section comment in `src/pages/legal/privacy.astro` carries the same bar. **The shipped sentences as at 2026-09-02**, so this file can be compared against the live page rather than against a struck one. All four, in order, complete: > The record in the table: me, and the small number of people who administer the > account it sits in with me. > The system that receives what you send **can only add a record — it cannot read > back what is stored.** > The notification goes to the practice's mailbox, which is read by me and by > administrative staff and is hosted on Google Workspace — so Google holds a copy > of whatever you send me. > The confirmation that went to you sits with whoever runs your email. That copy > is in your hands rather than mine. **What each rests on, because that mapping is the reason this file exists.** Sentence 1: the enumeration below, plus Pouya's *"a handful"* attestation for the human quantifier — **the measurement gives administrators, the attestation gives the number, and neither gives the other.** Sentence 2: `adr-intake-lambda-role` holds `PutItem` only, implicitDeny on all six read and modify actions. Sentence 3: **an attestation, not a measurement** — `AGENTS.md` §7's `info@smlcompany.ca` row; nothing in this repository or in AWS can check it. Sentence 4: the handler's second `SendEmailCommand`. **Three things the page deliberately does NOT say, and each was deleted by ruling rather than being unsupported.** The **root credential** (§7 records it as held by Pouya with no access key and MFA on — ⚠️ *held*, not *held only*, which is why publishing it needed a question and why §9 Q64 is closed as **moot** and not as answered). The **single-sign-on and resource-policy findings**. And the **`33`** — deliberately withheld even while the paragraph stood, because a role total moves when AWS creates a service-linked role by itself. **The wording approval Pouya reserved is discharged by the read-through** — his ruling, 2026-09-02: *"do not hold anything open waiting on a separate wording approval; the read-through is the approval."* Q63(a) had approved a version, and the version changed twice after it. ⚠️ **AND THE ENUMERATION BELOW WAS NOT ENOUGH TO SUPPORT THE COMPLETENESS CLAIM — *"every user and every role in the account was simulated against this table"*, which §7 still asserts and the page no longer carries. See the addendum at the foot of this file**, which is what that claim actually rests on. Read it before citing the five-row table. *(This pointer said "the second of those sentences" until 2026-09-02: it indexed the quote block by POSITION, and the block changed length under it. Name the claim, not its ordinal.)* --- ## The claim being checked — ⚠️ STRUCK 2026-09-01, QUOTED HERE AS THE DEFECT This is **no longer on the page.** It is kept because it is the string `check-claims.mjs`'s `sole-administrator-q62` pattern permanently bars, and a tripwire whose target is not recorded anywhere becomes unmaintainable. `src/pages/legal/privacy.astro`, §Who can see it, **as it stood at `bd282aa`**: > The table is reachable by the function that writes to it and by one > administrative account, which is mine — nobody else has access to the table. > There is no team, no assistant and no external administrator. ## The finding: it is false The account has an IAM group **`admins`** carrying the AWS managed policy **`AdministratorAccess`**, and it has **two members: `pouya` and `lars`.** `iam simulate-principal-policy` for `dynamodb:GetItem`, `dynamodb:Query` and `dynamodb:Scan` against `arn:aws:dynamodb:ca-central-1:327082975128:table/adr-intake-submissions`, across all five IAM users in the account: | principal | GetItem | Query | Scan | |---|---|---|---| | `user/pouya` | **allowed** | **allowed** | **allowed** | | `user/lars` | **allowed** | **allowed** | **allowed** | | `user/adr-sml-deploy` | implicitDeny | implicitDeny | implicitDeny | | `user/gitea-deploy-meshkinilaw` | implicitDeny | implicitDeny | implicitDeny | | `user/meshkini-backend-deploy` | implicitDeny | implicitDeny | implicitDeny | `lars` holds exactly the access `pouya` holds, by the same route: membership of `admins`. The user's own attachments are only `IAMUserChangePassword`, so the group is the whole of it. So the published sentence is wrong on both of its halves — a second account has access, and it belongs to a second administrator of a shared account. ## The rest of the surface, recorded so the check is complete rather than partial - **5 IAM users**: `adr-sml-deploy`, `gitea-deploy-meshkinilaw`, `lars`, `meshkini-backend-deploy`, `pouya`. The three deploy users are all `implicitDeny` above. `adr-sml-deploy`'s scope is S3 + CloudFront and touches no table (`docs/reference/deploy-credential-verification.md`). - **1 IAM group**: `admins` — `AdministratorAccess` and `Billing`, two members. - **33 IAM roles**, 26 of them not service-linked. Two carry `AdministratorAccess`: `cdk-hnb659fds-cfn-exec-role-327082975128-ca-central-1` and `…-us-east-1`. These are **AWS CDK bootstrap CloudFormation execution roles**, assumable by CloudFormation for stack deployment. They are a real path to the table for anyone who can deploy a CDK stack in this account — which is the two administrators above — rather than a third party. - **`adr-intake-lambda-role`** is the writing principal: `dynamodb:PutItem` on this table, `ses:SendEmail`/`SendRawEmail`, plus `AWSLambdaBasicExecutionRole`. **`PutItem` only — it cannot read the table**, which is worth stating because it is a stronger fact than the page currently claims and it is the part of the sentence that is true. - **No resource-based policy on the table.** DynamoDB supports one; this table has none, so access is governed entirely by identity policies. - The account is **not single-project** (`AGENTS.md` §10). `lars` and the two `meshkini*`/`gitea*` users are evidence of that on the IAM surface, not just in the S3 bucket listing §10 describes. ## What had to happen before `/legal/privacy/` went public — ✅ RESOLVED BY OPTION 2 Tracked as `AGENTS.md` §9 **Q62**, **closed 2026-09-02 on option 2.** Kept unstruck because the reasoning is what makes the ruling re-readable, and because option 1 remains live in one direction: if that access is ever actually removed, the page and the tripwire both have to change, and `check-claims.mjs`'s `rule:` line carries that instruction. 1. **Remove the access** — take `lars` out of `admins`, or replace that membership with a policy that denies DynamoDB on this table — and then this sentence becomes true. Note the likely collision: `AGENTS.md` Q23 records the Gitea instance as *jointly administered* and blocked on *"its second administrator"*, so this account is probably not the only thing that access is for. 2. **Correct the sentence** to what is true. It is a privacy policy, so the honest version is short and specific — the number of people with administrative access, and that the function that writes cannot read. **Do not resolve it by softening.** "Access is limited to authorised administrators" is the shape §4 exists to bar: defensible, uninformative, and it would replace a false specific with a true vacancy on the one page where a reader is entitled to the specific. --- ## Commands Run as `user/pouya`, `ca-central-1`, all read-only. Exit status read on each; no stderr suppressed anywhere. ```bash aws iam list-users --query 'Users[].UserName' aws iam list-groups --query 'Groups[].GroupName' aws iam get-group --group-name admins --query 'Users[].UserName' aws iam list-attached-group-policies --group-name admins --query 'AttachedPolicies[].PolicyName' aws iam list-group-policies --group-name admins --query 'PolicyNames' aws iam list-groups-for-user --user-name lars --query 'Groups[].GroupName' aws iam list-attached-user-policies --user-name lars --query 'AttachedPolicies[].PolicyName' aws iam list-user-policies --user-name lars --query 'PolicyNames' TARN="arn:aws:dynamodb:ca-central-1:327082975128:table/adr-intake-submissions" for U in pouya lars adr-sml-deploy gitea-deploy-meshkinilaw meshkini-backend-deploy; do aws iam simulate-principal-policy \ --policy-source-arn "arn:aws:iam::327082975128:user/${U}" \ --action-names dynamodb:GetItem dynamodb:Query dynamodb:Scan \ --resource-arns "$TARN" \ --query 'EvaluationResults[].{A:EvalActionName,D:EvalDecision}' --output text done # Roles: 33 total, 26 non-service-linked; screened for broad policies. for R in $(aws iam list-roles --query 'Roles[].RoleName' --output text \ | tr '\t' '\n' | grep -v '^AWSServiceRole'); do aws iam list-attached-role-policies --role-name "$R" \ --query 'AttachedPolicies[].PolicyName' --output text done aws iam get-role-policy --role-name adr-intake-lambda-role \ --policy-name adr-intake-lambda-inline --query PolicyDocument aws iam list-attached-role-policies --role-name adr-intake-lambda-role ``` ⚠️ **`simulate-principal-policy` was the tool that produced a false negative on this project once already** — `AGENTS.md` Q22, where eight checks returned empty because `2>/dev/null` was hiding an `InvalidInput` error caused by a zsh parameter-expansion bug (`$ACCT:user/` parses `:u` as a history modifier). The loop above brace-quotes `${U}` for that reason, prints one line per principal so a silently-skipped iteration is visible as a missing row, and suppresses nothing. **Five rows, or the run did not happen.** --- ## ⚠️ ADDENDUM 2026-09-02 — THE ENUMERATION ABOVE WAS INCOMPLETE IN THREE WAYS, AND ITS CONCLUSION SURVIVES ANYWAY **Why this addendum exists.** `/legal/privacy/` published a completeness claim about who can read this table — *"every user and every role in the account was simulated against this table"* — and, written as it stood, this file did not support it. ⚠️ **THAT SENTENCE IS NO LONGER ON THE PAGE**: Pouya ruled later the same day that the section states who and not how, and the measurement paragraph was deleted (see **The shipped sentences** at the top of this file, which is now four statements and does not include it). **This addendum is not thereby obsolete — it is now load-bearing in a different place.** `AGENTS.md` §7's `Intake table — who can read it` row still asserts the completeness, `docs/06` and §12 **R21** still instruct an operator to re-run it before cutover, and the page's first sentence still rests on its conclusion even though it no longer recites the method. **A claim moved off a public page into a register is still a claim.** *(This paragraph quoted a different sentence until 2026-09-02 — round 1's **pre-fix** wording, *"every account and role in this infrastructure…"*, which the audit then struck. So this file briefly quoted two different sentences as the live one, in the two halves of the same document, which defeats the comparison R14 exists for. Fixed by pointing at the block above rather than re-quoting it: one copy of a fact, in one place. `adversarial-reviewer`, round 2.)* Under R14 the artefact is what a reviewer compares the claim against, so a claim stronger than its artefact is unverifiable by construction even when it happens to be true. **What was missing.** 1. **`aws iam list-role-policies` was never run.** The command block above lists only `list-attached-role-policies`, which returns *managed* policies. **23 of the 26 non-service-linked roles carry inline policies**, and none of them had been read. The screen for "broad policies" could not have seen an inline grant. 2. **No role was ever simulated against the table.** Access was inferred from policy *names* (`AdministratorAccess`) rather than measured as a decision. 3. **The five users were simulated for reads only** — `GetItem`, `Query`, `Scan`. So the page's *(then-shipped; deleted by the mechanics ruling of 2026-09-02 and now §7's alone)* *"the credential that publishes this website has no access to the table at all"* covered three read actions and said "at all". **What the measurement found, and it changes the role count.** Simulating all 26 non-service-linked roles across **seven** actions — `GetItem`, `Query`, `Scan`, `BatchGetItem`, `PutItem`, `UpdateItem`, `DeleteItem`: | role | decision on the table | trust | |---|---|---| | `cdk-hnb659fds-cfn-exec-role-…-ca-central-1` | **allowed on all 7** | `cloudformation.amazonaws.com` only | | `cdk-hnb659fds-cfn-exec-role-…-us-east-1` | **allowed on all 7** | `cloudformation.amazonaws.com` only | | `cdk-hnb659fds-lookup-role-…-ca-central-1` | **allowed on the 4 READS**, denied on writes | `arn:aws:iam::327082975128:root` | | `cdk-hnb659fds-lookup-role-…-us-east-1` | **allowed on the 4 READS**, denied on writes | `arn:aws:iam::327082975128:root` | | `adr-intake-lambda-role` | `PutItem` **only**; implicitDeny on the other six | `lambda.amazonaws.com` | | the other 21 | implicitDeny on all 7 | — | **So FOUR roles can read the table, not the two this file recorded.** The two `lookup` roles were missed by exactly the gap above: their grant is the inline `LookupRolePolicy`, and `list-attached-role-policies` returns nothing for them. **Why the published sentence is nevertheless correct.** The question is not how many roles exist but which *people* they lead back to. - The two **cfn-exec** roles trust `cloudformation.amazonaws.com` and nothing else. No human can assume them; they are reachable only by deploying a CloudFormation/CDK stack, which requires a principal who can deploy one. - The two **lookup** roles trust the account root, which delegates the decision to the caller's own identity policy. Simulated for `sts:AssumeRole` against both role ARNs, for all five users, reading `ResourceSpecificResults` (ten per-resource decisions, not five — `EvaluationResults` is one entry per **action**, and an earlier pass here asserted the wrong count): | principal | assume `lookup-…-ca-central-1` | assume `lookup-…-us-east-1` | |---|---|---| | `user/pouya` | **allowed** | **allowed** | | `user/lars` | **allowed** | **allowed** | | `user/adr-sml-deploy` | implicitDeny | implicitDeny | | `user/gitea-deploy-meshkinilaw` | implicitDeny | implicitDeny | | `user/meshkini-backend-deploy` | implicitDeny | implicitDeny | - The **CloudFormation escalation path this file named and left untested** — *"a real path to the table for anyone who can deploy a CDK stack"* — is now measured. Simulated for `cloudformation:CreateStack`, `UpdateStack`, `CreateChangeSet`, `ExecuteChangeSet`, `iam:PassRole` and `sts:AssumeRole`, five users × six actions = **30 decisions**, count asserted: | principal | the CDK / CloudFormation path | |---|---| | `user/pouya` | **allowed** on all six | | `user/lars` | **allowed** on all six | | `user/adr-sml-deploy` | implicitDeny on all six | | `user/gitea-deploy-meshkinilaw` | implicitDeny on all six | | `user/meshkini-backend-deploy` | implicitDeny on all six | This is the finding that mattered most, because `meshkini-backend-deploy` is by its name another project's backend-deploy credential and `gitea-deploy-meshkinilaw` is held on a **jointly administered** Gitea instance (Q23). Either one, had it been able to drive CloudFormation, would have read the table without appearing in the five-row table above — and *"Two people can"* would have been wrong. Neither can. - **And the seven roles every sweep here had excluded BY CONSTRUCTION are now measured too.** Every role loop in this file filters `grep -v '^AWSServiceRole'`, and the assertion was written as *"twenty-six role rows"* — so a service-linked role was outside the claim rather than inside it, which matters because a service-linked role for a backup or migration service can read table contents. All **7** (`APIGateway`, `CloudFrontLogger`, `InternetMonitor`, `RDS`, `ResourceExplorer`, `Support`, `TrustedAdvisor`) are **implicitDeny on all seven actions** — 49 decisions, count asserted `[verified 2026-09-02]`. **So the enumeration is 33 of 33 roles, not 26 of 33**, and the page's *"every user and every role in the account"* is now literally true. Raised by `adversarial-reviewer`, round 2. - **There is no federated identity surface at all**: `list-saml-providers` **0**, `list-open-id-connect-providers` **0**, `sso-admin list-instances` **0** `[verified 2026-09-02]`. So "every user and every role" is not leaving out a federated principal, because there is none to leave out. - ⚠️ **AND THE `sso-admin` ZERO NEEDED A SECOND COMMAND TO MEAN ANYTHING, added 2026-09-02 (round 2).** `list-instances` answers about the account it is called in, so a **member of an AWS Organization returns 0 while Identity Center runs in the management account** — the zero would have been true and the conclusion false. `aws organizations describe-organization` returns **`AWSOrganizationsNotInUseException`: "Your account is not a member of an organization"** `[verified 2026-09-02]`, so there is no management account above this one and the zero is conclusive. Command 8. - **The table carries NO RESOURCE-BASED POLICY OF ITS OWN**, and this is now a command rather than an assertion. `aws dynamodb get-resource-policy` returns **`PolicyNotFoundException`** `[verified 2026-09-02]` — exit **254**, the error on stderr being the result. ⚠️ **A DynamoDB resource policy is invisible to `describe-table`**, so no earlier command in this file could have seen one, and `/legal/privacy/` publishes the claim (*"the table carries no policy of its own granting access to anyone"*). It is the identity-policy enumeration's blind spot: every simulation here asks what a **principal** may do, and a resource policy grants from the other side. Found unbacked by `adversarial-reviewer` round 2, on §10's own precedent — the client-backup bucket, where `get-bucket-policy` returning `NoSuchBucketPolicy` was recorded because *"a policy read alone could not have established the second half."* Command 7. Every read path therefore terminates at `pouya` or `lars`. ⚠️ **AND THAT IS WHERE THIS FILE WENT WRONG, SO THE CORRECTION SITS AT THE SENTENCE THAT CAUSED IT.** This read *"The count of people is two, and it is now the result of an enumeration rather than of a policy name"* until 2026-09-02, and `/legal/privacy/` published that count. **It is a count of IDENTITIES.** Two credentials is a lower bound on the number of people who can use them, and Pouya's attestation is that *"a handful"* is the true figure (§9 Q63). The enumeration stands exactly as measured; **an enumeration of principals is not a census.** **The account root user, recorded because an enumeration that quietly omits it is not an enumeration.** Root is not an IAM user and does not appear in `list-users`, so it cannot be simulated and no policy constrains it — root can always read the table. Two facts bound it: `get-account-summary` reports `AccountAccessKeysPresent: 0`, so **there is no programmatic root credential**, and `AccountMFAEnabled: 1`. Root access therefore requires the root password and its MFA device. ⚠️ **THE PAGE SAYS NOTHING ABOUT ROOT, AND THIS PASSAGE HAS NOW BEEN THE REASON FOR THAT TWICE ON OPPOSITE GROUNDS.** It first read *"The page does not mention root and should not"*, reasoned from its own last clause — *"who holds the root credentials is not established in this repository."* **Pouya then established it (§9 Q63(c), 2026-09-02): he holds it** `[verified 2026-09-02 — Pouya]`, the page published *"has no programmatic key, and I hold it"*, and the gap that opened immediately was that *held by* is not *held only by* — a reader takes the possessive as sole custody, which nothing measured or attested supports (§9 **Q64**). **His second ruling that day deleted the sentence** along with the rest of the mechanics, so **Q64 is closed as MOOT rather than answered and the underlying fact is exactly as unestablished as it was.** **The consequence to carry, because it is not "nothing happened":** root custody is now recorded in `AGENTS.md` §7 and nowhere public. ⚠️ **Nothing about it may be published without asking him again**, and the question to ask is not *who holds root* — that is answered — but *whether anyone else does*. The original reasoning still holds and is why the page's first sentence is scoped as it is: an account owner's own credential is inherent to every cloud account and is not a third party who has been *granted* access, which is why the measured claim was always scoped to *"every user and every role"* rather than to a bare "nobody else can". **Two claims are supported that were not before. One of them still ships; the other was deleted from the page by ruling on 2026-09-02 and is kept here because it remains true and remains §7's.** - **SHIPS** — *"The system that receives what you send can only add a record — it cannot read back what is stored"* — `adr-intake-lambda-role` returns `allowed` for `PutItem` and `implicitDeny` for `GetItem`, `Query`, `Scan`, `BatchGetItem`, `UpdateItem` and `DeleteItem`. Previously this rested on reading the policy document; it is now the simulator's decision. - **NO LONGER ON THE PAGE** — *"the credential that publishes this website has no access to the table at all"* — `adr-sml-deploy` is `implicitDeny` on all **seven**, so "at all" covers writes and deletes as well as reads. It went with the mechanics cut, not because anything about it changed. **And it corroborates §10 from the IAM surface.** Of the 26 non-service-linked roles, **9 belong to CDK bootstrap** and **14 to four unrelated production systems** in the same account. *(`/legal/privacy/` tells a reader this in as many words — *"The table sits in an Amazon Web Services account that also runs systems unrelated to this practice"* — in **§Where it is stored**, which is where the sentence now lives: the mechanics cut removed it and it was restored there, as a storage disclosure rather than a method. §10 is unaffected either way; it never depended on the page saying so.)* *(Their role names were listed here until 2026-09-02 and are not any more: this is a committed file, they are another project's IAM surface, and the count carries the whole of the argument. `adversarial-reviewer`, round 2.)* ⚠️ **THE INSTRUMENT FAILED FIRST, UNIFORMLY, AND IN THE DIRECTION THAT READS AS CLEAN.** The role sweep was first run as `--action-names $ACTS` with the seven actions in a shell variable. **zsh does not word-split parameter expansions**, so `simulate-principal-policy` received **one** action name — the whole string — and answered it: `implicitDeny` for 22 roles, and `allowed` for the four with a `*` grant, because `*` matches a bogus action too. Twenty-two clean rows and a plausible four. The tell was the shape of the output, not the verdict: one decision per role where there should have been seven. **The fix is the assertion, not the memory** — the loop now counts `EvaluationResults` per call and refuses a row that does not carry exactly seven, and the users' assume check counts `ResourceSpecificResults` and refuses a row that does not carry exactly two. `CLAUDE.md` records this class five times over; this is the sixth, and it is the "uniformly good" half. ### Commands — the ones this addendum rests on Read-only, run as `user/pouya` in `ca-central-1`. No stderr suppressed, exit status read on every call, and note the **literal** action lists: they are not in a variable, which is the whole point above. ```bash # 1. Inline policies — the command the original block never ran. # ⚠️ THE `grep -v` IS THE ORIGINAL DEFECT AND IS KEPT ONLY TO SHOW IT. It # excluded the 7 service-linked roles from a claim written as "every role", and a # service-linked role for a backup or migration service CAN read table contents. # For a full run, DELETE the grep -v — all 33 must be screened, which is what the # 2026-09-02 addendum measured and what the page's "every user and every role" # now rests on. aws iam list-roles --query 'Roles[].RoleName' --output text \ | tr '\t' '\n' \ | while IFS= read -r R; do aws iam list-role-policies --role-name "$R" --query 'PolicyNames' --output text done # 2. Every non-service-linked role, seven actions, decision asserted per row. # The count check is what makes a broken call loud instead of clean. aws iam simulate-principal-policy \ --policy-source-arn "arn:aws:iam::327082975128:role/" \ --action-names dynamodb:GetItem dynamodb:Query dynamodb:Scan \ dynamodb:BatchGetItem dynamodb:PutItem dynamodb:UpdateItem \ dynamodb:DeleteItem \ --resource-arns "arn:aws:dynamodb:ca-central-1:327082975128:table/adr-intake-submissions" \ --query 'length(EvaluationResults)' --output text # must print 7 # 3. Trust policies of the four roles that can read. aws iam get-role --role-name --query 'Role.AssumeRolePolicyDocument' # 4. Who can assume the two lookup roles — per RESOURCE, not per action. aws iam simulate-principal-policy \ --policy-source-arn "arn:aws:iam::327082975128:user/" \ --action-names sts:AssumeRole \ --resource-arns "arn:aws:iam::327082975128:role/cdk-hnb659fds-lookup-role-327082975128-ca-central-1" \ "arn:aws:iam::327082975128:role/cdk-hnb659fds-lookup-role-327082975128-us-east-1" \ --query 'EvaluationResults[].ResourceSpecificResults[].{R:EvalResourceName,D:EvalResourceDecision}' \ --output text # must print 2 rows ``` ```bash # 5. The CloudFormation / CDK escalation path, per user. Six actions, literal. aws iam simulate-principal-policy \ --policy-source-arn "arn:aws:iam::327082975128:user/" \ --action-names cloudformation:CreateStack cloudformation:UpdateStack \ cloudformation:CreateChangeSet cloudformation:ExecuteChangeSet \ iam:PassRole sts:AssumeRole \ --query 'EvaluationResults[].{A:EvalActionName,D:EvalDecision}' --output text # 6. Federated identity surfaces, and root. aws iam list-saml-providers --query 'length(SAMLProviderList)' aws iam list-open-id-connect-providers --query 'length(OpenIDConnectProviderList)' aws sso-admin list-instances --query 'length(Instances)' aws iam get-account-summary \ --query 'SummaryMap.{AccessKeysPresentRoot:AccountAccessKeysPresent,MFA:AccountMFAEnabled}' # 7. The table's OWN policy. ⚠️ NOT VISIBLE IN describe-table — a DynamoDB # resource policy needs its own call, and the page publishes a claim about it. # A "no policy" answer arrives as a NON-ZERO EXIT with PolicyNotFoundException # on stderr, so do not suppress stderr and do not read exit 0 as the result. aws dynamodb get-resource-policy \ --resource-arn 'arn:aws:dynamodb:ca-central-1:327082975128:table/adr-intake-submissions' # 8. Is the account in an AWS Organization? ⚠️ THIS IS WHAT MAKES COMMAND 6's # sso-admin ZERO CONCLUSIVE. `list-instances` answers about THIS account, so a # member account returns 0 while Identity Center runs in the management # account. Not-in-an-org means there is no such management account. aws organizations describe-organization ``` **Five user rows and THIRTY-THREE role rows, or the run did not happen.** ⚠️ **This said "twenty-six" until 2026-09-02, which made an INCOMPLETE sweep pass its own acceptance test** — the number matched command 1's `grep -v '^AWSServiceRole'`, so an operator following §12 R21's instruction to re-run this file would have reproduced the exclusion and got the pass line for it. The page's *"every user and every role"* rests on 33 of 33. `adversarial-reviewer`, round 2. And for every simulation: **seven decisions per role call, six per CDK-path call, two per-resource decisions per assume call** — the counts are the assertion, because a call that silently received one bogus action name answers `implicitDeny` and reads exactly like a clean row. **Commands 7 and 8 are read by their ERROR, not their output**: `PolicyNotFoundException` and `AWSOrganizationsNotInUseException` are each the clean result, arriving on stderr with a non-zero exit.