water park

Threat and credential model

Read in full as appendix lesson IA. The estate this describes is water park’s own AWS (the estate ), managed as Terraform (decision 31).

The repo’s write path to org IAM runs through a git repo and its CI. That is the point, and it is also the attack surface. Sections marked open need design before the lesson that depends on them is written.

Assets

The repo controls, directly or transitively: every managed IAM role and permission set, the org policy layer, security groups, and the apply credentials. Compromising the merge path or the apply credentials is equivalent to compromising org IAM. The repo is the crown jewels — not a reason to avoid the pattern (GUIs and tickets have the same power with less audit), but it must be stated.

Trust boundaries

  1. The code host. Merge rights are grant rights on the estate. Account security (MFA, branch protection, no force-push, signed commits open) is part of the posture; the code-host config itself should be managed, since the code host’s own Terraform provider can declare branch protection and CODEOWNERS as watched resources.
  2. The CI runner. A compromised runner in an apply job is full compromise. Mitigations: OIDC short-lived credentials, apply only from protected branches, no apply credentials in PR-triggered jobs, self-hosted posture open. (A Fountain self-hosted runner is a different thing and is trusted mode, decision 29; it never hosts an apply job.)
  3. Temporal. A forged approval signal is an approval. Signal authentication open.
  4. The context package registry. A poisoned release weakens every satellite’s guardrails. Provenance/signing open.
  5. The concierge agent. An agent authors PRs from plain-language requests (design/agentic.md ). It is an untrusted author and its sandbox is untrusted compute: assume everything readable inside is exfiltrated. So the sandbox holds no cloud credentials and is never a federation subject (decision 15); its verbs are served by a process outside the boundary — Rounds’ server is the reference implementation (decision 30): it holds the plan-tier credential behind a network-bound trust anchor, itself a principal in the repo, and it is the only thing that writes. The sandbox receives only a conversation-scoped token, and its egress is a default-deny allowlist naming the verb service and the code host (on a hosted sandbox provider only, decision 29). Request text is a prompt-injection surface — tolerable because the verification stack treats agent PRs exactly like human PRs. The requester’s identity is a declared claim (decision 17): a forged one yields a PR naming the wrong asker — reviewer-facing social engineering, not escalation, since CODEOWNERS approval is still what applies anything.

Attack paths and mitigations

PathMitigation
Over-broad PR merged by tired reviewerlint in editor, CheckNoNewAccess proof (A3b), generated CODEOWNERS (decision 21)
Plan credential exercised by an untrusted PR authordecision 22 — PR-time validation is credential-free; proofs run post-merge-queue or behind a maintainer label
Review routing quietly changedCODEOWNERS generated and drift-watched; a reroute is a diff (A20)
Satellite mints an over-powered rolelint refuses at build, iam:PermissionsBoundary at apply, independently (decision 20)
PR weakens the guardrails themselvesguardrail paths require security review and render high-severity (Op-manifest diff for Ops)
Out-of-band console changedrift watch on cron; owned-only reconcile PR; SG/trust drift is page-worthy (open: severity routing)
Stale plan applied after estate movedmanifest-digest freshness check (decision 24); apply refuses
Apply-credential theft from runnerOIDC short-lived creds, protected-branch-only apply, the apply boundary
Forged Temporal approval signalopendesign/break-glass.md
Prompt-injected agent opens a malicious PRverified identically to human PRs; no creds in the sandbox; agent cannot approve/apply/signal
Forged requester identityrendered on the PR as an unverified claim (decision 17); enrollment is itself a reviewed change
Credential exfiltration from the sandboxnothing to steal (decision 15); trust anchor pinned to cluster egress so a replayed token fails at STS; per-conversation sts:SourceIdentity
Orphaned/stale accessfirst-class expires → drift; access-review and offboard Ops
water park escalating itselfthe apply role’s own boundary (decision 12)

Credential model

Three tiers, never mixed in one job:

  1. Plan (read). Describe/list plus Access Analyzer check APIs, short-lived via OIDC. Not present in jobs an untrusted author can trigger (decision 22).
  2. Apply (write). Only in gated jobs on protected branches, or held by the Temporal worker. The most powerful credential in the org, and bounded: its boundary denies boundary-detachment and editing the apply role or baseline outside the pipeline, and scopes writes to owned resources. Exact policy open — belongs in the baseline.
  3. Org (management account). Org policies and Identity Center; separate credential, separate gate, narrowest use (design/multi-account.md ).

Floci needs none of these — every PR validates locally first. All three are federated short-lived credentials whose trust config is code under identity/. A loose subject condition on any federation trust is a standing backdoor, so those resources get the strictest lint and drift severity in the estate.

What water park does not defend against

Root/management-account compromise. A malicious security-team insider with merge rights on guardrail paths. Cloud control-plane compromise. Unmanaged (foreign) resources, since estate-wide scanning is an auditor’s job. Compromise of an enrolled intake identity: the holder can open PRs in that principal’s name, which CODEOWNERS still has to approve; revoking it is a PR against the same leaf file.

Open items

Signed commits policy. Severity routing for drift. Signal authentication. Registry provenance. The apply boundary policy. The read-tier action list. Each settles in the lesson that needs it: the boundary policy in I5, routing in I7, signal authentication in I10, provenance in I8.