Pouya's rulings, 2026-09-02.
Q63(a) — wording approved with two trims: the editorial closing sentence is
struck, and the mailbox clause is rewritten per (b).
Q63(b) — info@smlcompany.ca is a delegated mailbox read by Pouya and by
administrative staff. The page said "anyone who can reach that mailbox"; it now
says who. The answer reached four sentences, not the one the ruling named, in
three sections: "my mailbox" had been written as a personal one throughout.
Q63(c) — the account root credential is held by Pouya, has no programmatic key
and carries MFA. The page now states the first two.
And the answer to (c) took the human headcount off the page. His attestation:
"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 was
exhaustive and the inference off it was not: two IAM identities is a LOWER BOUND
on people and was published as an exact count. The page now attributes read
access to "the account's administrators — me, and the small number of people who
administer it with me", and the false inference is corrected at its source in
docs/reference/intake-table-access-verification.md as well as on the page.
R10 — CLOSED on a fresh one-line confirmation, not on the 2026-08-28 stamp.
ADRIC, ADRIO, the three OBA sections and the CTF all current. Re-stamped on all
four stamp-bearing sites; the row stays live, because its trigger is an event.
/med-arb/ — ratified as shipped, no credential line restored. His note for the
record: med-arb is a service he provides, not a designation.
The tripwire is unchanged and proven both ways: exit 0 on the revised page,
exit 1 with 5 matches on the pre-correction bytes rebuilt from bd282aa.
A sentence added to back the correction had no command behind it. "The table
carries no policy of its own granting access to anyone" was published with
nothing in the repo establishing it — a DynamoDB resource policy is invisible to
describe-table, and every simulation on file asks what a principal may do.
Measured rather than deleted: get-resource-policy returns PolicyNotFoundException,
and describe-organization returns AWSOrganizationsNotInUseException, which is
what makes the Identity Center zero conclusive rather than merely local. Both
recorded as commands 7 and 8; R21 gains claim (v) and two falsifiers.
Q64 OPENED, with a TODO(pouya) and an unticked cutover item: "I hold it" is true
whether or not a second person holds it, and reads as sole custody one paragraph
below "the small number of people who administer it with me". The request to
strike the possessive now is declined with a reason — he dictated the clause —
and the page cannot ship while the TODO stands.
Two review rounds, 22 findings, 21 resolved, 1 declined. Nine of round 2's twelve
were defects in round 1's own repairs. Stopped at two per D19.
Gates, exit status read: check 0 · build 0 (23 pages) · check:claims 0 ·
og:proof 0 · check:intake 0 · lint 0 · router.test 0 (30/30) · lighthouse 0,
worst of 23 99/100/100/100, /legal/privacy/ 100/100/100.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Md3GndFqWPzK78xAoebsg5
28 KiB
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.
The shipped sentences as at 2026-09-02, so this file can be compared against the live page rather than against a struck one:
The account's administrators can — me, and the small number of people who administer it with me. The table sits in an Amazon Web Services account that also runs systems unrelated to this practice, and administrative access to that account carries the ability to read the table. That is who can read the stored record; who reads the notification email is a separate question, answered in the last paragraph of this section.
The access itself is measured rather than assumed: every user and every role in the account was simulated against this table, and every identity that comes back able to read it is reachable only by those administrators. The account has no single sign-on and no federated login configured, and the table carries no policy of its own granting access to anyone. The account's root credential — the one path no policy constrains — has no programmatic key, and I hold it.
The function that receives the form can only add a record — it cannot read the table back. And the credential that publishes this website has no access to the table at all, for reading or for writing.
The wording was approved by Pouya on 2026-09-02 with two trims (§9 Q63(a)) —
the editorial closing sentence struck, and the mailbox clause rewritten because
info@smlcompany.ca is a delegated mailbox read by Pouya and administrative
staff, not a personal one (Q63(b); the fact is in AGENTS.md §7). 33 was
deliberately not published: a role total moves when AWS creates a service-linked
role by itself, and "every user and every role in the account" carries the
exhaustiveness without putting a second self-staling number on a legal page.
⚠️ AND THE ENUMERATION BELOW WAS NOT ENOUGH TO SUPPORT THE SECOND OF THOSE SENTENCES. See the addendum at the foot of this file, which is what the page actually rests on. Read it before citing the five-row table.
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 allimplicitDenyabove.adr-sml-deploy's scope is S3 + CloudFront and touches no table (docs/reference/deploy-credential-verification.md). - 1 IAM group:
admins—AdministratorAccessandBilling, two members. - 33 IAM roles, 26 of them not service-linked. Two carry
AdministratorAccess:cdk-hnb659fds-cfn-exec-role-327082975128-ca-central-1and…-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-roleis the writing principal:dynamodb:PutItemon this table,ses:SendEmail/SendRawEmail, plusAWSLambdaBasicExecutionRole.PutItemonly — 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).larsand the twomeshkini*/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.
- Remove the access — take
larsout ofadmins, or replace that membership with a policy that denies DynamoDB on this table — and then this sentence becomes true. Note the likely collision:AGENTS.mdQ23 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. - 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.
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/ publishes a completeness claim
about who can read this table — the second of the three sentences quoted at the
top of this file, under The shipped sentences. Written as it stood, this file
did not support it.
(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.
aws iam list-role-policieswas never run. The command block above lists onlylist-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.- No role was ever simulated against the table. Access was inferred from
policy names (
AdministratorAccess) rather than measured as a decision. - The five users were simulated for reads only —
GetItem,Query,Scan. So the page's "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.comand 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:AssumeRoleagainst both role ARNs, for all five users, readingResourceSpecificResults(ten per-resource decisions, not five —EvaluationResultsis 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:PassRoleandsts: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 byadversarial-reviewer, round 2. -
There is no federated identity surface at all:
list-saml-providers0,list-open-id-connect-providers0,sso-admin list-instances0[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-adminZERO NEEDED A SECOND COMMAND TO MEAN ANYTHING, added 2026-09-02 (round 2).list-instancesanswers 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-organizationreturnsAWSOrganizationsNotInUseException: "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-policyreturnsPolicyNotFoundException[verified 2026-09-02]— exit 254, the error on stderr being the result. ⚠️ A DynamoDB resource policy is invisible todescribe-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 byadversarial-reviewerround 2, on §10's own precedent — the client-backup bucket, whereget-bucket-policyreturningNoSuchBucketPolicywas 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.
⚠️ THIS PASSAGE SAID "The page does not mention root and should not", AND
THAT JUDGEMENT IS SUPERSEDED — §9 Q63(c), 2026-09-02. It was reasoned from its
own last clause: "Who holds the root credentials is not established in this
repository." Pouya established it on 2026-09-02: he holds it
[verified 2026-09-02 — Pouya]. With the holder known, mentioning root
strengthens the paragraph rather than opening a hole in it — it closes the
one path a careful reader would ask about after being told every user and every
role was simulated. The page now states that the root credential has no
programmatic key and that he holds it; it does not state a headcount for it, and
AGENTS.md §7 carries the fact with §12 R21's trigger on it. ⚠️ AND THE
ATTESTATION IS held by Pouya, NOT held ONLY by Pouya — §9 Q64 is open on
exactly that gap. Nothing here excludes a second holder: root cannot be
simulated, and get-account-summary reports only that there is no access key and
that MFA is on. The shipped possessive sits one paragraph below "the small
number of people who administer it with me", where a reader takes it as sole
custody. This is the identity/human error of Q63 pointing the other way — one
line from Pouya either arms it or strikes the possessive. The rest of the
original reasoning holds and is why the sentence is still scoped the way 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 shipped sentence is
scoped to "every user and every role" and to who has been granted access
rather than to a bare "nobody else can".
Two page sentences are now supported that were not before.
- "The function that receives the form can only add a record and cannot read the
table back" —
adr-intake-lambda-rolereturnsallowedforPutItemandimplicitDenyforGetItem,Query,Scan,BatchGetItem,UpdateItemandDeleteItem. Previously this rested on reading the policy document; it is now the simulator's decision. - "the credential that publishes this website has no access to the table at
all" —
adr-sml-deployisimplicitDenyon all seven, so "at all" now covers writes and deletes as well as reads.
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 — which is what /legal/privacy/ now tells a
reader in as many words. (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.
# 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/<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 <ROLE> --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/<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
# 5. The CloudFormation / CDK escalation path, per user. Six actions, literal.
aws iam simulate-principal-policy \
--policy-source-arn "arn:aws:iam::327082975128:user/<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.