feat: production run — Q61 ramp, /404/, CloudFront router, cutover runbook
Build and deploy / build-and-deploy (push) Failing after 4s
Build and deploy / build-and-deploy (push) Failing after 4s
Five items of Pouya's production run, 2026-09-01.
Q61 — scroll-padding-top becomes a max() ramp on `10lh - 83px`, with the
plain calc() first as the fallback for engines without `lh`. Hidden focus
stops under minimumFontSize=32: 290 of 1,455 -> 0, control build still
290. Default settings byte-identical (0 differences over 352 page-widths x
17 fields). The 12 residual cells at minimumFontSize=16/20 are pre-existing
and unchanged-or-better; reported, not widened, per instruction.
Intake backend + CloudFront — docs/09-cutover-runbook.md is the
copy-paste sequence for admin execution: every command followed by its
verification and expected output, rollback per part, and Part 10 is Q60's
TTL test. infra/cloudfront/router.js is the trailing-slash function
(30-case suite; 8 fail against the pre-review version, incl. a
protocol-relative open redirect). infra/cloudfront/configure.mjs is
dry-run-by-default and idempotent. scripts/intake-env.mjs emits the six
Lambda env vars from src/data/site.ts.
Four launch blockers found by reading the running system:
- handler.mjs wrote pk/sk; the live table's key is submissionId with no
sort key, so every submission would have failed validation silently
- the Lambda invoke permission is scoped to the old route path
- 22 of 23 pages 403 without the router function
- there was no 404 page; src/pages/404.astro adds it
Claims audit (D20 cutover pass) — five gloss over-reaches corrected on
/practice/energy/, /practice/insurance/ (x2), /practice/technology/ and
/med-arb/. Three findings left open for Pouya: Q62, the /med-arb/ gloss,
and Q60.
Q62 — one frozen-tripwire pattern added under the freeze's own breach
exception, with a probe and four negative fixtures. check:claims exits 1
until the false /legal/privacy/ sentence is corrected, so both deploy
paths are blocked by a mechanism rather than by memory.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Md3GndFqWPzK78xAoebsg5
This commit is contained in:
co-authored by
Claude Opus 5
parent
ca1c2524e1
commit
bd282aa47d
@@ -0,0 +1,141 @@
|
||||
# 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.** Every figure below was read from AWS on **2026-09-01** with the
|
||||
commands listed at the end, run read-only as `arn:aws:iam::327082975128:user/pouya`.
|
||||
No command in this file creates or changes anything. Re-run them rather than
|
||||
trusting this file; it is dated for that reason.
|
||||
|
||||
---
|
||||
|
||||
## The claim being checked
|
||||
|
||||
`src/pages/legal/privacy.astro`, §Who can see it:
|
||||
|
||||
> 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 has to happen before `/legal/privacy/` goes public
|
||||
|
||||
Tracked as `AGENTS.md` §9 **Q62**. It is one of two things and both are Pouya's:
|
||||
|
||||
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.**
|
||||
Reference in New Issue
Block a user