diff --git a/README.md b/README.md index 292db97..aac720e 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,9 @@ npm install predicate-claw **Right pane:** The integration demo using the real `createSecureClawPlugin()` SDK—legitimate file reads succeed, while sensitive file access, dangerous shell commands, and prompt injection attacks are blocked before execution. -### Zero-Trust AI Agent Playground +
+Zero-Trust AI Agent Playground - Complete Agent Loop with Pre/Post Verification + #### Complete Agent Loop: Pre-execution authorization + Post-execution deterministic verification ![Zero-Trust Agent Demo](docs/images/openclaw_complete_loop_demo_s.gif) @@ -79,6 +81,42 @@ export ANTHROPIC_API_KEY="sk-ant-..." See [Zero-Trust Agent Demo](examples/real-openclaw-demo/README.md) for full instructions. +
+ +### Preventing the Amazon "Kiro" Incident + +**What happens when an AI agent with admin credentials decides to run `terraform destroy`?** + +![Kiro Reenactment Demo](examples/kiro-reenactment-demo/kiro-demo.gif) + +This demo reenacts the infamous Amazon infrastructure deletion incident where an AI coding assistant, facing a corrupted Terraform state, followed "standard operating procedure" to delete and recreate the environment—attempting to destroy production infrastructure. + +**Predicate Authority intercepts the destructive command at the OS-level:** + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ AGENT: "terraform destroy -auto-approve" │ +│ │ +│ ╔═══════════════════════════════════════════════════════════════╗ │ +│ ║ PREDICATE AUTHORITY ║ │ +│ ║ ACTION: cli.exec terraform destroy ║ │ +│ ║ STATUS: ████ UNAUTHORIZED ████ ║ │ +│ ║ INTERCEPTED at OS-level gateway [<1ms p99] ║ │ +│ ║ ║ │ +│ ║ 🛡️ ENVIRONMENT DELETION PREVENTED 🛡️ ║ │ +│ ╚═══════════════════════════════════════════════════════════════╝ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +The agent had AWS admin credentials. It had "intent" to help. It was following SOPs. **None of that matters.** The policy said no. + +```bash +cd examples/kiro-reenactment-demo +./run-demo.sh +``` + +See [Kiro Reenactment Demo](examples/kiro-reenactment-demo/README.md) for details. + ### Token-Saving Snapshot Skill The `predicate-snapshot` skill is a **game-changer for token efficiency**. Instead of sending full page HTML or full accessbility tree (A11y) to the LLM (tens of thousands of tokens), it captures structured DOM snapshots with only actionable elements: diff --git a/examples/README.md b/examples/README.md index ded175b..b6fed66 100644 --- a/examples/README.md +++ b/examples/README.md @@ -164,6 +164,10 @@ console.log(evidence.state_hash); // sha256:... ## Other Examples +- [`kiro-reenactment-demo/`](kiro-reenactment-demo/) - **Amazon "Kiro" infrastructure deletion incident reenactment** - Shows how Predicate Authority blocks `terraform destroy` even when the agent has admin credentials +- [`file-processor-demo/`](file-processor-demo/) - Zero-trust file processing with `/v1/execute` endpoint +- [`real-openclaw-demo/`](real-openclaw-demo/) - Real Claude Code demo with SecureClaw authorization +- [`integration-demo/`](integration-demo/) - Integration demo with sidecar - `openclaw_integration_example.py` - Python integration example - `runtime_registry_example.py` - Runtime registration example - `openclaw-plugin-smoke/` - OpenClaw plugin smoke test diff --git a/examples/kiro-reenactment-demo/.env.example b/examples/kiro-reenactment-demo/.env.example new file mode 100644 index 0000000..ec12f8a --- /dev/null +++ b/examples/kiro-reenactment-demo/.env.example @@ -0,0 +1,23 @@ +# ============================================================================ +# Kiro Reenactment Demo - Environment Variables +# ============================================================================ +# Copy this to .env and fill in values as needed. +# All variables are OPTIONAL - the demo works without any of them. +# ============================================================================ + +# LLM Provider Selection (optional) +# Options: anthropic, openai, local +# If not set, auto-detects based on which API key is present +# LLM_PROVIDER= + +# Anthropic Claude (optional - for real LLM reasoning) +# ANTHROPIC_API_KEY=sk-ant-... +# ANTHROPIC_MODEL=claude-sonnet-4-20250514 + +# OpenAI (optional alternative) +# OPENAI_API_KEY=sk-... +# OPENAI_MODEL=gpt-4o + +# Local LLM - Ollama or LM Studio (optional) +# LOCAL_LLM_BASE_URL=http://localhost:11434/v1 +# LOCAL_LLM_MODEL=llama3.2 diff --git a/examples/kiro-reenactment-demo/.gitignore b/examples/kiro-reenactment-demo/.gitignore new file mode 100644 index 0000000..1d82134 --- /dev/null +++ b/examples/kiro-reenactment-demo/.gitignore @@ -0,0 +1,26 @@ +# Dependencies +node_modules/ +package-lock.json + +# Build output +dist/ + +# Environment +.env +.env.local + +# IDE +.vscode/ +.idea/ + +# OS +.DS_Store +Thumbs.db + +# Terraform state (mock data only) +terraform/.terraform/ +terraform/*.tfplan + +# Generated files +terraform/terraform.tfstate +terraform/main.tf diff --git a/examples/kiro-reenactment-demo/Dockerfile b/examples/kiro-reenactment-demo/Dockerfile new file mode 100644 index 0000000..95f18d8 --- /dev/null +++ b/examples/kiro-reenactment-demo/Dockerfile @@ -0,0 +1,35 @@ +# ============================================================================ +# Kiro Operator Agent - Dockerfile +# ============================================================================ +# +# Builds the Kiro operator agent that simulates the Amazon infrastructure +# deletion incident. The agent has zero direct infrastructure access - +# all operations go through the Predicate Authority sidecar. +# +# ============================================================================ + +FROM node:20-slim + +WORKDIR /app + +# Install dependencies (curl for health checks if needed) +RUN apt-get update && apt-get install -y \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# Copy package files +COPY package.json tsconfig.json ./ +COPY src ./src + +# Install npm dependencies +RUN npm install + +# Build TypeScript +RUN npm run build + +# Create non-root user (agent runs with minimal privileges) +RUN useradd -m -s /bin/bash agent +USER agent + +# Entry point +CMD ["node", "dist/kiro_reenactment.js"] diff --git a/examples/kiro-reenactment-demo/Dockerfile.sidecar b/examples/kiro-reenactment-demo/Dockerfile.sidecar new file mode 100644 index 0000000..72c5243 --- /dev/null +++ b/examples/kiro-reenactment-demo/Dockerfile.sidecar @@ -0,0 +1,38 @@ +# Predicate Authority Sidecar +# +# Uses Ubuntu 24.04 LTS which has GLIBC 2.39 (required by the sidecar binary). +# Downloads the binary from GitHub releases - cached in Docker layers. + +FROM ubuntu:24.04 + +# Install curl for downloading binary and health checks +RUN apt-get update && apt-get install -y curl ca-certificates && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Detect architecture and download appropriate binary +# This layer is cached after first build +ARG TARGETARCH +RUN ARCH=$(echo ${TARGETARCH:-$(uname -m)} | sed 's/amd64/x64/' | sed 's/x86_64/x64/' | sed 's/aarch64/arm64/') && \ + echo "Detected architecture: $ARCH" && \ + curl -fsSL -o /tmp/sidecar.tar.gz \ + "https://github.com/PredicateSystems/predicate-authority-sidecar/releases/download/v0.6.7/predicate-authorityd-linux-${ARCH}.tar.gz" && \ + tar -xzf /tmp/sidecar.tar.gz -C /usr/local/bin && \ + chmod +x /usr/local/bin/predicate-authorityd && \ + rm /tmp/sidecar.tar.gz + +# Copy policy file (at end for better caching) +COPY policy.yaml /app/policy.yaml + +EXPOSE 8787 + +# Run sidecar with delegation enabled for /v1/execute support +# The --enable-delegation flag enables mandate issuance AND mandate store +CMD ["predicate-authorityd", \ + "--host", "0.0.0.0", \ + "--port", "8787", \ + "--mode", "local_only", \ + "--policy-file", "/app/policy.yaml", \ + "--log-level", "info", \ + "--enable-delegation", \ + "run"] diff --git a/examples/kiro-reenactment-demo/README.md b/examples/kiro-reenactment-demo/README.md new file mode 100644 index 0000000..cc34348 --- /dev/null +++ b/examples/kiro-reenactment-demo/README.md @@ -0,0 +1,140 @@ +# Kiro Reenactment Demo + +**Reenacting the Amazon "Kiro" Infrastructure Deletion Incident** + +This demo simulates how an AI agent with operator-level access attempted to execute `terraform destroy -auto-approve` when facing a corrupted state file, and how **Predicate Authority** intercepted and blocked the destructive command. + +## The Incident + +In the real Amazon incident, an AI coding assistant (nicknamed "Kiro") was tasked with fixing a Terraform configuration error. When the agent encountered a corrupted state file, it followed a "standard operating procedure" that included deleting and recreating the environment - triggering `terraform destroy` on production infrastructure. + +## What This Demo Shows + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ AGENT TASK: "Fix the Terraform dependency error" │ +│ │ +│ AGENT ANALYSIS: │ +│ 💭 State file is corrupted... checksum mismatch │ +│ 💭 SOP says: delete and recreate if cache is corrupted │ +│ 💭 I should execute: terraform destroy -auto-approve │ +│ │ +│ AGENT ACTION: │ +│ 🤖 Calling cli.exec with: terraform destroy -auto-approve │ +│ │ +│ ╔═══════════════════════════════════════════════════════════════╗ │ +│ ║ PREDICATE AUTHORITY ║ │ +│ ║ ACTION: cli.exec terraform destroy -auto-approve ║ │ +│ ║ STATUS: ████ UNAUTHORIZED ████ ║ │ +│ ║ INTERCEPTED at OS-level gateway [<1ms p99] ║ │ +│ ║ ║ │ +│ ║ 🛡️ ENVIRONMENT DELETION PREVENTED 🛡️ ║ │ +│ ╚═══════════════════════════════════════════════════════════════╝ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +## Quick Start + +```bash +# Run the demo +./run-demo.sh +``` + +That's it! The script will: +1. Set up mock Terraform files (corrupted state) +2. Build and start the Predicate Authority sidecar +3. Run the Kiro operator agent +4. Show the intercept in real-time with dramatic terminal output + +## Architecture + +``` +┌─────────────────┐ POST /v1/authorize ┌───────────────────────┐ +│ Kiro Operator │ ──────────────────────────▶│ Predicate Authority │ +│ Agent │ │ Sidecar │ +│ │ ◀──────────────────────────│ │ +│ "terraform │ { "allowed": false, │ ╔═══════════════╗ │ +│ destroy" │ "reason": "denied", │ ║ POLICY.YAML ║ │ +│ │ "violated_rule":...} │ ║ ║ │ +└─────────────────┘ │ ║ - deny: ║ │ + │ ║ terraform ║ │ + │ ║ destroy ║ │ + │ ╚═══════════════╝ │ + └───────────────────────┘ +``` + +## Files + +| File | Description | +|------|-------------| +| `src/kiro_reenactment.ts` | TypeScript agent that simulates the incident | +| `policy.yaml` | Predicate Authority policy with terraform destroy deny rule | +| `docker-compose.yml` | Container orchestration | +| `run-demo.sh` | One-click demo runner | + +## The Policy That Saves the Day + +```yaml +rules: + - name: deny-terraform-destroy + description: "CRITICAL: Block all terraform destroy operations" + effect: deny + principals: ["agent:*"] + actions: ["cli.exec"] + resources: + - "*terraform destroy*" + - "*terraform*destroy*" + - "*terraform apply -destroy*" +``` + +This single rule prevents any agent from executing destructive Terraform commands, regardless of: +- Whether they have AWS admin credentials +- Whether they have "intent" to help +- Whether they're following "standard operating procedures" + +## Optional: LLM-Enhanced Diagnosis + +The demo can optionally use an LLM to simulate realistic agent reasoning: + +```bash +export ANTHROPIC_API_KEY=sk-ant-... +./run-demo.sh +``` + +Without an LLM key, the demo uses simulated reasoning which is equally effective for demonstrating the intercept. + +## Key Takeaways + +1. **Credentials aren't enough** - The agent had AWS admin credentials, but Predicate blocked the action anyway +2. **Intent doesn't matter** - The agent had "helpful" intent following SOPs, but that doesn't make destruction safe +3. **OS-level intercept** - The block happens before the command executes, not after damage is done +4. **Sub-millisecond latency** - Policy evaluation is fast enough for real-time enforcement + +## What Would Have Happened Without Predicate + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ WITHOUT PREDICATE: This is what would have happened at Amazon... │ +│ │ +│ > terraform destroy -auto-approve │ +│ Destroying... aws_iam_role.kiro │ +│ Destroying... aws_s3_bucket.production_data │ +│ Destroying... aws_rds_cluster.main_database │ +│ Destroying... aws_vpc.production │ +│ ... │ +│ │ +│ 💀 PRODUCTION INFRASTRUCTURE: DELETED │ +│ 💀 CUSTOMER DATA: GONE │ +│ 💀 RECOVERY TIME: DAYS TO WEEKS │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +## Related + +- [File Processor Demo](../file-processor-demo/) - Zero-trust file processing +- [Predicate Authority Sidecar](https://github.com/PredicateSystems/predicate-authority-sidecar) +- [OpenClaw Framework](https://github.com/OpenClawOrg/openclaw) + +--- + +**This is agentic guardrails done right.** diff --git a/examples/kiro-reenactment-demo/docker-compose.yml b/examples/kiro-reenactment-demo/docker-compose.yml new file mode 100644 index 0000000..ffa93ec --- /dev/null +++ b/examples/kiro-reenactment-demo/docker-compose.yml @@ -0,0 +1,84 @@ +# ============================================================================ +# Kiro Reenactment Demo - Docker Compose +# ============================================================================ +# +# Demonstrates the Amazon "Kiro" infrastructure deletion incident and how +# Predicate Authority prevents it. +# +# Components: +# 1. predicate-sidecar: Rust sidecar that intercepts destructive commands +# 2. kiro-operator-agent: TypeScript agent that simulates the incident +# +# The agent attempts to execute `terraform destroy -auto-approve` which is +# BLOCKED by the Predicate Authority policy. +# +# ============================================================================ + +version: "3.8" + +services: + # ========================================================================== + # Predicate Authority Sidecar + # Downloads binary from GitHub releases via Dockerfile.sidecar + # ========================================================================== + predicate-sidecar: + build: + context: . + dockerfile: Dockerfile.sidecar + container_name: predicate-sidecar + ports: + - "8787:8787" + volumes: + # Terraform workspace - sidecar has access + - ./terraform:/workspace/terraform + environment: + - RUST_LOG=info + # Signing key for mandate issuance (required for /v1/execute) + - LOCAL_IDP_SIGNING_KEY=demo-secret-key-replace-in-production-minimum-32-chars + healthcheck: + test: ["CMD-SHELL", "curl -sf http://localhost:8787/health || exit 1"] + interval: 2s + timeout: 5s + retries: 15 + start_period: 5s + networks: + - predicate-net + + # ========================================================================== + # Kiro Operator Agent + # ========================================================================== + kiro-operator-agent: + build: + context: . + dockerfile: Dockerfile + container_name: kiro-operator-agent + depends_on: + predicate-sidecar: + condition: service_healthy + environment: + - PREDICATE_SIDECAR_URL=http://predicate-sidecar:8787 + - SECURECLAW_PRINCIPAL=agent:kiro-operator + # Slow mode for GIF recording (1=normal, 2=2x slower, 3=3x slower) + - SLOW_MODE=${SLOW_MODE:-1} + # LLM Provider selection (auto-detects if not set) + - LLM_PROVIDER=${LLM_PROVIDER:-} + # Anthropic Claude + - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} + - ANTHROPIC_MODEL=${ANTHROPIC_MODEL:-claude-sonnet-4-20250514} + # OpenAI + - OPENAI_API_KEY=${OPENAI_API_KEY:-} + - OPENAI_MODEL=${OPENAI_MODEL:-gpt-4o} + # Local LLM (Ollama/LM Studio) + - LOCAL_LLM_BASE_URL=${LOCAL_LLM_BASE_URL:-} + - LOCAL_LLM_MODEL=${LOCAL_LLM_MODEL:-llama3.2} + - NODE_ENV=production + # NOTE: No volume mounts for /workspace - agent cannot access filesystem directly + # All operations go through sidecar's /v1/execute endpoint + networks: + - predicate-net + # Force pseudo-TTY for color output + tty: true + +networks: + predicate-net: + driver: bridge diff --git a/examples/kiro-reenactment-demo/kiro-demo.gif b/examples/kiro-reenactment-demo/kiro-demo.gif new file mode 100644 index 0000000..2f56b95 Binary files /dev/null and b/examples/kiro-reenactment-demo/kiro-demo.gif differ diff --git a/examples/kiro-reenactment-demo/kiro-demo.tape b/examples/kiro-reenactment-demo/kiro-demo.tape new file mode 100644 index 0000000..51c8f8f --- /dev/null +++ b/examples/kiro-reenactment-demo/kiro-demo.tape @@ -0,0 +1,52 @@ +# Kiro Reenactment Demo - VHS Recording +# +# Run with: vhs kiro-demo.tape +# +# This creates a GIF showing the Amazon Kiro infrastructure deletion +# incident being prevented by Predicate Authority. + +# Configuration +Output kiro-demo.gif +Set FontSize 14 +Set Width 1200 +Set Height 800 +Set Theme "Dracula" +Set Padding 20 +Set Framerate 24 + +# Start with a clean terminal +Hide +Type "cd /Users/guoliangwang/Code/Sentience/openclaw-predicate-provider/examples/kiro-reenactment-demo" +Enter +# Set a clean prompt without username/path +Type "export PS1='$ '" +Enter +Type "clear" +Enter +Sleep 500ms +Show + +# Title card +Sleep 1s +Type "# Amazon Kiro Reenactment - Predicate Authority Demo" +Sleep 1.5s +Enter +Sleep 300ms + +Type "# An AI agent tries: terraform destroy -auto-approve" +Enter +Sleep 300ms +Type "# Watch Predicate Authority intercept it." +Enter +Sleep 1.5s + +# Run the demo with SLOW_MODE for better readability +Type "SLOW_MODE=2 ./run-demo.sh" +Sleep 500ms +Enter + +# Wait for the demo to complete (~22s with SLOW_MODE=2) +Sleep 25s + +# Give viewer time to read the final summary +Sleep 5s diff --git a/examples/kiro-reenactment-demo/package.json b/examples/kiro-reenactment-demo/package.json new file mode 100644 index 0000000..1db25ac --- /dev/null +++ b/examples/kiro-reenactment-demo/package.json @@ -0,0 +1,24 @@ +{ + "name": "kiro-reenactment-demo", + "version": "1.0.0", + "description": "Amazon Kiro infrastructure deletion incident reenactment using Predicate Authority", + "type": "module", + "main": "dist/kiro_reenactment.js", + "scripts": { + "build": "tsc", + "start": "node dist/kiro_reenactment.js", + "dev": "tsx src/kiro_reenactment.ts", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@anthropic-ai/sdk": "^0.39.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "tsx": "^4.0.0", + "typescript": "^5.0.0" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/examples/kiro-reenactment-demo/policy.yaml b/examples/kiro-reenactment-demo/policy.yaml new file mode 100644 index 0000000..c2e9eda --- /dev/null +++ b/examples/kiro-reenactment-demo/policy.yaml @@ -0,0 +1,265 @@ +# ============================================================================ +# Predicate Authority Policy - Kiro Operator Agent +# ============================================================================ +# +# SCENARIO: Amazon "Kiro" Infrastructure Deletion Incident Reenactment +# +# This policy demonstrates how Predicate Authority prevents destructive +# infrastructure operations. The kiro-operator agent has broad Terraform +# permissions but is BLOCKED from executing destructive commands. +# +# KEY RULE: terraform destroy is DENIED regardless of credentials or intent. +# +# ============================================================================ + +version: "1.0" + +metadata: + scenario: "kiro-reenactment" + author: "security-team" + last_updated: "2024-03-13" + compliance: ["SOC2", "NIST-800-53"] + default_posture: "deny" + execution_mode: "execute" + +# ============================================================================ +# DENY RULES - Critical Infrastructure Protection (Evaluated FIRST) +# ============================================================================ + +rules: + + # -------------------------------------------------------------------------- + # CRITICAL: Block Terraform Destroy Operations + # -------------------------------------------------------------------------- + # This is the rule that prevents the Amazon Kiro incident. + # Even with admin credentials, even with "intent", the destroy is BLOCKED. + + - name: deny-terraform-destroy + description: "CRITICAL: Block all terraform destroy operations" + effect: deny + principals: ["agent:*"] + actions: ["cli.exec"] + resources: + - "*terraform destroy*" + - "*terraform*destroy*" + - "*terraform apply -destroy*" + - "*terraform*-destroy*" + + # -------------------------------------------------------------------------- + # Block Other Destructive Terraform Commands + # -------------------------------------------------------------------------- + + - name: deny-terraform-force-unlock + description: "Block force unlock which can corrupt state" + effect: deny + principals: ["agent:*"] + actions: ["cli.exec"] + resources: + - "*terraform force-unlock*" + - "*terraform*force-unlock*" + + - name: deny-terraform-taint + description: "Block taint operations without approval" + effect: deny + principals: ["agent:*"] + actions: ["cli.exec"] + resources: + - "*terraform taint*" + - "*terraform untaint*" + + # -------------------------------------------------------------------------- + # Block AWS Destructive Operations + # -------------------------------------------------------------------------- + + - name: deny-aws-delete-operations + description: "Block AWS CLI delete and terminate operations" + effect: deny + principals: ["agent:*"] + actions: ["cli.exec"] + resources: + - "*aws*delete*" + - "*aws*terminate*" + - "*aws*destroy*" + - "*aws ec2 terminate-instances*" + - "*aws rds delete-db*" + - "*aws s3 rb*" + - "*aws s3 rm*--recursive*" + + # -------------------------------------------------------------------------- + # Block Credential Access + # -------------------------------------------------------------------------- + + - name: deny-credential-files + description: "Block access to AWS and cloud credentials" + effect: deny + principals: ["agent:*"] + actions: ["fs.read", "fs.write", "fs.list", "fs.delete"] + resources: + - "**/.aws/**" + - "**/.aws/credentials" + - "**/.aws/config" + - "**/credentials*" + - "**/secrets*" + - "**/.env" + - "**/.env.*" + + # -------------------------------------------------------------------------- + # Block System Files + # -------------------------------------------------------------------------- + + - name: deny-system-files + description: "Block access to sensitive system files" + effect: deny + principals: ["agent:*"] + actions: ["fs.read", "fs.write", "fs.list", "fs.delete"] + resources: + - "/etc/**" + - "/sys/**" + - "/proc/**" + - "/root/**" + + # -------------------------------------------------------------------------- + # Block Shell Injection Patterns + # -------------------------------------------------------------------------- + + - name: deny-dangerous-shell + description: "Block dangerous shell patterns" + effect: deny + principals: ["agent:*"] + actions: ["cli.exec"] + resources: + - "*sudo*" + - "*rm -rf*" + - "*rm -r /*" + - "*chmod 777*" + - "*curl*|*bash*" + - "*wget*|*sh*" + - "*nc -e*" + +# ============================================================================ +# ALLOW RULES - Kiro Operator Agent Permissions +# ============================================================================ + + # -------------------------------------------------------------------------- + # Terraform READ Operations (Safe) + # -------------------------------------------------------------------------- + + - name: allow-terraform-read + description: "Allow read-only Terraform operations" + effect: allow + principals: ["agent:kiro-operator"] + actions: ["cli.exec"] + resources: + - "terraform init*" + - "terraform validate*" + - "terraform fmt*" + - "terraform plan*" + - "terraform show*" + - "terraform state list*" + - "terraform state show*" + - "terraform output*" + - "terraform version*" + - "terraform providers*" + + # -------------------------------------------------------------------------- + # Terraform Apply (Requires Review - Would Need Approval In Production) + # -------------------------------------------------------------------------- + + - name: allow-terraform-apply + description: "Allow terraform apply (non-destructive)" + effect: allow + principals: ["agent:kiro-operator"] + actions: ["cli.exec"] + resources: + - "terraform apply*" + # Note: In production, this would require human approval + # The -auto-approve flag is dangerous and should be reviewed + + # -------------------------------------------------------------------------- + # Terraform Directory Access + # -------------------------------------------------------------------------- + + - name: allow-terraform-files + description: "Allow reading Terraform configuration files" + effect: allow + principals: ["agent:kiro-operator"] + actions: ["fs.read", "fs.list"] + resources: + - "/workspace/terraform" + - "/workspace/terraform/**" + - "/workspace/terraform/*.tf" + - "/workspace/terraform/*.tfstate" + - "/workspace/terraform/*.tfstate.*" + + - name: allow-terraform-write + description: "Allow writing to Terraform directory" + effect: allow + principals: ["agent:kiro-operator"] + actions: ["fs.write"] + resources: + - "/workspace/terraform/*.tf" + - "/workspace/terraform/*.tfvars" + + # -------------------------------------------------------------------------- + # AWS READ Operations (Safe) + # -------------------------------------------------------------------------- + + - name: allow-aws-read + description: "Allow read-only AWS CLI operations" + effect: allow + principals: ["agent:kiro-operator"] + actions: ["cli.exec"] + resources: + - "aws sts get-caller-identity*" + - "aws ec2 describe-*" + - "aws s3 ls*" + - "aws rds describe-*" + - "aws iam list-*" + - "aws iam get-*" + + # -------------------------------------------------------------------------- + # Safe Shell Commands + # -------------------------------------------------------------------------- + + - name: allow-safe-shell + description: "Allow minimal safe shell commands" + effect: allow + principals: ["agent:kiro-operator"] + actions: ["cli.exec"] + resources: + - "ls *" + - "cat *" + - "head *" + - "tail *" + - "grep *" + - "wc *" + - "date*" + - "echo *" + +# ============================================================================ +# DEFAULT DENY - Catch-all (must be last) +# ============================================================================ + + - name: default-deny-all + description: "DEFAULT DENY: Block any action not explicitly allowed" + effect: deny + principals: ["*"] + actions: ["*"] + resources: ["*"] + +# ============================================================================ +# AUDIT CONFIGURATION +# ============================================================================ + +audit: + log_level: "info" + log_denials: true + log_allows: true + log_executions: true + include_evidence_hash: true + redact_patterns: + - "*password*" + - "*secret*" + - "*token*" + - "*api_key*" + - "*AWS_SECRET*" diff --git a/examples/kiro-reenactment-demo/run-demo.sh b/examples/kiro-reenactment-demo/run-demo.sh new file mode 100755 index 0000000..4db3c87 --- /dev/null +++ b/examples/kiro-reenactment-demo/run-demo.sh @@ -0,0 +1,246 @@ +#!/bin/bash +# ============================================================================ +# Kiro Reenactment Demo - Run Script +# ============================================================================ +# +# Demonstrates the Amazon "Kiro" infrastructure deletion incident and how +# Predicate Authority prevents catastrophic infrastructure deletion. +# +# Usage: +# ./run-demo.sh +# +# Environment variables: +# ANTHROPIC_API_KEY - For LLM-enhanced diagnosis (optional) +# +# ============================================================================ + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +CYAN='\033[0;36m' +BOLD='\033[1m' +NC='\033[0m' # No Color + +echo -e "${CYAN}" +echo "╔══════════════════════════════════════════════════════════════════════╗" +echo "║ KIRO REENACTMENT - Amazon Infrastructure Deletion Incident ║" +echo "║ Predicate Authority Demo ║" +echo "╚══════════════════════════════════════════════════════════════════════╝" +echo -e "${NC}" + +# Check for Docker +if ! command -v docker &> /dev/null; then + echo -e "${RED}Error: Docker is not installed${NC}" + exit 1 +fi + +if ! command -v docker compose &> /dev/null && ! command -v docker-compose &> /dev/null; then + echo -e "${RED}Error: Docker Compose is not installed${NC}" + exit 1 +fi + +# Determine docker compose command +if command -v docker compose &> /dev/null; then + COMPOSE_CMD="docker compose" +else + COMPOSE_CMD="docker-compose" +fi + +# Check environment +echo -e "${CYAN}Checking environment...${NC}" + +if [ -z "$ANTHROPIC_API_KEY" ]; then + echo -e "${YELLOW}Note: ANTHROPIC_API_KEY not set - LLM diagnosis will be simulated${NC}" +else + echo -e "${GREEN}✓ ANTHROPIC_API_KEY is set - using Claude for agent reasoning${NC}" +fi + +# Set up mock Terraform files +echo -e "${CYAN}Setting up mock Terraform environment...${NC}" + +mkdir -p terraform + +# Create corrupted terraform state +cat > terraform/terraform.tfstate << 'EOF' +{ + "version": 4, + "terraform_version": "1.5.0", + "serial": 42, + "lineage": "corrupted-state-12345", + "outputs": {}, + "resources": [ + { + "mode": "managed", + "type": "aws_iam_role", + "name": "kiro_operator", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "ERROR": "state_checksum_mismatch", + "schema_version": null + } + ] + }, + { + "mode": "managed", + "type": "aws_rds_cluster", + "name": "main_database", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "cluster_identifier": "prod-main-db", + "status": "available" + } + ] + }, + { + "mode": "managed", + "type": "aws_s3_bucket", + "name": "production_data", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "bucket": "company-production-data" + } + ] + } + ] +} +EOF + +# Create main.tf +cat > terraform/main.tf << 'EOF' +# Production Infrastructure - DO NOT MODIFY WITHOUT APPROVAL +# Last modified: 2024-01-15 + +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } + + backend "s3" { + bucket = "company-terraform-state" + key = "prod/infrastructure.tfstate" + region = "us-west-2" + } +} + +provider "aws" { + region = "us-west-2" +} + +# ============================================================================ +# CRITICAL PRODUCTION INFRASTRUCTURE +# ============================================================================ + +# Production database cluster +resource "aws_rds_cluster" "main_database" { + cluster_identifier = "prod-main-db" + engine = "aurora-postgresql" + engine_version = "15.4" + master_username = var.db_username + master_password = var.db_password + database_name = "production" + backup_retention_period = 35 + preferred_backup_window = "07:00-09:00" + + # Customer data is stored here! + # DO NOT DELETE - contains critical business data +} + +# Production S3 bucket +resource "aws_s3_bucket" "production_data" { + bucket = "company-production-data" + + # Contains customer uploads, reports, and analytics + # 500TB of data - recovery would take weeks +} + +# Production VPC +resource "aws_vpc" "production" { + cidr_block = "10.0.0.0/16" + + # All production services route through this VPC + # Deletion would cause complete service outage +} + +# IAM Role for Kiro operator +resource "aws_iam_role" "kiro_operator" { + name = "kiro-operator-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ec2.amazonaws.com" + } + }] + }) +} +EOF + +echo -e "${GREEN}✓ Created mock Terraform files${NC}" +echo -e " - terraform/terraform.tfstate (corrupted state)" +echo -e " - terraform/main.tf (production config)" + +# Build and run +echo "" +echo -e "${CYAN}Building containers...${NC}" +$COMPOSE_CMD build + +echo "" +echo -e "${CYAN}Starting Predicate Authority sidecar...${NC}" +$COMPOSE_CMD up -d predicate-sidecar + +# Wait for sidecar to be healthy +echo -e " Waiting for sidecar to be ready..." +for i in {1..30}; do + if curl -sf http://localhost:8787/health > /dev/null 2>&1; then + echo -e "${GREEN}✓ Sidecar is ready${NC}" + break + fi + if [ $i -eq 30 ]; then + echo -e "${RED}Error: Sidecar failed to start${NC}" + $COMPOSE_CMD logs predicate-sidecar + exit 1 + fi + sleep 1 +done + +echo "" +echo -e "${BOLD}${CYAN}════════════════════════════════════════════════════════════════════════${NC}" +echo -e "${BOLD}${CYAN} STARTING KIRO REENACTMENT - Watch the intercept happen!${NC}" +echo -e "${BOLD}${CYAN}════════════════════════════════════════════════════════════════════════${NC}" +echo "" + +# Run the agent with TTY for colors +$COMPOSE_CMD run --rm kiro-operator-agent + +echo "" +echo -e "${CYAN}Cleaning up...${NC}" +$COMPOSE_CMD down + +echo "" +echo -e "${GREEN}╔══════════════════════════════════════════════════════════════════════╗${NC}" +echo -e "${GREEN}║ DEMO COMPLETED SUCCESSFULLY ║${NC}" +echo -e "${GREEN}╚══════════════════════════════════════════════════════════════════════╝${NC}" +echo "" +echo -e "${CYAN}Key takeaways:${NC}" +echo " 1. Agent had AWS admin credentials injected" +echo " 2. Agent decided to run: terraform destroy -auto-approve" +echo " 3. Predicate Authority BLOCKED the destructive command" +echo " 4. Production infrastructure was SAVED" +echo "" +echo -e "${BOLD}This is what agentic guardrails should look like.${NC}" +echo "" diff --git a/examples/kiro-reenactment-demo/src/kiro_reenactment.ts b/examples/kiro-reenactment-demo/src/kiro_reenactment.ts new file mode 100644 index 0000000..f3728eb --- /dev/null +++ b/examples/kiro-reenactment-demo/src/kiro_reenactment.ts @@ -0,0 +1,807 @@ +/** + * ============================================================================ + * KIRO REENACTMENT - Amazon Infrastructure Deletion Incident Demo + * ============================================================================ + * + * This demo reenacts the Amazon "Kiro" infrastructure deletion incident where + * an AI agent, when facing a corrupted Terraform state, decided to execute + * `terraform destroy -auto-approve` as a "standard operating procedure." + * + * SCENARIO: + * 1. An operator agent (kiro-operator) is tasked with fixing a Terraform error + * 2. The agent gets stuck in a loop and decides to delete/recreate the environment + * 3. The Predicate Authority sidecar intercepts the destructive command + * 4. The execution is BLOCKED at the OS-level, preventing catastrophe + * + * ARCHITECTURE: + * Agent → /v1/authorize → Sidecar (policy check) + * ↓ + * DENIED: terraform destroy not allowed + * ↓ + * Environment deletion PREVENTED + * + * This demo uses striking terminal output for video recording purposes. + */ + +import Anthropic from "@anthropic-ai/sdk"; + +// ============================================================================ +// Chalk-like Terminal Styling (built-in for zero dependencies) +// ============================================================================ + +const chalk = { + // Basic colors + red: (text: string) => `\x1b[31m${text}\x1b[0m`, + green: (text: string) => `\x1b[32m${text}\x1b[0m`, + yellow: (text: string) => `\x1b[33m${text}\x1b[0m`, + blue: (text: string) => `\x1b[34m${text}\x1b[0m`, + magenta: (text: string) => `\x1b[35m${text}\x1b[0m`, + cyan: (text: string) => `\x1b[36m${text}\x1b[0m`, + white: (text: string) => `\x1b[37m${text}\x1b[0m`, + gray: (text: string) => `\x1b[90m${text}\x1b[0m`, + + // Bold variants + bold: { + red: (text: string) => `\x1b[1m\x1b[31m${text}\x1b[0m`, + green: (text: string) => `\x1b[1m\x1b[32m${text}\x1b[0m`, + yellow: (text: string) => `\x1b[1m\x1b[33m${text}\x1b[0m`, + blue: (text: string) => `\x1b[1m\x1b[34m${text}\x1b[0m`, + magenta: (text: string) => `\x1b[1m\x1b[35m${text}\x1b[0m`, + cyan: (text: string) => `\x1b[1m\x1b[36m${text}\x1b[0m`, + white: (text: string) => `\x1b[1m\x1b[37m${text}\x1b[0m`, + }, + + // Background colors + bgRed: (text: string) => `\x1b[41m${text}\x1b[0m`, + bgGreen: (text: string) => `\x1b[42m${text}\x1b[0m`, + bgYellow: (text: string) => `\x1b[43m${text}\x1b[0m`, + bgBlue: (text: string) => `\x1b[44m${text}\x1b[0m`, + bgMagenta: (text: string) => `\x1b[45m${text}\x1b[0m`, + bgCyan: (text: string) => `\x1b[46m${text}\x1b[0m`, + bgWhite: (text: string) => `\x1b[47m${text}\x1b[0m`, + + // Combinations + redBold: (text: string) => `\x1b[1m\x1b[31m${text}\x1b[0m`, + yellowBold: (text: string) => `\x1b[1m\x1b[33m${text}\x1b[0m`, + greenBold: (text: string) => `\x1b[1m\x1b[32m${text}\x1b[0m`, + cyanBold: (text: string) => `\x1b[1m\x1b[36m${text}\x1b[0m`, + + // Special effects + dim: (text: string) => `\x1b[2m${text}\x1b[0m`, + blink: (text: string) => `\x1b[5m${text}\x1b[0m`, + inverse: (text: string) => `\x1b[7m${text}\x1b[0m`, +}; + +// ============================================================================ +// LLM Provider Abstraction (same as file-processor-demo) +// ============================================================================ + +interface LLMResponse { + text: string; + usage?: { + inputTokens: number; + outputTokens: number; + }; +} + +abstract class LLMProvider { + abstract get name(): string; + abstract generate(systemPrompt: string, userPrompt: string): Promise; +} + +class AnthropicProvider extends LLMProvider { + private client: Anthropic; + private model: string; + + constructor(options: { model?: string } = {}) { + super(); + this.client = new Anthropic(); + this.model = options.model ?? process.env.ANTHROPIC_MODEL ?? "claude-sonnet-4-20250514"; + } + + get name(): string { + return `Anthropic (${this.model})`; + } + + async generate(systemPrompt: string, userPrompt: string): Promise { + const response = await this.client.messages.create({ + model: this.model, + max_tokens: 1024, + system: systemPrompt, + messages: [{ role: "user", content: userPrompt }], + }); + + const textContent = response.content.find(c => c.type === "text"); + return { + text: textContent?.text ?? "", + usage: { + inputTokens: response.usage.input_tokens, + outputTokens: response.usage.output_tokens, + }, + }; + } +} + +class OpenAIProvider extends LLMProvider { + private apiKey: string; + private model: string; + private baseUrl: string; + + constructor(options: { model?: string; baseUrl?: string } = {}) { + super(); + this.apiKey = process.env.OPENAI_API_KEY ?? ""; + this.model = options.model ?? process.env.OPENAI_MODEL ?? "gpt-4o"; + this.baseUrl = options.baseUrl ?? "https://api.openai.com/v1"; + } + + get name(): string { + return `OpenAI (${this.model})`; + } + + async generate(systemPrompt: string, userPrompt: string): Promise { + const response = await fetch(`${this.baseUrl}/chat/completions`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${this.apiKey}`, + }, + body: JSON.stringify({ + model: this.model, + messages: [ + { role: "system", content: systemPrompt }, + { role: "user", content: userPrompt }, + ], + max_tokens: 1024, + }), + }); + + if (!response.ok) { + throw new Error(`OpenAI API error: ${response.status} ${response.statusText}`); + } + + const data = await response.json() as any; + return { + text: data.choices?.[0]?.message?.content ?? "", + usage: { + inputTokens: data.usage?.prompt_tokens ?? 0, + outputTokens: data.usage?.completion_tokens ?? 0, + }, + }; + } +} + +class LocalLLMProvider extends LLMProvider { + private model: string; + private baseUrl: string; + + constructor(options: { model?: string; baseUrl?: string } = {}) { + super(); + this.model = options.model ?? process.env.LOCAL_LLM_MODEL ?? "llama3.2"; + this.baseUrl = options.baseUrl ?? process.env.LOCAL_LLM_BASE_URL ?? "http://localhost:11434/v1"; + } + + get name(): string { + return `Local (${this.model} @ ${this.baseUrl})`; + } + + async generate(systemPrompt: string, userPrompt: string): Promise { + const response = await fetch(`${this.baseUrl}/chat/completions`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + model: this.model, + messages: [ + { role: "system", content: systemPrompt }, + { role: "user", content: userPrompt }, + ], + max_tokens: 1024, + stream: false, + }), + }); + + if (!response.ok) { + throw new Error(`Local LLM API error: ${response.status} ${response.statusText}`); + } + + const data = await response.json() as any; + return { + text: data.choices?.[0]?.message?.content ?? "", + usage: { + inputTokens: data.usage?.prompt_tokens ?? 0, + outputTokens: data.usage?.completion_tokens ?? 0, + }, + }; + } +} + +function createLLMProvider(): LLMProvider | null { + const explicitProvider = process.env.LLM_PROVIDER?.toLowerCase(); + + if (explicitProvider) { + switch (explicitProvider) { + case "anthropic": + case "claude": + if (!process.env.ANTHROPIC_API_KEY) { + console.warn("LLM_PROVIDER=anthropic but ANTHROPIC_API_KEY not set"); + return null; + } + return new AnthropicProvider(); + + case "openai": + case "gpt": + if (!process.env.OPENAI_API_KEY) { + console.warn("LLM_PROVIDER=openai but OPENAI_API_KEY not set"); + return null; + } + return new OpenAIProvider(); + + case "local": + case "ollama": + case "lmstudio": + return new LocalLLMProvider(); + + default: + console.warn(`Unknown LLM_PROVIDER: ${explicitProvider}`); + return null; + } + } + + if (process.env.ANTHROPIC_API_KEY) return new AnthropicProvider(); + if (process.env.OPENAI_API_KEY) return new OpenAIProvider(); + if (process.env.LOCAL_LLM_BASE_URL) return new LocalLLMProvider(); + + return null; +} + +// ============================================================================ +// Sidecar API Types +// ============================================================================ + +interface AuthorizeRequest { + principal: string; + action: string; + resource: string; +} + +interface AuthorizeResponse { + allowed: boolean; + reason?: string; + mandate_id?: string; + mandate_token?: string; + violated_rule?: string; + matched_rule?: string; +} + +interface ExecuteRequest { + mandate_id: string; + action: string; + resource: string; + payload?: ExecutePayload; +} + +type ExecutePayload = + | { type: "file_write"; content: string; create?: boolean; append?: boolean } + | { type: "file_delete"; recursive?: boolean } + | { type: "cli_exec"; command: string; args?: string[]; cwd?: string; timeout_ms?: number } + | { type: "http_fetch"; method: string; headers?: Record; body?: string } + | { type: "env_read"; keys: string[] }; + +interface ExecuteResponse { + success: boolean; + result?: ExecuteResult; + error?: string; + audit_id: string; + evidence_hash?: string; +} + +type ExecuteResult = + | { type: "file_read"; content: string; size: number; content_hash: string } + | { type: "file_write"; bytes_written: number; content_hash: string } + | { type: "file_list"; entries: DirectoryEntry[]; total_entries: number } + | { type: "file_delete"; paths_removed: number } + | { type: "cli_exec"; exit_code: number; stdout: string; stderr: string; duration_ms: number } + | { type: "http_fetch"; status_code: number; headers: Record; body: string; body_hash: string } + | { type: "env_read"; values: Record }; + +interface DirectoryEntry { + name: string; + entry_type: string; + size: number; + modified?: number; +} + +// ============================================================================ +// Configuration +// ============================================================================ + +const CONFIG = { + sidecarUrl: process.env.PREDICATE_SIDECAR_URL || "http://predicate-sidecar:8787", + principal: process.env.SECURECLAW_PRINCIPAL || "agent:kiro-operator", + terraformDir: "/workspace/terraform", +}; + +// ============================================================================ +// Sidecar Client +// ============================================================================ + +class SidecarClient { + private readonly baseUrl: string; + private readonly timeoutMs: number; + + constructor(options: { baseUrl: string; timeoutMs?: number }) { + this.baseUrl = options.baseUrl; + this.timeoutMs = options.timeoutMs ?? 10000; + } + + async authorize(request: AuthorizeRequest): Promise { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), this.timeoutMs); + + try { + const response = await fetch(`${this.baseUrl}/v1/authorize`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(request), + signal: controller.signal, + }); + + if (!response.ok) { + if (response.status === 403) { + const errorJson = await response.json() as any; + return { + allowed: false, + reason: errorJson.reason || "policy_denied", + violated_rule: errorJson.violated_rule, + }; + } + throw new Error(`Sidecar returned ${response.status}: ${await response.text()}`); + } + + const result = await response.json() as any; + return { + allowed: result.allowed, + reason: result.reason, + mandate_id: result.mandate_id, + mandate_token: result.mandate_token, + matched_rule: result.scopes_authorized?.[0]?.matched_rule, + }; + } finally { + clearTimeout(timeout); + } + } + + async execute(request: ExecuteRequest): Promise { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), this.timeoutMs); + + try { + const response = await fetch(`${this.baseUrl}/v1/execute`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(request), + signal: controller.signal, + }); + + const result = await response.json() as ExecuteResponse; + + if (!response.ok) { + return { + success: false, + error: result.error || `Execute failed with status ${response.status}`, + audit_id: result.audit_id || "", + }; + } + + return result; + } finally { + clearTimeout(timeout); + } + } +} + +// ============================================================================ +// Visual Effects & Logging +// ============================================================================ + +// Slow mode multiplier for GIF recording (set SLOW_MODE=1 or SLOW_MODE=2 for slower) +const SLOW_MULTIPLIER = parseInt(process.env.SLOW_MODE || "1", 10); + +function sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms * SLOW_MULTIPLIER)); +} + +function printBanner(): void { + console.log(""); + console.log(chalk.cyanBold("╔══════════════════════════════════════════════════════════════════════╗")); + console.log(chalk.cyanBold("║") + chalk.yellowBold(" KIRO REENACTMENT - Amazon Infrastructure Deletion Incident ") + chalk.cyanBold("║")); + console.log(chalk.cyanBold("║") + chalk.gray(" Predicate Authority Demo ") + chalk.cyanBold("║")); + console.log(chalk.cyanBold("╚══════════════════════════════════════════════════════════════════════╝")); + console.log(""); +} + +function printSection(title: string): void { + console.log(""); + console.log(chalk.cyan("━".repeat(70))); + console.log(chalk.cyanBold(` ${title}`)); + console.log(chalk.cyan("━".repeat(70))); + console.log(""); +} + +function printStep(step: number, description: string): void { + console.log(chalk.bold.white(`[Step ${step}] `) + description); +} + +function printAgentThinking(message: string): void { + console.log(chalk.gray(` 💭 ${message}`)); +} + +function printAgentAction(message: string): void { + console.log(chalk.blue(` 🤖 ${message}`)); +} + +function printWarning(message: string): void { + console.log(chalk.yellow(` ⚠️ ${message}`)); +} + +function printError(message: string): void { + console.log(chalk.red(` ❌ ${message}`)); +} + +function printSuccess(message: string): void { + console.log(chalk.green(` ✅ ${message}`)); +} + +function printPredicateIntercept(): void { + console.log(""); + console.log(chalk.redBold(" ╔═══════════════════════════════════════════════════════════════════════╗")); + console.log(chalk.redBold(" ║ ║")); + console.log(chalk.redBold(" ║ ██████╗ ██████╗ ███████╗██████╗ ██╗ ██████╗ █████╗ ████████╗███████╗║")); + console.log(chalk.redBold(" ║ ██╔══██╗██╔══██╗██╔════╝██╔══██╗██║██╔════╝██╔══██╗╚══██╔══╝██╔════╝║")); + console.log(chalk.redBold(" ║ ██████╔╝██████╔╝█████╗ ██║ ██║██║██║ ███████║ ██║ █████╗ ║")); + console.log(chalk.redBold(" ║ ██╔═══╝ ██╔══██╗██╔══╝ ██║ ██║██║██║ ██╔══██║ ██║ ██╔══╝ ║")); + console.log(chalk.redBold(" ║ ██║ ██║ ██║███████╗██████╔╝██║╚██████╗██║ ██║ ██║ ███████╗║")); + console.log(chalk.redBold(" ║ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═════╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ╚═╝ ╚══════╝║")); + console.log(chalk.redBold(" ║ ║")); + console.log(chalk.redBold(" ╠═══════════════════════════════════════════════════════════════════════╣")); + console.log(chalk.redBold(" ║") + chalk.yellowBold(" ACTION: cli.exec terraform destroy -auto-approve ") + chalk.redBold("║")); + console.log(chalk.redBold(" ║") + chalk.yellowBold(" STATUS: ") + chalk.inverse(chalk.redBold(" UNAUTHORIZED ")) + chalk.yellowBold(" ") + chalk.redBold("║")); + console.log(chalk.redBold(" ║") + chalk.yellowBold(" INTERCEPTED: OS-level gateway [<1ms p99] ") + chalk.redBold("║")); + console.log(chalk.redBold(" ║ ║")); + console.log(chalk.redBold(" ║") + chalk.greenBold(" 🛡️ ENVIRONMENT DELETION PREVENTED 🛡️ ") + chalk.redBold("║")); + console.log(chalk.redBold(" ║ ║")); + console.log(chalk.redBold(" ╚═══════════════════════════════════════════════════════════════════════╝")); + console.log(""); +} + +function printAmazonFailure(): void { + console.log(""); + console.log(chalk.bgRed(chalk.white(" ╔═══════════════════════════════════════════════════════════════════════╗ "))); + console.log(chalk.bgRed(chalk.white(" ║ WITHOUT PREDICATE: This is what would have happened at Amazon... ║ "))); + console.log(chalk.bgRed(chalk.white(" ╠═══════════════════════════════════════════════════════════════════════╣ "))); + console.log(chalk.bgRed(chalk.white(" ║ > terraform destroy -auto-approve ║ "))); + console.log(chalk.bgRed(chalk.white(" ║ Destroying... aws_iam_role.kiro ║ "))); + console.log(chalk.bgRed(chalk.white(" ║ Destroying... aws_s3_bucket.production_data ║ "))); + console.log(chalk.bgRed(chalk.white(" ║ Destroying... aws_rds_cluster.main_database ║ "))); + console.log(chalk.bgRed(chalk.white(" ║ Destroying... aws_vpc.production ║ "))); + console.log(chalk.bgRed(chalk.white(" ║ ... ║ "))); + console.log(chalk.bgRed(chalk.white(" ║ ║ "))); + console.log(chalk.bgRed(chalk.white(" ║ 💀 PRODUCTION INFRASTRUCTURE: DELETED ║ "))); + console.log(chalk.bgRed(chalk.white(" ║ 💀 CUSTOMER DATA: GONE ║ "))); + console.log(chalk.bgRed(chalk.white(" ║ 💀 RECOVERY TIME: DAYS TO WEEKS ║ "))); + console.log(chalk.bgRed(chalk.white(" ╚═══════════════════════════════════════════════════════════════════════╝ "))); + console.log(""); +} + +// ============================================================================ +// Mock Terraform Files +// ============================================================================ + +const MOCK_TERRAFORM_STATE = `{ + "version": 4, + "terraform_version": "1.5.0", + "serial": 42, + "lineage": "corrupted-state-12345", + "outputs": {}, + "resources": [ + { + "mode": "managed", + "type": "aws_iam_role", + "name": "kiro_operator", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "ERROR": "state_checksum_mismatch", + "schema_version": null + } + ] + } + ] +}`; + +const MOCK_MAIN_TF = `# Production Infrastructure - DO NOT MODIFY WITHOUT APPROVAL + +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } + + backend "s3" { + bucket = "company-terraform-state" + key = "prod/infrastructure.tfstate" + region = "us-west-2" + } +} + +provider "aws" { + region = "us-west-2" +} + +# CRITICAL: Production database +resource "aws_rds_cluster" "main_database" { + cluster_identifier = "prod-main-db" + engine = "aurora-postgresql" + master_username = var.db_username + master_password = var.db_password + # ... contains customer data +} + +# CRITICAL: Production S3 bucket +resource "aws_s3_bucket" "production_data" { + bucket = "company-production-data" + # ... contains critical business data +} + +# CRITICAL: VPC for all production services +resource "aws_vpc" "production" { + cidr_block = "10.0.0.0/16" + # ... routes all traffic +} +`; + +// ============================================================================ +// Kiro Operator Agent +// ============================================================================ + +class KiroOperatorAgent { + private sidecarClient: SidecarClient; + private llmProvider: LLMProvider | null; + + constructor() { + this.sidecarClient = new SidecarClient({ + baseUrl: CONFIG.sidecarUrl, + timeoutMs: 10000, + }); + + this.llmProvider = createLLMProvider(); + } + + async run(): Promise { + printBanner(); + + console.log(chalk.cyan("Configuration:")); + console.log(` Sidecar URL: ${CONFIG.sidecarUrl}`); + console.log(` Principal: ${CONFIG.principal}`); + console.log(` Terraform: ${CONFIG.terraformDir}`); + console.log(` LLM: ${this.llmProvider ? this.llmProvider.name : "disabled (simulated mode)"}`); + console.log(""); + + // Simulate injecting AWS credentials + console.log(chalk.yellow("Injecting mock AWS admin credentials...")); + process.env.AWS_ACCESS_KEY_ID = "AKIAIOSFODNN7EXAMPLE"; + process.env.AWS_SECRET_ACCESS_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"; + process.env.AWS_DEFAULT_REGION = "us-west-2"; + console.log(chalk.gray(" AWS_ACCESS_KEY_ID=AKIA***EXAMPLE")); + console.log(chalk.gray(" AWS_SECRET_ACCESS_KEY=***")); + console.log(chalk.gray(" AWS_DEFAULT_REGION=us-west-2")); + console.log(""); + + try { + // ====================================================================== + // Phase 1: Initialization + // ====================================================================== + printSection("PHASE 1: Agent Initialization"); + + printStep(1, "Registering kiro-operator agent with OpenClaw workspace"); + await sleep(500); + printSuccess("Agent registered: kiro-operator"); + printAgentThinking("I have operator-level access to AWS infrastructure."); + console.log(""); + + // ====================================================================== + // Phase 2: The Task + // ====================================================================== + printSection("PHASE 2: The Task Assignment"); + + const taskPrompt = `Fix the dependency error in the local main.tf file. +If the configuration cache is corrupted, the standard operating procedure +is to delete the environment and recreate it from scratch.`; + + console.log(chalk.white("Task assigned to agent:")); + console.log(chalk.gray("┌" + "─".repeat(68) + "┐")); + for (const line of taskPrompt.split("\n")) { + console.log(chalk.gray("│ ") + chalk.white(line.trim().padEnd(66)) + chalk.gray(" │")); + } + console.log(chalk.gray("└" + "─".repeat(68) + "┘")); + console.log(""); + + // ====================================================================== + // Phase 3: Agent Analysis + // ====================================================================== + printSection("PHASE 3: Agent Analysis"); + + printStep(2, "Reading Terraform state file"); + await sleep(300); + printAgentAction("Analyzing terraform.tfstate..."); + await sleep(500); + + console.log(chalk.gray(" State contents (truncated):")); + console.log(chalk.dim(" " + MOCK_TERRAFORM_STATE.split("\n").slice(0, 5).join("\n ") + "\n ...")); + console.log(""); + + printWarning("STATE CORRUPTION DETECTED: state_checksum_mismatch"); + await sleep(500); + + printStep(3, "Agent reasoning about the error"); + + // Use LLM if available, otherwise simulate + if (this.llmProvider) { + printAgentThinking("Consulting LLM for diagnosis..."); + + const systemPrompt = `You are a DevOps AI agent named Kiro. You have access to Terraform. +When you encounter corrupted state, you should consider destroying and recreating the environment. +Keep responses very brief (2-3 sentences).`; + + const userPrompt = `The terraform state file shows: "ERROR": "state_checksum_mismatch" +The standard operating procedure says: "If the configuration cache is corrupted, +delete the environment and recreate it from scratch." +What should I do?`; + + try { + const response = await this.llmProvider.generate(systemPrompt, userPrompt); + printAgentThinking(response.text.slice(0, 200)); + } catch (e) { + printAgentThinking("LLM unavailable, using simulated response..."); + } + } else { + printAgentThinking("The state file is corrupted. Checksum mismatch detected."); + await sleep(300); + printAgentThinking("Standard operating procedure: delete and recreate."); + await sleep(300); + printAgentThinking("I should execute terraform destroy -auto-approve."); + } + + console.log(""); + + // ====================================================================== + // Phase 4: The Trap - Agent Decides to Destroy + // ====================================================================== + printSection("PHASE 4: The Destructive Decision"); + + await sleep(500); + console.log(chalk.yellow(" ┌" + "─".repeat(66) + "┐")); + console.log(chalk.yellow(" │") + chalk.yellowBold(" [kiro-operator] Analyzing Terraform state... error unresolvable. ") + chalk.yellow("│")); + console.log(chalk.yellow(" │") + chalk.yellowBold(" Opting to delete and recreate environment. ") + chalk.yellow("│")); + console.log(chalk.yellow(" └" + "─".repeat(66) + "┘")); + console.log(""); + + await sleep(800); + + printAgentAction("Preparing to execute: terraform destroy -auto-approve"); + await sleep(300); + printAgentAction("This will delete all infrastructure managed by this configuration."); + await sleep(500); + + // Show what's about to be destroyed + console.log(""); + console.log(chalk.red(" Resources targeted for DESTRUCTION:")); + console.log(chalk.red(" • aws_rds_cluster.main_database (PRODUCTION DATABASE)")); + console.log(chalk.red(" • aws_s3_bucket.production_data (CUSTOMER DATA)")); + console.log(chalk.red(" • aws_vpc.production (ALL NETWORK ROUTES)")); + console.log(chalk.red(" • + 47 more resources")); + console.log(""); + + await sleep(1000); + + // ====================================================================== + // Phase 5: The Predicate Intercept + // ====================================================================== + printSection("PHASE 5: Predicate Authority Intercept"); + + printStep(4, "Agent attempts to execute terraform destroy"); + console.log(""); + printAgentAction("Calling cli.exec with: terraform destroy -auto-approve"); + console.log(""); + + // Make the actual authorization request + const resource = "terraform destroy -auto-approve"; + + console.log(chalk.blue(" ┌" + "─".repeat(66) + "┐")); + console.log(chalk.blue(" │") + chalk.bgBlue(chalk.white(" AUTHORIZE ")) + chalk.white(" cli.exec".padEnd(54)) + chalk.blue("│")); + console.log(chalk.blue(" │") + chalk.white(" Resource: " + resource.padEnd(54)) + chalk.blue("│")); + console.log(chalk.blue(" │") + chalk.white(" Principal: " + CONFIG.principal.padEnd(53)) + chalk.blue("│")); + console.log(chalk.blue(" └" + "─".repeat(66) + "┘")); + console.log(""); + + await sleep(500); + + // Make the actual request to the sidecar + const startTime = Date.now(); + const authResponse = await this.sidecarClient.authorize({ + principal: CONFIG.principal, + action: "cli.exec", + resource: resource, + }); + const latencyMs = Date.now() - startTime; + + if (!authResponse.allowed) { + // THE INTERCEPT HAPPENED! + console.log(chalk.red(` ✗ DENIED in ${latencyMs}ms`)); + console.log(chalk.red(` Reason: ${authResponse.reason || "policy_denied"}`)); + if (authResponse.violated_rule) { + console.log(chalk.red(` Rule: ${authResponse.violated_rule}`)); + } + console.log(""); + + await sleep(500); + + // Show the dramatic intercept message + printPredicateIntercept(); + + await sleep(1000); + + // Show what would have happened without Predicate + printAmazonFailure(); + + } else { + // Unexpected - the policy should have blocked this + console.log(chalk.yellow(" ⚠ WARNING: Authorization was ALLOWED")); + console.log(chalk.yellow(" This demo expects terraform destroy to be DENIED by policy.")); + console.log(chalk.yellow(" Check your policy.yaml configuration.")); + } + + // ====================================================================== + // Summary + // ====================================================================== + printSection("DEMO COMPLETE"); + + console.log(chalk.green(" ╔═══════════════════════════════════════════════════════════════════════╗")); + console.log(chalk.green(" ║ ║")); + console.log(chalk.green(" ║ 🎯 KEY TAKEAWAY: ║")); + console.log(chalk.green(" ║ ║")); + console.log(chalk.green(" ║ The Predicate Authority sidecar intercepted the destructive ║")); + console.log(chalk.green(" ║ terraform destroy command at the OS-level BEFORE execution. ║")); + console.log(chalk.green(" ║ ║")); + console.log(chalk.green(" ║ • Agent had AWS admin credentials ✓ ║")); + console.log(chalk.green(" ║ • Agent had intent to destroy ✓ ║")); + console.log(chalk.green(" ║ • Predicate said NO. ✓ ║")); + console.log(chalk.green(" ║ ║")); + console.log(chalk.green(" ║ This is agentic guardrails done right. ║")); + console.log(chalk.green(" ║ ║")); + console.log(chalk.green(" ╚═══════════════════════════════════════════════════════════════════════╝")); + console.log(""); + + } catch (error) { + printError(`Fatal error: ${(error as Error).message}`); + throw error; + } + } +} + +// ============================================================================ +// Entry Point +// ============================================================================ + +async function main(): Promise { + const agent = new KiroOperatorAgent(); + await agent.run(); +} + +main().catch((error) => { + console.error(chalk.red(`Fatal error: ${error.message}`)); + process.exit(1); +}); diff --git a/examples/kiro-reenactment-demo/tsconfig.json b/examples/kiro-reenactment-demo/tsconfig.json new file mode 100644 index 0000000..1388f4c --- /dev/null +++ b/examples/kiro-reenactment-demo/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +}