Skip to content
Merged
Show file tree
Hide file tree
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ A governance layer that violated its own policies would be theater.

**paved-org prevents what [CloudCanary](https://github.com/ChrisInvictus/CloudCanary) detects.** Run both: prevention for the known, detection for the drift. The key-creation constraint above prevents the exact event CloudCanary's identity-drift canary alerts on.


## AI-workload security
Extending the prevention/detection thesis to AI workloads:
- [**MCP trust-boundary threat model**](docs/threat-models/mcp-trust-boundaries.md) — five boundaries (B1–B5), STRIDE × OWASP LLM Top 10 (2025), with an IAM lens.
- [**MCP security runbook**](docs/runbooks/securing-mcp-setup.md) — a two-part applied audit: a managed-connector host and a self-hosted agent gateway, with host hardening, a fail-closed exec-approval gate, and a staging-validated container.
- [**`modules/ai-guardrails`**](modules/ai-guardrails/) — org-policy guardrails for AI workloads (verified canned constraints), the prevention half of what [CloudCanary](https://github.com/ChrisInvictus/CloudCanary) detects.
## Cost & reproducibility

Built end-to-end on GCP free-trial credits. The org layer (policies, folders, WIF, state) is control-plane configuration and costs $0 to keep alive. The workload layer (Act 3) carries the one real cost — a global external HTTPS load balancer for IAP, ~$18/month — so it lives for roughly 48 hours: built, evidenced, then `terraform destroy`ed.
Expand Down
17 changes: 17 additions & 0 deletions docs/evidence/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,20 @@ Each core claim maps to a captured artifact. Every one of these states is reprod
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.

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

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.

| # | 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-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-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.
Binary file added docs/evidence/p2-01-ssh-passwordauth-before.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/evidence/p2-02-ssh-passwordauth-after.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/evidence/p2-03-insecureauth-before.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/evidence/p2-04-insecureauth-after.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/evidence/p2-05-exec-approvals-before.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/evidence/p2-06-exec-approvals-after.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/evidence/p2-09-exec-gate-in-container.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/evidence/p2-10-image-credential-free.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
64 changes: 64 additions & 0 deletions docs/runbooks/compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# OpenClaw gateway — hardened containerized deployment (staging).
#
# Every hardening decision is declarative and lives in this file, so the file is
# the documentation. The image is the official GHCR build, pinned by immutable
# digest. The gateway runs through OpenClaw's own documented startup; all
# hardening here is deployment-level isolation, applied around that startup.
#
# Threat model: self-hosted, always-on, loopback-bound gateway reached over a
# private tailnet. See docs/runbooks/securing-mcp-setup.md (Part 2).
#
# Secret-handling note: OpenClaw's documented flow persists its gateway token and
# channel tokens into its state directory (the named volume below). This
# deployment does not claim to remove those from disk; it bounds access to them
# with container isolation (read-only rootfs, dropped capabilities,
# no-new-privileges, non-root user). Native Docker-secrets injection is a
# documented enhancement, deferred (see runbook roadmap).

services:
openclaw-gateway:
# Official image, pinned by immutable digest (not a mutable tag) for
# reproducibility and provenance.
image: ghcr.io/openclaw/openclaw@sha256:6a31d44b2944e7adcd2b582bf6fb463111264ebca97a0201795b799135bd102c

container_name: openclaw-gateway

# Runs as the image's built-in non-root 'node' user (uid 1000), declared
# explicitly rather than trusted implicitly.
user: "1000:1000"

# --- Isolation (the containment layer) ---------------------------------
read_only: true # immutable root filesystem
cap_drop: [ALL] # drop every Linux capability
security_opt:
- no-new-privileges:true # block setuid/privilege escalation
# No docker socket mount. No privileged. No added capabilities.

# --- Writable surfaces: explicit and minimal ---------------------------
# With a read-only rootfs, the process needs declared scratch. tmpfs is
# RAM-backed and never persisted.
tmpfs:
- /tmp:mode=1777,size=128m

# Named volume for gateway state: OpenClaw persists its config store here,
# AND the exec-approvals gate (exec-approvals.json + socket). This MUST
# persist or the B5 approval gate reverts to the ask=off default — the
# High-severity finding, re-opened. Validation gate 2 checks this explicitly.
volumes:
- openclaw-state:/home/node/.openclaw

# --- Exposure ----------------------------------------------------------
# Publish to host loopback ONLY. Tailscale reaches the gateway via the
# host's tailnet interface. No routable bind; no public ingress.
ports:
- "127.0.0.1:18789:18789"

# --- Resource limits ---------------------------------------------------
mem_limit: 1g
cpus: 1.5

# --- Supervision -------------------------------------------------------
restart: unless-stopped

volumes:
openclaw-state:
Loading
Loading