Skip to content
Open
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
41 changes: 37 additions & 4 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,53 @@
"name": "aws-samples"
},
"metadata": {
"description": "A curated marketplace of Claude Code plugins for AWS development skills, sub-agents, and MCP servers for building on AWS.",
"version": "0.3.0"
"description": "A curated marketplace of Claude Code plugins for AWS development \u2014 skills, sub-agents, and MCP servers for building on AWS.",
"version": "0.4.0"
},
"plugins": [
{
"name": "aws-dev-toolkit",
"source": "./plugins/aws-dev-toolkit",
"description": "AWS development toolkit 34 skills, 11 agents, and 3 MCP servers for building, migrating, and performing architecture reviews on AWS.",
"description": "AWS development toolkit \u2014 34 skills, 11 agents, and 3 MCP servers for building, migrating, and performing architecture reviews on AWS.",
"version": "0.3.0",
"author": {
"name": "aws-samples"
},
"keywords": ["aws", "iac", "cdk", "cloudformation", "terraform", "serverless", "well-architected", "migration", "gcp", "azure", "bedrock"],
"keywords": [
"aws",
"iac",
"cdk",
"cloudformation",
"terraform",
"serverless",
"well-architected",
"migration",
"gcp",
"azure",
"bedrock"
],
"category": "cloud-development"
},
{
"name": "fail-closed-security-hooks",
"source": "./plugins/fail-closed-security-hooks",
"description": "Fail-closed runtime guardrails \u2014 PII/secrets prompt guard (7 jurisdictions), git policy guard, tamper-evident HMAC-chained audit log (CloudWatch/SIEM dual-write), and a token-budget circuit breaker. For startups touching regulated data.",
"version": "1.0.0",
"author": {
"name": "Tim Wu"
},
"keywords": [
"security",
"guardrails",
"compliance",
"pii",
"audit",
"fintech",
"healthcare",
"bedrock",
"devsecops"
],
"category": "security"
}
]
}
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,22 @@ Add to `~/.kiro/settings/mcp.json` (user-level) or `.kiro/settings/mcp.json` (wo
| `awsknowledge` | http | `https://knowledge-mcp.global.api.aws` | AWS documentation search, service recommendations, and regional availability |
| `awspricing` | stdio | `awslabs.aws-pricing-mcp-server` | AWS service pricing data, cost reports, and IaC cost analysis |

### fail-closed-security-hooks

Runtime guardrails on what the agent *does* — the layer a startup needs the moment it touches regulated data (fintech, health). Five hooks via a fail-closed telemetry shim: a PII/secrets prompt guard (credentials + national IDs across 7 jurisdictions), a git policy guard, a tamper-evident HMAC-chained audit log (optional CloudWatch/SIEM dual-write), and a per-session token-budget circuit breaker. A broken control fails *safe*, not open.

```bash
/plugin install fail-closed-security-hooks@aws-samples
```

Or load locally during development:

```bash
claude --plugin-dir ./plugins/fail-closed-security-hooks
```

See [plugins/fail-closed-security-hooks/README.md](plugins/fail-closed-security-hooks/README.md) for configuration and the audit-chain verifier.

## Prerequisites

- [Claude Code](https://code.claude.com) v1.0.33+
Expand Down
12 changes: 12 additions & 0 deletions plugins/fail-closed-security-hooks/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "fail-closed-security-hooks",
"version": "1.0.0",
"description": "Fail-closed guardrails for what the agent does: PII/secrets prompt guard (US/UK/JP/KR/SG/EU/AU national IDs + credentials), git policy guard, tamper-evident HMAC-chained audit log with optional CloudWatch/SIEM dual-write, and a per-session token-budget circuit breaker — all wrapped by a telemetry shim that turns silent hook failures into explicit denials. For startups touching regulated data (fintech, health).",
"author": {
"name": "Tim Wu"
},
"homepage": "https://github.com/timwukp/claude-code-on-aws-bedrock-best-practices",
"repository": "https://github.com/timwukp/claude-code-on-aws-bedrock-best-practices",
"keywords": ["security", "guardrails", "compliance", "pii", "audit", "fintech", "healthcare", "aws-bedrock", "devsecops"],
"license": "MIT-0"
}
16 changes: 16 additions & 0 deletions plugins/fail-closed-security-hooks/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
MIT No Attribution

Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
59 changes: 59 additions & 0 deletions plugins/fail-closed-security-hooks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# fail-closed-security-hooks

Guardrails on **what the agent does**, not just what it can build. Five hooks wired through a fail-closed telemetry shim — the layer a startup needs the moment it touches regulated data (fintech, health).

```bash
/plugin install fail-closed-security-hooks@aws-samples
```

Or load locally during development:

```bash
claude --plugin-dir ./plugins/fail-closed-security-hooks
```

## What it does

| Hook | Events | What it enforces |
|---|---|---|
| **pii-guard** | UserPromptSubmit, PreToolUse | Scans prompts and tool inputs for secrets (AWS keys, private keys, JWTs, DB connection strings, credit cards) and **national identifiers across the US, UK, Japan, South Korea, Singapore, EU (IBAN), and Australia**, then **blocks before the content reaches the model**. Every pattern is individually disable-able. |
| **git-guard** | PreToolUse (Bash) | Remote-URL allowlist, force-push prevention, protected-branch enforcement, and destructive-op blocking (`reset --hard`, `clean -f`, forced checkout). |
| **audit-logger** | UserPromptSubmit, PostToolUse | **Tamper-evident HMAC-SHA256 hash-chained** JSONL audit log. Any post-hoc edit, deletion, reorder, or insertion breaks the chain forward and is caught by `scripts/chain-verify.sh`. Optional dual-write to CloudWatch + SIEM. |
| **token-budget-guard** | PreToolUse, PostToolUse | Per-session circuit breaker. Blocks further tool calls once a token or call budget is exceeded — a backstop against runaway agent loops. |
| **hook-wrapper** | wraps all of the above | Telemetry shim. Emits per-hook timing/exit JSON and **converts silent hook crashes/timeouts into explicit `exit 2` denials** (fail-closed), so a broken control fails *safe* instead of failing *open*. |

## Quick start

```bash
# 1. Provide an HMAC key for the tamper-evident audit chain
export AUDIT_HMAC_KEY="$(openssl rand -hex 32)"

# 2. (optional) git-guard policy
export GIT_GUARD_ALLOWED_DOMAINS="github.com,gitlab.com"
export GIT_GUARD_PROTECTED_BRANCHES="main,master,release/*"

# 3. Verify the audit chain any time
bash scripts/chain-verify.sh ~/.claude/claude-code-security/audit.jsonl # exit 0 = intact, 1 = tampered
```

Default state/log paths are user-writable (`~/.claude/claude-code-security/`) for evaluation. For **un-removable, fleet-wide enforcement**, deploy the same hooks via `managed-settings.json` with root-owned paths — the hook logic is identical; only the trust boundary and defaults change. See the upstream repo for Terraform IaC, managed-settings, a CloudWatch dashboard, and a STRIDE threat model.

## Configuration

| Env var | Hook | Default |
|---|---|---|
| `AUDIT_HMAC_KEY` | audit-logger | dev key auto-generated under state dir |
| `CLAUDE_AUDIT_CLOUDWATCH_GROUP` | audit-logger | (off) |
| `CLAUDE_AUDIT_SIEM_REQUIRED` | audit-logger | (off) |
| `GIT_GUARD_ALLOWED_DOMAINS` | git-guard | `github.com,gitlab.com,bitbucket.org` |
| `GIT_GUARD_PROTECTED_BRANCHES` | git-guard | `main,master,release/*,production` |
| `CLAUDE_TOKEN_BUDGET` / `CLAUDE_CALL_BUDGET` | token-budget-guard | `1000000` / `500` |
| `CLAUDE_HOOK_TIMEOUT_MS` | hook-wrapper | `5000` |

## Testing

Upstream ships a 76-assertion test suite (`claude plugin validate --strict` clean): https://github.com/timwukp/claude-code-on-aws-bedrock-best-practices

## License

MIT-0 (contributed copy). Upstream source is Apache-2.0.
168 changes: 168 additions & 0 deletions plugins/fail-closed-security-hooks/hooks/audit-logger.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
#!/usr/bin/env bash
# =============================================================================
# Audit Logger — Tamper-evident, fail-closed audit log for Claude Code
# =============================================================================
# Hook version: 2.0.0
# Last updated: 2026-05-29
# Compatible with: claude-code 2.1.150+
# Dependencies: bash 4+, jq, openssl (HMAC), aws CLI (optional, for CloudWatch)
# Maintainer: <your security team email>
# Change log:
# 2.0.0 (2026-05-29) — HMAC chain, fail-closed, mandatory SIEM, CloudWatch dual-write,
# prev_hash stored under /var/lib/claude-code/audit-state
# 1.0.0 (2026-05-28) — initial release
# =============================================================================
# Each event line is a JSON object that includes:
# - prev_hash: HMAC-SHA256 of the previous event line (or "GENESIS")
# - hmac: HMAC-SHA256(key, prev_hash || event_body_canonical)
# Verifying the chain (chain-verify.sh) detects ANY post-hoc edit, deletion,
# reorder, or insertion (the smallest tamper breaks the hash chain forward).
#
# Key sources (in order):
# 1. AUDIT_HMAC_KEY env (must be set in managed-settings, NOT user-readable)
# 2. /etc/claude-code/audit-key (root:audit 0640)
# 3. AWS Secrets Manager (audit-hmac-key) — fetched if neither above present
#
# Fail-closed: if we cannot append to the local log AND cannot ship to
# CloudWatch, exit 2 to block the tool. Audit-evasion is treated as a
# deny-worthy condition by default. Set CLAUDE_AUDIT_FAIL_OPEN=1 to override
# (NOT recommended in production).
#
# Mandatory SIEM check (P0-2):
# Set CLAUDE_AUDIT_SIEM_REQUIRED=1 to refuse startup unless either:
# - CLAUDE_AUDIT_CLOUDWATCH_GROUP is set AND aws CLI works, OR
# - CLAUDE_AUDIT_ALERT_CMD is set
# =============================================================================

set -u
input=$(cat)

# Defaults are user-writable for plugin / opt-in mode. For enterprise
# fail-closed enforcement, override these in managed-settings to root-owned
# paths (/var/log/claude-code, /var/lib/claude-code) — see the full repo.
LOG_FILE="${CLAUDE_AUDIT_LOG:-$HOME/.claude/claude-code-security/audit.jsonl}"
STATE_DIR="${CLAUDE_AUDIT_STATE:-$HOME/.claude/claude-code-security/audit-state}"
CW_GROUP="${CLAUDE_AUDIT_CLOUDWATCH_GROUP:-}"
CW_STREAM="${CLAUDE_AUDIT_CLOUDWATCH_STREAM:-$(hostname 2>/dev/null || echo unknown)}"
ALERT_CMD="${CLAUDE_AUDIT_ALERT_CMD:-}"
SIEM_REQ="${CLAUDE_AUDIT_SIEM_REQUIRED:-0}"
FAIL_OPEN="${CLAUDE_AUDIT_FAIL_OPEN:-0}"

mkdir -p "$(dirname "$LOG_FILE")" "$STATE_DIR" 2>/dev/null || true

# --- Mandatory SIEM check -----------------------------------------------------
if [[ "$SIEM_REQ" == "1" ]]; then
ok=0
[[ -n "$CW_GROUP" ]] && command -v aws >/dev/null 2>&1 && ok=1
[[ -n "$ALERT_CMD" ]] && ok=1
if [[ "$ok" != "1" ]]; then
echo "audit-logger: SIEM forwarding required but no working forwarder configured" >&2
[[ "$FAIL_OPEN" != "1" ]] && exit 2
fi
fi

# --- Resolve HMAC key ---------------------------------------------------------
get_key() {
if [[ -n "${AUDIT_HMAC_KEY:-}" ]]; then printf '%s' "$AUDIT_HMAC_KEY"; return; fi
if [[ -r /etc/claude-code/audit-key ]]; then cat /etc/claude-code/audit-key; return; fi
if command -v aws >/dev/null 2>&1; then
aws secretsmanager get-secret-value --secret-id audit-hmac-key \
--query SecretString --output text 2>/dev/null && return
fi
# Final fallback: derive from hostname+install marker (NOT cryptographically
# secure but lets unit tests and dev environments run). Marker can be created
# at install time so the same machine produces a stable key.
if [[ -r "$STATE_DIR/key.dev" ]]; then cat "$STATE_DIR/key.dev"; return; fi
return 1
}

KEY=$(get_key 2>/dev/null || true)
if [[ -z "$KEY" ]]; then
# Generate a dev key on first run so the chain is stable per machine
if command -v openssl >/dev/null 2>&1; then
openssl rand -hex 32 > "$STATE_DIR/key.dev" 2>/dev/null || true
chmod 0600 "$STATE_DIR/key.dev" 2>/dev/null || true
KEY=$(cat "$STATE_DIR/key.dev" 2>/dev/null || true)
fi
fi
if [[ -z "$KEY" ]]; then
echo "audit-logger: no HMAC key available" >&2
[[ "$FAIL_OPEN" != "1" ]] && exit 2
fi

# --- Parse fields -------------------------------------------------------------
event=""; session_id=""; cwd=""; tool_name=""; command_str=""
if command -v jq >/dev/null 2>&1; then
event=$(printf '%s' "$input" | jq -r '.hook_event_name // empty')
session_id=$(printf '%s' "$input" | jq -r '.session_id // empty')
cwd=$(printf '%s' "$input" | jq -r '.cwd // empty')
tool_name=$(printf '%s' "$input" | jq -r '.tool_name // empty')
case "$event" in
PostToolUse) command_str=$(printf '%s' "$input" | jq -r '.tool_input.command // .tool_input.file_path // empty') ;;
UserPromptSubmit) command_str=$(printf '%s' "$input" | jq -r '.prompt // empty' | head -c 500) ;;
esac
fi

ts=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ" 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ")
user=$(whoami 2>/dev/null || echo unknown)
host=$(hostname 2>/dev/null || echo unknown)

# --- Read previous hash -------------------------------------------------------
PREV_FILE="$STATE_DIR/last-hmac"
[[ ! -e "$PREV_FILE" ]] && echo "GENESIS" > "$PREV_FILE" 2>/dev/null
prev_hash=$(cat "$PREV_FILE" 2>/dev/null || echo "GENESIS")

# --- Build canonical body, compute HMAC ---------------------------------------
body=$(jq -nc \
--arg ts "$ts" --arg user "$user" --arg host "$host" \
--arg event "$event" --arg session "$session_id" --arg cwd "$cwd" \
--arg tool "$tool_name" --arg cmd "$command_str" --arg prev "$prev_hash" \
'{ts:$ts,user:$user,host:$host,event:$event,session_id:$session,cwd:$cwd,tool:$tool,action:$cmd,prev_hash:$prev}')

hmac=$(printf '%s' "$body" | openssl dgst -sha256 -hmac "$KEY" 2>/dev/null \
| awk '{print $NF}')
if [[ -z "$hmac" ]]; then
echo "audit-logger: HMAC computation failed" >&2
[[ "$FAIL_OPEN" != "1" ]] && exit 2
fi

entry=$(printf '%s' "$body" | jq -c --arg h "$hmac" '. + {hmac:$h}')

# --- Write locally (best-effort) ---------------------------------------------
local_ok=0
if echo "$entry" >> "$LOG_FILE" 2>/dev/null; then
echo "$hmac" > "$PREV_FILE" 2>/dev/null
local_ok=1
fi
# Try fallback path if root path failed
if [[ "$local_ok" != "1" ]]; then
fb="$HOME/.claude/audit-fallback.jsonl"
mkdir -p "$(dirname "$fb")" 2>/dev/null || true
if echo "$entry" >> "$fb" 2>/dev/null; then
echo "$hmac" > "$PREV_FILE" 2>/dev/null
local_ok=1
fi
fi

# --- Ship to CloudWatch (best-effort) ----------------------------------------
cw_ok=0
if [[ -n "$CW_GROUP" ]] && command -v aws >/dev/null 2>&1; then
ts_ms=$(python3 -c 'import time;print(int(time.time()*1000))' 2>/dev/null || echo 0)
aws logs put-log-events \
--log-group-name "$CW_GROUP" --log-stream-name "$CW_STREAM" \
--log-events "timestamp=$ts_ms,message=$(printf '%s' "$entry" | sed 's/"/\\"/g')" \
>/dev/null 2>&1 && cw_ok=1
fi

# --- Optional alert webhook --------------------------------------------------
if [[ -n "$ALERT_CMD" ]]; then
printf '%s\n' "$entry" | bash -c "$ALERT_CMD" >/dev/null 2>&1 || true
fi

# --- Fail-closed if both local AND remote failed ------------------------------
if [[ "$local_ok" != "1" && "$cw_ok" != "1" ]]; then
echo "audit-logger: cannot persist event locally or remotely; blocking tool" >&2
[[ "$FAIL_OPEN" != "1" ]] && exit 2
fi

exit 0
Loading