Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 186 additions & 15 deletions docs/evidence/README.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,209 @@
# Evidence index
# Evidence

Each core claim maps to a captured artifact. Every one of these states is reproducible on demand — the code regenerates them.
Every core claim maps to a captured artifact, and every state shown is
reproducible on demand — the code regenerates it.

This page has two layers: an **at-a-glance table** for each section (scan the
whole picture on one screen), followed by the **full walkthrough** where each
claim is paired with its screenshot inline (scroll top to bottom for the detail).
Jump to the walkthrough via any row, or just keep scrolling.

Two bodies of evidence: **Acts 1–3** (the GCP org baseline) and **Part 2** (the
self-hosted AI-agent gateway audit).

---

## At a glance

### Acts 1–3 — GCP organization baseline

| # | Claim | Artifact | Status |
|---|---|---|---|
| 1 | Org converged: 7 guardrails + folders + WIF, all code-managed | `act1-converged-plan.png` | ✅ |
| 2 | Keyless pipeline: plan-on-PR via WIF, zero SA keys in existence | `act2-keyless-pipeline-pr.png` | ✅ |
| 3 | Apply-on-merge: a merge becoming real infrastructure, keyless | `act2-apply-on-merge-builds.png` | ✅ |
| 4 | The platform refuses violations: SA key creation denied by org policy | `act1-org-policy-denies-sa-key.png` | ✅ |
| 5 | Deny-by-default in the wild: the default compute SA, stripped of auto-grants by org policy, cannot read Cloud Build's staging bucket until granted objectViewer on exactly that bucket | `act1-default-sa-born-with-nothing.png` | ✅ |
| 6 | The vending machine: a hardened project created by a pull request — 14 resources, and the workload gate visibly closed (`lb_ip: not yet deployed`) | `act3-project-vended-via-pr-{1,2,3}.png` | ✅ |
| 5 | Deny-by-default: the default compute SA, stripped of auto-grants, cannot read Cloud Build's staging bucket until granted objectViewer on exactly that bucket | `act1-default-sa-born-with-nothing.png` | ✅ |
| 6 | The vending machine: a hardened project created by a pull request — 14 resources, workload gate visibly closed (`lb_ip: not yet deployed`) | `act3-project-vended-via-pr-{1,2,3}.png` | ✅ |
| 7 | The paid zero-trust layer, reviewed in full before a dollar was spent | `act3-workload-plan-{1,2,3}.png` | ✅ |
| 8 | Zero trust, front door: anonymous request → identity challenge | `act3-iap-identity-challenge.png` | ✅ |
| 9 | Zero trust, allow path: IAP-asserted identity rendered by the app | `act3-iap-asserted-identity.png` | ✅ |
| 10 | Zero trust, deny path: an authenticated Google account, absent from the access list, receives 403 | `act3-iap-403-unauthorized.png` | ✅ |
| 11 | Reproducibility: the paid layer destroyed through the same pipeline | `act3-teardown-plan.png` | ✅ |

Rows 8–10 are the zero-trust proof set: anonymous → challenged, authorized → named, authenticated-but-unauthorized → denied. A control you've only seen permit is a control you haven't tested.

Trap 11 to the runbook's field notes: deletion_protection is enforced from state, not config — flipping it and destroying in one change deadlocks; apply the flag first, or evict via terraform state rm + delete out-of-band. Set it false from birth on resources with planned lifespans.
Rows 8–10 are the zero-trust proof set: anonymous → challenged, authorized →
named, authenticated-but-unauthorized → denied. *A control you've only seen
permit is a control you haven't tested.*

## Part 2 — MCP / AI-agent connector audit (`securing-mcp-setup.md`)
### Part 2 — self-hosted AI-agent gateway audit

Host hardening and containment evidence for the self-hosted gateway audit. Rows p2-08–10 are captured on a non-production staging host; rows p2-01–07 are from the production gateway.
Host hardening and containment evidence for the runbook
[`docs/runbooks/securing-mcp-setup.md`](../runbooks/securing-mcp-setup.md).
Rows p2-01–07 are from the **production** gateway; p2-08–10 are from a
**non-production staging** host, labeled as such.

| # | Claim | Artifact | Status |
|---|---|---|---|
| p2-01/02 | SSH password authentication disabled: effective `sshd` config `yes` → `no` | `p2-01-ssh-passwordauth-before.png`, `p2-02-ssh-passwordauth-after.png` | ✅ |
| p2-03/04 | Control-UI insecure auth disabled: `allowInsecureAuth` `true` → `false`, gateway active after change | `p2-03-insecureauth-before.png`, `p2-04-insecureauth-after.png` | ✅ |
| p2-05 | Exec authorization baseline: `security=full, ask=off`, empty allowlist — ungated shell/file-write | `p2-05-exec-approvals-before.png` | ✅ |
| p2-01/02 | SSH password authentication disabled: effective `sshd` config `yes` → `no` | `p2-01before.png`, `p2-02after.png` | ✅ |
| p2-03/04 | Control-UI insecure auth disabled: `allowInsecureAuth` `true` → `false`, gateway active after | `p2-03before.png`, `p2-04after.png` | ✅ |
| p2-05 | Exec baseline (High): `security=full, ask=off`, empty allowlist — ungated shell/file-write | `p2-05-exec-approvals-before.png` | ✅ |
| p2-06 | Exec-approval gate configured: `ask=always` | `p2-06-exec-approvals-after.png` | ✅ |
| p2-07 | Gate enforced (fail-closed): a shell command over the channel returns an approval prompt, does not execute | `p2-07-exec-gate-approval-required-telegram.png` | ✅ |
| p2-08 | Container isolation: read-only rootfs, `cap_drop=ALL`, non-root uid 1000, `no-new-privileges`, not privileged | `p2-08-container-isolation-hardening.png` | ✅ (staging) |
| p2-07 | Gate enforced (fail-closed): a channel command returns an approval prompt, does not execute | `p2-07-exec-gate-approval-required-telegram.png` | ✅ |
| p2-08 | Container isolation: read-only rootfs, `cap_drop=ALL`, non-root uid 1000, `no-new-privileges` | `p2-08-container-isolation-hardening.png` | ✅ (staging) |
| p2-09 | Exec-approval gate operates in-container: `security=full, ask=always, askFallback=deny` | `p2-09-exec-gate-in-container.png` | ✅ (staging) |
| p2-10 | Image credential-free: zero credential-string matches in image layers | `p2-10-image-credential-free.png` | ✅ (staging) |

The exec-gate set (p2-05 → p2-07) is the B5 remediation: ungated → configured → enforced, verified behaviorally rather than by config inspection. The container set (p2-08 → p2-10) is the containment layer, validated on staging ahead of a documented production promotion.
The exec-gate set (p2-05 → p2-07) is the B5 remediation: ungated → configured →
enforced, verified behaviorally rather than by config inspection. The container
set (p2-08 → p2-10) is the containment layer, validated on staging ahead of a
documented production promotion.

> **Field note (Trap 11):** `deletion_protection` is enforced from *state*, not
> config — flipping it and destroying in one change deadlocks. Apply the flag
> first, or evict via `terraform state rm` + delete out-of-band. Set it `false`
> from birth on resources with planned lifespans.

---

# Walkthrough

Each claim, paired with the screenshot that proves it.

## Acts 1–3 — the GCP organization baseline

### 1 · Org converged
Seven guardrails, folder hierarchy, and Workload Identity Federation — all
code-managed, plan showing no drift.

![Converged Terraform plan: no changes, infrastructure matches configuration](act1-converged-plan.png)

### 2 · Keyless pipeline (plan-on-PR)
A pull request renders a Terraform plan, authenticated to GCP via WIF — zero
service-account keys in existence.

![Plan-on-PR: GitHub Actions rendering a Terraform plan via Workload Identity Federation](act2-keyless-pipeline-pr.png)

### 3 · Apply-on-merge
A merge becoming real infrastructure — still keyless, end to end.

![Apply-on-merge: a merged PR building infrastructure through the keyless pipeline](act2-apply-on-merge-builds.png)

### 4 · The platform refuses violations
A service-account key creation request, denied by org policy at the platform
level — not by review, by the platform itself.

![Org policy denying a service-account key creation request](act1-org-policy-denies-sa-key.png)

### 5 · Deny-by-default in the wild
The default compute service account, stripped of automatic grants by org policy,
cannot read Cloud Build's staging bucket until explicitly granted `objectViewer`
on exactly that bucket.

![Default compute SA born with no privilege, unable to read a bucket until granted](act1-default-sa-born-with-nothing.png)

### 6 · The vending machine
A hardened project created by a pull request — 14 resources, with the workload
gate visibly closed (`lb_ip: not yet deployed`).

![A hardened project vended by pull request, part 1](act3-project-vended-via-pr-1.png)

![A hardened project vended by pull request, part 2](act3-project-vended-via-pr-2.png)

![A hardened project vended by pull request, part 3](act3-project-vended-via-pr-3.png)

### 7 · The paid layer, reviewed before a dollar was spent
The zero-trust workload plan, read in full prior to apply.

![Zero-trust workload Terraform plan, part 1](act3-workload-plan-1.png)

![Zero-trust workload Terraform plan, part 2](act3-workload-plan-2.png)

![Zero-trust workload Terraform plan, part 3](act3-workload-plan-3.png)

### 8–10 · Zero trust, proven three ways
Anonymous → challenged, authorized → named, authenticated-but-unauthorized →
denied.

**Front door — anonymous request meets an identity challenge:**

![IAP identity challenge presented to an anonymous request](act3-iap-identity-challenge.png)

**Allow path — IAP-asserted identity, rendered by the app:**

![The application rendering an IAP-asserted identity on the allow path](act3-iap-asserted-identity.png)

**Deny path — an authenticated Google account, absent from the access list, receives 403:**

![A 403 for an authenticated but unauthorized account](act3-iap-403-unauthorized.png)

### 11 · Reproducibility
The paid layer destroyed through the same pipeline that built it.

![Teardown plan destroying the paid layer through the pipeline](act3-teardown-plan.png)

## Part 2 — self-hosted AI-agent gateway audit

Rows p2-01–07 are from the **production** gateway; p2-08–10 are from a
**non-production staging** host (the container validation).

### p2-01/02 · SSH password authentication disabled
Effective `sshd` config moved from password-auth `yes` to `no` — verified against
the effective configuration, not just the file.

**Before:**

![sshd effective config showing PasswordAuthentication yes](p2-01-ssh-passwordauth-before.png)

**After:**

![sshd effective config showing PasswordAuthentication no](p2-02-ssh-passwordauth-after.png)

### p2-03/04 · Control-UI insecure auth disabled
`allowInsecureAuth` moved `true` → `false`; gateway confirmed active after the
change.

**Before:**

![Gateway config showing allowInsecureAuth true](p2-03-insecureauth-before.png)

**After:**

![Gateway config showing allowInsecureAuth false, service active](p2-04-insecureauth-after.png)

### p2-05 · Exec authorization — the baseline finding (High)
`security=full, ask=off`, empty allowlist: any command over the channel executed
immediately, unattended, at the service user's privilege.

![Exec approvals showing security full, ask off, no allowlist — ungated](p2-05-exec-approvals-before.png)

### p2-06 · Exec-approval gate configured
Policy set to require approval per invocation (`ask=always`).

![Exec approvals showing ask always configured](p2-06-exec-approvals-after.png)

### p2-07 · Gate enforced, fail-closed
The decisive test: the same shell command over the channel now returns an
approval prompt (Allow Once / Deny) and does **not** execute without explicit
approval. *Verified behaviorally — configuration state alone did not reflect
enforcement until the fail-open fallback was corrected.*

![A message-delivered command returning an approval-required prompt instead of executing](p2-07-exec-gate-approval-required-telegram.png)

### p2-08 · Container isolation (staging)
Runtime host-config verified: read-only root filesystem, all Linux capabilities
dropped, non-root uid 1000, `no-new-privileges`, not privileged.

![docker inspect output confirming read-only rootfs, cap-drop ALL, non-root, no-new-privileges](p2-08-container-isolation-hardening.png)

### p2-09 · Exec-approval gate operates in-container (staging)
The B5 gate confirmed operating identically inside the container:
`security=full, ask=always, askFallback=deny`, mounted on the state volume so it
does not revert.

![Exec approvals inside the container showing ask always, askFallback deny](p2-09-exec-gate-in-container.png)

### p2-10 · Image credential-free (staging)
Image layers scanned for credential strings — zero matches. No credential
material baked into the image.

![Image layer scan showing zero credential-string matches](p2-10-image-credential-free.png)
Loading