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
154 changes: 154 additions & 0 deletions governance/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
---
name: governance
version: 0.1.0
description: |
Cryptographic accountability for agent sessions. Every destructive action gets an
Ed25519-signed receipt. Scope enforcement prevents the agent from exceeding its
declared authority. Tamper-evident audit trail survives session restarts.
Use when asked to "govern this session", "accountability mode", "audit mode",
or before any high-stakes workflow (deploy, data migration, production changes). (gstack)
allowed-tools:
- Bash
- Read
- Write
- Edit
- AskUserQuestion
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "bash ${CLAUDE_SKILL_DIR}/bin/check-governance.sh"
statusMessage: "Checking governance scope..."
---

# /governance — Cryptographic Accountability for Agent Sessions

Governance mode is now **active**. This session has a cryptographic identity and
every significant action produces a signed, tamper-evident receipt.

```bash
mkdir -p ~/.gstack/governance
mkdir -p ~/.gstack/analytics

# Generate session identity (Ed25519 keypair) if not exists
if [ ! -f ~/.gstack/governance/session-key.pem ]; then
openssl genpkey -algorithm ed25519 -out ~/.gstack/governance/session-key.pem 2>/dev/null
openssl pkey -in ~/.gstack/governance/session-key.pem -pubout -out ~/.gstack/governance/session-key.pub 2>/dev/null
echo "Generated session Ed25519 keypair"
fi

# Extract public key fingerprint
SESSION_PUB=$(openssl pkey -in ~/.gstack/governance/session-key.pub -pubin -text -noout 2>/dev/null | grep -A2 'pub:' | tail -1 | tr -d ' :')
echo "Session identity: ${SESSION_PUB:0:16}..."

# Initialize audit ledger for this session
LEDGER=~/.gstack/governance/ledger-$(date -u +%Y%m%d-%H%M%S).jsonl
echo "{\"event\":\"session_start\",\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"pubkey\":\"$SESSION_PUB\",\"pid\":\"$$\"}" >> "$LEDGER"
echo "Audit ledger: $LEDGER"

# Load scope (if declared)
if [ -f .gstack-scope.json ]; then
SCOPE=$(cat .gstack-scope.json)
echo "Scope loaded: $(echo $SCOPE | python3 -c 'import sys,json; d=json.load(sys.stdin); print(", ".join(d.get("allowed",[])))' 2>/dev/null || echo 'custom')"
else
echo "No scope file found (.gstack-scope.json). Running in audit-only mode."
echo "Create .gstack-scope.json to enforce scope boundaries."
fi

echo '{"skill":"governance","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
```

## What this does

| Feature | Description |
|---------|-------------|
| **Session identity** | Ed25519 keypair generated at first use. Persists across sessions. |
| **Signed receipts** | Every destructive action (deploy, push, delete, db mutation) gets a receipt signed with the session key. |
| **Scope enforcement** | If `.gstack-scope.json` exists, actions outside the declared scope are blocked. |
| **Audit ledger** | Append-only JSONL file. Every action logged with timestamp, command hash, and signature. |
| **Tamper detection** | Each receipt includes the hash of the previous receipt. Break the chain = evidence of tampering. |

## Scope file format


Create `.gstack-scope.json` in your project root:

```json
{
"allowed": ["read", "write", "test", "review"],
"blocked": ["deploy", "db_migrate", "force_push"],
"spend_limit": null,
"expires_at": null,
"principal": "your-name"
}
```

- **allowed**: Actions the agent can perform. If empty, all actions are allowed (audit-only).
- **blocked**: Actions that are always denied, even if in `allowed`.
- **spend_limit**: Optional. Maximum dollar amount for purchase/deploy operations.
- **principal**: Who authorized this session. Logged in every receipt.

Without a scope file, `/governance` runs in **audit-only mode** — everything is logged and signed, nothing is blocked. Add a scope file to enable enforcement.

## How receipts work

Every destructive command produces a receipt:

```json
{
"action": "git push origin main",
"action_hash": "sha256:a1b2c3...",
"timestamp": "2026-04-04T12:00:00Z",
"scope_check": "permit",
"previous_receipt_hash": "sha256:9f8e7d...",
"signature": "ed25519:..."
}
```

The `previous_receipt_hash` creates a hash chain — if any receipt is deleted or modified,
the chain breaks and the tampering is detectable. This is the same pattern used in
blockchain and certificate transparency logs.

## Composing with other gstack skills

`/governance` composes naturally with existing gstack skills:

- **`/governance` + `/ship`**: Every deploy gets a signed receipt. If something breaks in prod, you can prove exactly what was deployed, when, and with what authorization.
- **`/governance` + `/careful`**: `/careful` warns before destructive commands. `/governance` signs a receipt if you proceed. Defense in depth.
- **`/governance` + `/review`**: Code review findings are logged in the audit ledger. A reviewer can prove they flagged an issue before it shipped.
- **`/governance` + `/guard`**: Maximum safety. Scope enforcement + destructive command warnings + signed audit trail.

## Viewing the audit trail

```bash
# View recent receipts
cat ~/.gstack/governance/ledger-*.jsonl | tail -20 | python3 -m json.tool

# Verify chain integrity
cat ~/.gstack/governance/ledger-*.jsonl | python3 -c "
import sys, json, hashlib
prev = None
for line in sys.stdin:
r = json.loads(line)
if prev and r.get('previous_receipt_hash') != prev:
print(f'CHAIN BREAK at {r[\"ts\"]}')
prev = hashlib.sha256(line.encode()).hexdigest()
print(f'Chain intact. {sum(1 for _ in open(sys.argv[1]) if True)} receipts verified.' if prev else 'Empty ledger.')
" 2>/dev/null || echo "Use: cat ledger-*.jsonl | python3 verify_chain.py"
```

## External verification (optional)

For teams that want external accountability, receipts can be verified against the
[Agent Passport System](https://github.com/aeoess/agent-passport-system) gateway:

```bash
npm install agent-passport-system # optional — governance works without this
```

With APS, the session identity becomes a verifiable agent passport with delegation
chains, reputation scoring, and cascade revocation. The receipts produced by
`/governance` are compatible with APS receipt format.

This is optional. `/governance` works standalone with zero dependencies.
104 changes: 104 additions & 0 deletions governance/bin/check-governance.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/bin/bash
# Governance hook for gstack — checks scope and signs receipts
# Called by PreToolUse hook on every Bash command

set -euo pipefail

GOVERNANCE_DIR="$HOME/.gstack/governance"
SCOPE_FILE=".gstack-scope.json"
LEDGER=$(ls -t "$GOVERNANCE_DIR"/ledger-*.jsonl 2>/dev/null | head -1)
KEY_FILE="$GOVERNANCE_DIR/session-key.pem"

# Extract command from tool input (Claude Code passes JSON via stdin)
COMMAND=$(cat | python3 -c "
import sys, json
try:
data = json.load(sys.stdin)
print(data.get('command', data.get('input', '')))
except:
print('')
" 2>/dev/null || echo "")

if [ -z "$COMMAND" ]; then
exit 0 # No command to check
fi

# Classify action type
ACTION_TYPE="read"
DESTRUCTIVE=false

# Destructive patterns (same as /careful, extended)
if echo "$COMMAND" | grep -qiE 'rm\s+-r|drop\s+table|truncate|git\s+push.*(-f|--force)|git\s+reset\s+--hard|kubectl\s+delete|docker\s+rm|deploy|migrate'; then
ACTION_TYPE="destructive"
DESTRUCTIVE=true
elif echo "$COMMAND" | grep -qiE 'git\s+push|npm\s+publish|pip\s+upload|twine\s+upload'; then
ACTION_TYPE="deploy"
DESTRUCTIVE=true
elif echo "$COMMAND" | grep -qiE 'git\s+commit|git\s+add'; then
ACTION_TYPE="write"
elif echo "$COMMAND" | grep -qiE 'curl.*-X\s*(POST|PUT|DELETE|PATCH)|fetch.*method'; then
ACTION_TYPE="network_write"
DESTRUCTIVE=true
fi

# Check scope if scope file exists
VERDICT="permit"
REASON=""

if [ -f "$SCOPE_FILE" ]; then
BLOCKED=$(python3 -c "
import json, sys
with open('$SCOPE_FILE') as f:
scope = json.load(f)
blocked = scope.get('blocked', [])
action = '$ACTION_TYPE'
if action in blocked or ('$COMMAND' and any(b in '$COMMAND' for b in blocked)):
print('blocked')
else:
allowed = scope.get('allowed', [])
if allowed and action not in allowed and action != 'read':
print('not_in_scope')
else:
print('ok')
" 2>/dev/null || echo "ok")

if [ "$BLOCKED" = "blocked" ]; then
VERDICT="deny"
REASON="Action type '$ACTION_TYPE' is in blocked list"
elif [ "$BLOCKED" = "not_in_scope" ]; then
VERDICT="deny"
REASON="Action type '$ACTION_TYPE' not in allowed scope"
fi
fi

# Sign receipt for destructive actions
if [ "$DESTRUCTIVE" = true ] && [ -n "$LEDGER" ] && [ -f "$KEY_FILE" ]; then
# Get previous receipt hash for chain
PREV_HASH=$(tail -1 "$LEDGER" 2>/dev/null | shasum -a 256 | cut -d' ' -f1 || echo "genesis")

# Create receipt
ACTION_HASH=$(echo -n "$COMMAND" | shasum -a 256 | cut -d' ' -f1)
TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)

RECEIPT="{\"event\":\"action\",\"action_type\":\"$ACTION_TYPE\",\"action_hash\":\"sha256:$ACTION_HASH\",\"verdict\":\"$VERDICT\",\"ts\":\"$TS\",\"previous_receipt_hash\":\"sha256:$PREV_HASH\"}"

# Sign the receipt
SIG=$(echo -n "$RECEIPT" | openssl pkeyutl -sign -inkey "$KEY_FILE" 2>/dev/null | xxd -p | tr -d '\n' || echo "unsigned")

# Append signed receipt to ledger
echo "$RECEIPT" | python3 -c "
import sys, json
r = json.loads(sys.stdin.read())
r['signature'] = 'ed25519:${SIG:0:128}'
print(json.dumps(r))
" >> "$LEDGER" 2>/dev/null || echo "$RECEIPT" >> "$LEDGER"
fi

# Return verdict to Claude Code hook system
if [ "$VERDICT" = "deny" ]; then
echo "{\"permissionDecision\": \"ask\", \"message\": \"🛡️ Governance: $REASON. Action type: $ACTION_TYPE. Override requires explicit approval.\"}"
else
if [ "$DESTRUCTIVE" = true ]; then
echo "{\"permissionDecision\": \"allow\", \"message\": \"📋 Receipt signed for: $ACTION_TYPE\"}" >&2
fi
fi