@provos/ironcurtain 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/README.md +219 -248
  2. package/dist/cli.js +10 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/config/config-command.js +144 -6
  5. package/dist/config/config-command.js.map +1 -1
  6. package/dist/config/constitution-user-base.md +2 -0
  7. package/dist/config/first-start.js +77 -12
  8. package/dist/config/first-start.js.map +1 -1
  9. package/dist/config/generated/compiled-policy.json +199 -72
  10. package/dist/config/generated/tool-annotations.json +1018 -36
  11. package/dist/config/mcp-servers.json +10 -0
  12. package/dist/config/paths.d.ts +31 -0
  13. package/dist/config/paths.js +52 -2
  14. package/dist/config/paths.js.map +1 -1
  15. package/dist/config/types.d.ts +8 -0
  16. package/dist/config/user-config.d.ts +21 -3
  17. package/dist/config/user-config.js +34 -3
  18. package/dist/config/user-config.js.map +1 -1
  19. package/dist/docker/adapters/claude-code.js +104 -23
  20. package/dist/docker/adapters/claude-code.js.map +1 -1
  21. package/dist/docker/agent-adapter.d.ts +24 -1
  22. package/dist/docker/agent-adapter.js +6 -1
  23. package/dist/docker/agent-adapter.js.map +1 -1
  24. package/dist/docker/docker-agent-session.d.ts +16 -9
  25. package/dist/docker/docker-agent-session.js +64 -90
  26. package/dist/docker/docker-agent-session.js.map +1 -1
  27. package/dist/docker/docker-infrastructure.d.ts +51 -0
  28. package/dist/docker/docker-infrastructure.js +261 -0
  29. package/dist/docker/docker-infrastructure.js.map +1 -0
  30. package/dist/docker/docker-manager.js +3 -0
  31. package/dist/docker/docker-manager.js.map +1 -1
  32. package/dist/docker/mitm-proxy.js +4 -0
  33. package/dist/docker/mitm-proxy.js.map +1 -1
  34. package/dist/docker/oauth-credentials.d.ts +81 -0
  35. package/dist/docker/oauth-credentials.js +178 -0
  36. package/dist/docker/oauth-credentials.js.map +1 -0
  37. package/dist/docker/orientation.d.ts +1 -1
  38. package/dist/docker/orientation.js +3 -2
  39. package/dist/docker/orientation.js.map +1 -1
  40. package/dist/docker/provider-config.d.ts +3 -0
  41. package/dist/docker/provider-config.js +28 -0
  42. package/dist/docker/provider-config.js.map +1 -1
  43. package/dist/docker/pty-session.d.ts +27 -0
  44. package/dist/docker/pty-session.js +510 -0
  45. package/dist/docker/pty-session.js.map +1 -0
  46. package/dist/docker/pty-types.d.ts +24 -0
  47. package/dist/docker/pty-types.js +12 -0
  48. package/dist/docker/pty-types.js.map +1 -0
  49. package/dist/docker/types.d.ts +6 -0
  50. package/dist/escalation/escalation-watcher.d.ts +44 -0
  51. package/dist/escalation/escalation-watcher.js +92 -0
  52. package/dist/escalation/escalation-watcher.js.map +1 -0
  53. package/dist/escalation/listener-command.d.ts +10 -0
  54. package/dist/escalation/listener-command.js +407 -0
  55. package/dist/escalation/listener-command.js.map +1 -0
  56. package/dist/escalation/listener-state.d.ts +49 -0
  57. package/dist/escalation/listener-state.js +104 -0
  58. package/dist/escalation/listener-state.js.map +1 -0
  59. package/dist/escalation/session-registry.d.ts +17 -0
  60. package/dist/escalation/session-registry.js +98 -0
  61. package/dist/escalation/session-registry.js.map +1 -0
  62. package/dist/index.js +15 -0
  63. package/dist/index.js.map +1 -1
  64. package/dist/logger.js +2 -3
  65. package/dist/logger.js.map +1 -1
  66. package/dist/pipeline/annotate.js +35 -15
  67. package/dist/pipeline/annotate.js.map +1 -1
  68. package/dist/pipeline/constitution-customizer.d.ts +2 -1
  69. package/dist/pipeline/constitution-customizer.js +36 -3
  70. package/dist/pipeline/constitution-customizer.js.map +1 -1
  71. package/dist/pipeline/github-identity.d.ts +38 -0
  72. package/dist/pipeline/github-identity.js +64 -0
  73. package/dist/pipeline/github-identity.js.map +1 -0
  74. package/dist/pipeline/handwritten-scenarios.js +34 -0
  75. package/dist/pipeline/handwritten-scenarios.js.map +1 -1
  76. package/dist/sandbox/index.js +14 -3
  77. package/dist/sandbox/index.js.map +1 -1
  78. package/dist/servers/fetch-server.js +2 -2
  79. package/dist/session/base-transport.d.ts +19 -0
  80. package/dist/session/base-transport.js +36 -0
  81. package/dist/session/base-transport.js.map +1 -0
  82. package/dist/session/cli-transport.d.ts +3 -3
  83. package/dist/session/cli-transport.js +7 -4
  84. package/dist/session/cli-transport.js.map +1 -1
  85. package/dist/session/index.js +24 -78
  86. package/dist/session/index.js.map +1 -1
  87. package/dist/session/interaction-log.d.ts +13 -0
  88. package/dist/session/interaction-log.js +28 -0
  89. package/dist/session/interaction-log.js.map +1 -0
  90. package/dist/session/preflight.d.ts +3 -0
  91. package/dist/session/preflight.js +39 -18
  92. package/dist/session/preflight.js.map +1 -1
  93. package/dist/session/types.d.ts +2 -0
  94. package/dist/signal/bot-command.js +5 -0
  95. package/dist/signal/bot-command.js.map +1 -1
  96. package/dist/signal/format.d.ts +21 -1
  97. package/dist/signal/format.js +41 -3
  98. package/dist/signal/format.js.map +1 -1
  99. package/dist/signal/signal-bot-daemon.d.ts +53 -18
  100. package/dist/signal/signal-bot-daemon.js +344 -106
  101. package/dist/signal/signal-bot-daemon.js.map +1 -1
  102. package/dist/signal/signal-config.d.ts +4 -0
  103. package/dist/signal/signal-config.js +2 -0
  104. package/dist/signal/signal-config.js.map +1 -1
  105. package/dist/signal/signal-transport.d.ts +13 -7
  106. package/dist/signal/signal-transport.js +36 -12
  107. package/dist/signal/signal-transport.js.map +1 -1
  108. package/dist/trusted-process/audit-log.d.ts +6 -1
  109. package/dist/trusted-process/audit-log.js +22 -2
  110. package/dist/trusted-process/audit-log.js.map +1 -1
  111. package/dist/trusted-process/audit-redactor.d.ts +25 -0
  112. package/dist/trusted-process/audit-redactor.js +121 -0
  113. package/dist/trusted-process/audit-redactor.js.map +1 -0
  114. package/dist/trusted-process/escalation.d.ts +1 -1
  115. package/dist/trusted-process/escalation.js +6 -1
  116. package/dist/trusted-process/escalation.js.map +1 -1
  117. package/dist/trusted-process/index.d.ts +2 -1
  118. package/dist/trusted-process/index.js +47 -6
  119. package/dist/trusted-process/index.js.map +1 -1
  120. package/dist/trusted-process/mcp-client-manager.js +14 -2
  121. package/dist/trusted-process/mcp-client-manager.js.map +1 -1
  122. package/dist/trusted-process/mcp-error-utils.d.ts +11 -0
  123. package/dist/trusted-process/mcp-error-utils.js +50 -0
  124. package/dist/trusted-process/mcp-error-utils.js.map +1 -0
  125. package/dist/trusted-process/mcp-proxy-server.d.ts +4 -2
  126. package/dist/trusted-process/mcp-proxy-server.js +56 -13
  127. package/dist/trusted-process/mcp-proxy-server.js.map +1 -1
  128. package/dist/trusted-process/path-utils.d.ts +15 -0
  129. package/dist/trusted-process/path-utils.js +38 -0
  130. package/dist/trusted-process/path-utils.js.map +1 -1
  131. package/dist/trusted-process/sandbox-integration.d.ts +5 -0
  132. package/dist/trusted-process/sandbox-integration.js +1 -1
  133. package/dist/trusted-process/sandbox-integration.js.map +1 -1
  134. package/dist/trusted-process/server-context.d.ts +21 -0
  135. package/dist/trusted-process/server-context.js +42 -0
  136. package/dist/trusted-process/server-context.js.map +1 -0
  137. package/dist/types/argument-roles.d.ts +5 -2
  138. package/dist/types/argument-roles.js +25 -2
  139. package/dist/types/argument-roles.js.map +1 -1
  140. package/docker/Dockerfile.claude-code +8 -3
  141. package/docker/entrypoint-claude-code.sh +64 -4
  142. package/package.json +16 -8
  143. package/src/config/constitution-user-base.md +2 -0
  144. package/src/config/generated/compiled-policy.json +199 -72
  145. package/src/config/generated/tool-annotations.json +1018 -36
  146. package/src/config/mcp-servers.json +10 -0
package/README.md CHANGED
@@ -42,295 +42,247 @@ The agent clones a repository and edits a file. The policy engine escalates `git
42
42
 
43
43
  IronCurtain supports two session modes with different trust models:
44
44
 
45
- - **Builtin Agent (Code Mode)** — IronCurtain's own LLM agent writes TypeScript snippets that execute in a V8 sandbox. IronCurtain controls the agent, the sandbox, and the policy engine.
46
- - **Docker Agent Mode** — An external agent (Claude Code, Goose, etc.) runs inside a Docker container with no network access. IronCurtain doesn't control the agent — it only mediates the agent's external access through policy-enforced proxies.
45
+ - **Builtin Agent (Code Mode)** — IronCurtain's own LLM agent writes TypeScript snippets that execute in a V8 sandbox. IronCurtain controls the agent, the sandbox, and the policy engine. Every tool call exits the sandbox as a structured MCP request, passes through the policy engine (allow / deny / escalate), and only then reaches the real MCP server.
47
46
 
48
- ### Builtin Agent (Code Mode)
47
+ - **Docker Agent Mode** — An external agent (Claude Code, Goose, etc.) runs inside a Docker container with no network access. IronCurtain mediates the external effects: LLM API calls pass through a TLS-terminating MITM proxy (host allowlist, fake-to-real key swap), and MCP tool calls pass through the same policy engine used by Code Mode.
49
48
 
50
- ```
51
- ┌─────────────────────────────────────────────┐
52
- │ Agent (LLM) │
53
- │ Generates TypeScript to accomplish tasks │
54
- └──────────────────┬──────────────────────────┘
55
- │ TypeScript code
56
-
57
- ┌─────────────────────────────────────────────┐
58
- │ V8 Isolated Sandbox │
59
- │ Code executes in isolation. │
60
- │ Only interface to the world: typed │
61
- │ function stubs that produce MCP requests. │
62
- │ │
63
- │ filesystem.read_file({path: '...'}) │
64
- │ git.status({repo_path: '...'}) │
65
- └──────────────────┬──────────────────────────┘
66
- │ MCP tool-call requests
67
-
68
- ┌─────────────────────────────────────────────┐
69
- │ Trusted Process (MCP Proxy) │
70
- │ │
71
- │ ┌───────────────────────────────────────┐ │
72
- │ │ Policy Engine │ │
73
- │ │ 1. Structural invariants (hardcoded) │ │
74
- │ │ 2. Compiled constitution rules │ │
75
- │ │ → allow / deny / escalate │ │
76
- │ └───────────────────────────────────────┘ │
77
- │ ┌──────────────┐ ┌─────────────────────┐ │
78
- │ │ Audit Log │ │ Escalation Handler │ │
79
- │ │ (JSONL) │ │ (human approval) │ │
80
- │ └──────────────┘ └─────────────────────┘ │
81
- └──────────────────┬──────────────────────────┘
82
- │ approved calls only
83
-
84
- ┌──────────┐ ┌──────────┐ ┌──────────┐
85
- │Filesystem│ │ Git │ │ Other │
86
- │MCP Server│ │MCP Server│ │MCP Server│
87
- └──────────┘ └──────────┘ └──────────┘
88
- ```
49
+ In both modes, the agent is **untrusted**. Security does not depend on the model following instructions — it is enforced at the boundary.
89
50
 
90
- **Four layers, strict trust boundaries:**
51
+ See [SANDBOXING.md](SANDBOXING.md) for the full architecture with diagrams, layer-by-layer trust analysis, and macOS platform notes.
91
52
 
92
- 1. **Agent** -- An LLM (Claude, GPT, Gemini) that writes TypeScript to accomplish user tasks. It has no direct access to the system.
93
- 2. **Sandbox** -- A V8 isolate ([UTCP Code Mode](https://utcp.dev/)) that executes the agent's TypeScript. The only way to interact with the outside world is through typed function stubs that produce structured MCP requests.
94
- 3. **Trusted Process** -- The security kernel. Every MCP request from the sandbox passes through a two-phase policy engine before reaching any real server. Structural checks enforce hardcoded invariants (protected paths, unknown tool denial). Compiled rule evaluation evaluates the compiled constitution rules. Denied calls are blocked; escalated calls are presented to the user for approval.
95
- 4. **MCP Servers** -- Standard [Model Context Protocol](https://modelcontextprotocol.io/) servers that provide filesystem access, git operations, and other capabilities. Only approved requests reach them.
53
+ ## Quick Start
96
54
 
97
- ## Policy Compilation Pipeline
55
+ ### Prerequisites
98
56
 
99
- The constitution is compiled into enforceable policy through a four-stage LLM pipeline:
57
+ - Node.js 22+ (required by `isolated-vm`; maximum Node 25)
58
+ - Docker (required for Docker Agent Mode and PTY mode)
59
+ - An API key for at least one LLM provider (Anthropic, Google, or OpenAI)
100
60
 
101
- ```
102
- constitution.md → [Annotate] → [Compile] → [Resolve Lists] → [Generate Scenarios] → [Verify & Repair]
103
- │ │ │ │ │
104
- ▼ ▼ ▼ ▼ ▼
105
- tool-annotations compiled-policy dynamic-lists test-scenarios verified policy
106
- .json .json .json .json (or build failure)
61
+ ### Install
62
+
63
+ **As a global CLI tool (end users):**
64
+
65
+ ```bash
66
+ npm install -g @provos/ironcurtain
107
67
  ```
108
68
 
109
- 1. **Annotate** -- Classify each MCP tool's arguments by role (read-path, write-path, delete-path, none).
110
- 2. **Compile** -- Translate the English constitution into deterministic if/then rules. Categorical references ("major news sites", "my contacts") are emitted as `@list-name` symbolic references with list definitions.
111
- 3. **Resolve Lists** -- Resolve dynamic list definitions to concrete values via LLM knowledge or MCP tool-use (e.g., querying a contacts database). Resolved values are written to `dynamic-lists.json` and can be user-inspected/edited. Skipped when no lists are present.
112
- 4. **Generate Scenarios** -- Create test scenarios from the constitution, combined with mandatory handwritten invariant tests.
113
- 5. **Verify & Repair** -- Execute scenarios against the real policy engine. An LLM judge analyzes failures and generates targeted repairs (up to 2 rounds). The build fails if the policy cannot be verified.
69
+ **From source (development):**
114
70
 
115
- All artifacts are content-hash cached -- only changed inputs trigger recompilation.
71
+ ```bash
72
+ git clone https://github.com/provos/ironcurtain.git
73
+ cd ironcurtain
74
+ npm install
75
+ ```
116
76
 
117
- ### What compiled rules look like
77
+ ### One-time setup
118
78
 
119
- A constitution like:
79
+ **1. Set your API key:**
120
80
 
121
- ```markdown
122
- - The agent may perform read-only git operations (status, diff, log) within the sandbox without approval.
123
- - The agent must receive human approval before git push, pull, fetch, or any remote-contacting operation.
81
+ ```bash
82
+ export ANTHROPIC_API_KEY=sk-ant-...
124
83
  ```
125
84
 
126
- compiles into deterministic JSON rules:
85
+ Or add it to `~/.ironcurtain/config.json` via `ironcurtain config`. Environment variables take precedence. Supported: `ANTHROPIC_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY`, `OPENAI_API_KEY`.
127
86
 
128
- ```json
129
- [
130
- {
131
- "tool": "git_status",
132
- "decision": "allow",
133
- "condition": { "directory": { "within": "$SANDBOX" } }
134
- },
135
- {
136
- "tool": "git_diff",
137
- "decision": "allow",
138
- "condition": { "directory": { "within": "$SANDBOX" } }
139
- },
140
- {
141
- "tool": "git_push",
142
- "decision": "escalate",
143
- "reason": "Remote-contacting git operations require human approval"
144
- }
145
- ]
146
- ```
87
+ **2. Run the first-start wizard** (runs automatically on first `ironcurtain start`, or explicitly):
147
88
 
148
- Any tool call that doesn't match an explicit allow or escalate rule is **denied by default**. Rules define what is permitted or needs human judgment; everything else is blocked.
89
+ ```bash
90
+ ironcurtain setup
91
+ ```
149
92
 
150
- ### Docker Agent Mode
93
+ Walks you through GitHub token setup, web search provider, model selection, and other settings. Creates `~/.ironcurtain/config.json` with your choices.
151
94
 
152
- In Docker mode, IronCurtain runs an external agent — not its own. The agent (Claude Code, Goose, etc.) already has its own LLM loop, tool-calling mechanism, and execution model. IronCurtain's role is to **mediate external access**: every LLM API call and every MCP tool call must pass through host-side proxies that enforce policy.
95
+ **3. Customize your policy** (optional but recommended):
153
96
 
154
- ```
155
- ┌──────────────────────────────────────────────┐
156
- │ Docker Container (--network=none) │
157
- │ │
158
- │ ┌────────────────────────────────────────┐ │
159
- │ │ External Agent │ │
160
- │ │ (Claude Code, Goose, etc.) │ │
161
- │ │ Own LLM loop, tools, execution │ │
162
- │ └──────┬──────────────────┬──────────────┘ │
163
- │ │ LLM API calls │ MCP tool calls │
164
- │ ▼ ▼ │
165
- │ [UDS] [UDS] │
166
- └─────────┬──────────────────┬─────────────────┘
167
- │ │
168
- ▼ ▼
169
- ┌──────────────────┐ ┌─────────────────────────┐
170
- │ MITM Proxy │ │ MCP Proxy │
171
- │ (host process) │ │ (host process) │
172
- │ │ │ │
173
- │ Host allowlist │ │ Policy Engine │
174
- │ Endpoint filter │ │ allow / deny / │
175
- │ Fake→real key │ │ escalate │
176
- │ swap │ │ │
177
- └────────┬─────────┘ └────────────┬────────────┘
178
- │ │
179
- ▼ ▼
180
- LLM Provider MCP Servers
181
- (Anthropic, etc.) (filesystem, git, etc.)
97
+ ```bash
98
+ ironcurtain customize-policy
182
99
  ```
183
100
 
184
- The key difference from Code Mode: IronCurtain does **not** control the agent's execution. The agent has its own tool-calling mechanism (Claude Code uses its own tools internally). IronCurtain only sees the external effects — LLM API calls and MCP tool calls — and enforces policy on those boundaries.
101
+ An LLM-assisted conversation that generates a constitution tailored to your workflow, saved to `~/.ironcurtain/constitution-user.md`. You can also edit this file directly.
185
102
 
186
- See [SANDBOXING.md](SANDBOXING.md) for the full sandboxing architecture.
103
+ **4. Compile the policy:**
187
104
 
188
- ## Getting Started
105
+ ```bash
106
+ ironcurtain compile-policy
107
+ ```
189
108
 
190
- ### Prerequisites
109
+ Translates your constitution into deterministic rules, generates test scenarios, and verifies them. Compiled artifacts go to `~/.ironcurtain/generated/`. The package ships with pre-compiled defaults — skip this step until you customize.
191
110
 
192
- - Node.js 20+
193
- - An API key for at least one supported LLM provider (Anthropic, Google, or OpenAI)
111
+ ## Running Modes
194
112
 
195
- ### Install
113
+ ### Interactive mode
196
114
 
197
- **As a global CLI tool (end users):**
115
+ A multi-turn session where you type tasks and the agent responds:
198
116
 
199
117
  ```bash
200
- npm install -g @provos/ironcurtain
118
+ ironcurtain start
201
119
  ```
202
120
 
203
- **From source (development):**
121
+ Escalated tool calls pause the agent and prompt you with `/approve` or `/deny`.
122
+
123
+ ### Single-shot mode
124
+
125
+ Send one task and exit when the agent finishes:
204
126
 
205
127
  ```bash
206
- git clone https://github.com/provos/ironcurtain.git
207
- cd ironcurtain
208
- npm install
128
+ ironcurtain start "Summarize the files in the current directory"
209
129
  ```
210
130
 
211
- ### 1. Configure your API key
131
+ ### Session resume
212
132
 
213
- Set your LLM provider API key via environment variable:
133
+ Resume a previous session's conversation history:
214
134
 
215
135
  ```bash
216
- export ANTHROPIC_API_KEY=sk-ant-...
136
+ ironcurtain start --resume <session-id>
217
137
  ```
218
138
 
219
- Or add it to `~/.ironcurtain/config.json` (auto-created on first run with defaults):
220
-
221
- ```json
222
- {
223
- "anthropicApiKey": "sk-ant-..."
224
- }
225
- ```
139
+ Session IDs are printed on session start and stored under `~/.ironcurtain/sessions/`.
226
140
 
227
- Environment variables take precedence over config file values. Supported providers: `ANTHROPIC_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY`, `OPENAI_API_KEY`.
141
+ ### PTY mode and the escalation listener
228
142
 
229
- ### 2. Configure settings
143
+ PTY mode attaches your terminal directly to Claude Code running inside the Docker container. You get Claude Code's full interactive TUI — spinners, diffs, file previews, slash commands — while IronCurtain still mediates every tool call through its policy engine.
230
144
 
231
145
  ```bash
232
- ironcurtain config
146
+ ironcurtain start --pty
233
147
  ```
234
148
 
235
- This opens an interactive editor for `~/.ironcurtain/config.json` where you can configure models, security settings, resource budgets, and auto-compaction. API keys should be set via environment variables.
236
-
237
- ### 3. Customize your policy
238
-
239
- Run the interactive policy customizer to create a constitution tailored to your workflow:
149
+ **Why a separate escalation listener?** In PTY mode your terminal is fully occupied by Claude Code's raw TTY — there is no channel left to inject escalation prompts inline. Each PTY session registers itself in `~/.ironcurtain/pty-registry/` and a separate companion process handles approvals:
240
150
 
241
151
  ```bash
242
- ironcurtain customize-policy
152
+ # Terminal 1 — interactive Claude Code session
153
+ ironcurtain start --pty
154
+
155
+ # Terminal 2 — approve or deny escalations from all active PTY sessions
156
+ ironcurtain escalation-listener
243
157
  ```
244
158
 
245
- The customizer walks you through an LLM-assisted conversation about what your agent should and shouldn't be able to do, then generates a constitution file at `~/.ironcurtain/constitution-user.md`. This file is appended to the base constitution, which defines the guiding principles:
159
+ **How session discovery works:**
246
160
 
247
- ```markdown
248
- # IronCurtain Constitution
161
+ 1. When `--pty` starts, it writes a registration file to `~/.ironcurtain/pty-registry/session-<id>.json` containing the session ID, escalation directory path, display label, and process PID.
162
+ 2. The escalation listener polls `pty-registry/` every second. It discovers new sessions, attaches an escalation watcher to each one, and detaches watchers when sessions end. Stale registrations (process PID no longer alive) are removed automatically, so a crashed PTY session doesn't leave phantom entries.
249
163
 
250
- ## Guiding Principles
164
+ **The escalation flow:**
251
165
 
252
- 1. **Least privilege**: The agent may only access resources explicitly permitted by policy.
253
- 2. **No destruction**: Delete operations outside the sandbox are never permitted,
254
- unless an explicit exception is granted by the user guidance.
255
- 3. **Human oversight**: Operations outside the sandbox require explicit human approval.
256
- ```
166
+ When a tool call is escalated, the PTY session emits a **BEL character** (`\x07`) — your terminal bell or visual flash signals that attention is needed. Switch to the listener terminal. The listener TUI re-renders in place: active sessions are listed with sequential display numbers, and each pending escalation shows the tool name, arguments, and the policy reason for the escalation.
257
167
 
258
- The customizer produces concrete guidance like:
168
+ **Listener commands:**
259
169
 
260
- ```markdown
261
- # User Policy Customizations
170
+ | Command | Description |
171
+ | -------------- | ----------------------------------------- |
172
+ | `/approve N` | Approve escalation #N |
173
+ | `/deny N` | Deny escalation #N |
174
+ | `/approve all` | Approve all pending escalations |
175
+ | `/deny all` | Deny all pending escalations |
176
+ | `/sessions` | Show detailed session information |
177
+ | `/quit` | Exit the listener |
262
178
 
263
- ## Concrete Guidance
179
+ Escalation numbers are sequential across all sessions. If you have two active PTY sessions, their escalations share a single numbered list in the dashboard — escalation #3 might be from session [1] and #4 from session [2]. Use the number shown in the dashboard to resolve them.
264
180
 
265
- - The agent is allowed to read, write and delete content in the Downloads folder
266
- - The agent is allowed to read documents in the Users document folder.
267
- - The agent is allowed to perform all local read and write git operations within the sandbox
268
- - The agent must ask for human approval for all other git operations
269
- - The agent may fetch web content from popular news sites.
270
- ```
181
+ **Multiple concurrent sessions:** The listener aggregates escalations from all active PTY sessions in one dashboard. Each session is assigned a display number (e.g., `Claude Code [1]`, `Claude Code [2]`) when it first appears.
182
+
183
+ **Single-instance lock:** Only one escalation listener may run at a time, enforced via a PID-checked lock file at `~/.ironcurtain/escalation-listener.lock`. A stale lock from a crashed process is cleaned up automatically on the next start.
184
+
185
+ **Emergency exit and terminal recovery:** Press `Ctrl-\` to trigger a graceful shutdown of the PTY session (stops containers, proxies, and performs async cleanup). If the process is killed ungracefully (e.g., `kill -9`), run `reset` in that terminal to restore normal terminal mode.
271
186
 
272
- You can also edit `~/.ironcurtain/constitution-user.md` directly. If you need to override the base principles, place a full constitution at `~/.ironcurtain/constitution.md` — it replaces the package-bundled base entirely.
187
+ ### Signal messaging transport
273
188
 
274
- ### 4. Annotate tools and compile the policy
189
+ Run IronCurtain sessions via Signal messages — send tasks, receive responses, and approve or deny escalations from your phone. All communication is end-to-end encrypted via the Signal protocol.
275
190
 
276
191
  ```bash
277
- ironcurtain annotate-tools # classify MCP tool arguments (developer task)
278
- ironcurtain compile-policy # compile constitution into enforceable rules (user task)
279
- ironcurtain refresh-lists # re-resolve dynamic lists without full recompilation
192
+ ironcurtain setup-signal # One-time setup wizard
193
+ ironcurtain bot # Start the Signal bot daemon
280
194
  ```
281
195
 
282
- Or with npm scripts during development: `npm run annotate-tools` / `npm run compile-policy`.
196
+ See [TRANSPORT.md](TRANSPORT.md) for setup instructions, architecture details, and why we chose Signal over alternatives like Telegram.
283
197
 
284
- Tool annotation connects to your MCP servers and classifies each tool's arguments via LLM. This only needs re-running when you add or change MCP servers. Policy compilation translates your constitution into deterministic rules, generates test scenarios, and verifies them. The compiled artifacts are written to `~/.ironcurtain/generated/`. Review the generated `compiled-policy.json` -- these are the rules that will be enforced at runtime. (The package ships with pre-compiled defaults so you can run immediately without compiling.)
198
+ ## Session Commands
285
199
 
286
- IronCurtain ships with pre-configured MCP servers for filesystem and git operations. See [Adding MCP Servers](#adding-mcp-servers) for how to extend this.
200
+ Commands available during an **interactive** or **single-shot** session:
287
201
 
288
- ### 5. Run the agent
202
+ | Command | Description |
203
+ | ---------- | ------------------------------------------------ |
204
+ | `/approve` | Approve the pending escalation |
205
+ | `/deny` | Deny the pending escalation |
206
+ | `/budget` | Show resource consumption (tokens, steps, cost) |
207
+ | `/logs` | Display diagnostic events |
208
+ | `/quit` | End the session |
289
209
 
290
- **Interactive mode** (multi-turn session with human escalation support):
210
+ In **PTY mode**, use the escalation listener instead (see above) the PTY terminal is occupied by Claude Code's TUI.
291
211
 
292
- ```bash
293
- ironcurtain start
294
- ```
212
+ ## Policy: Constitution → Enforcement
295
213
 
296
- **Single-shot mode** (send one task, get a response):
214
+ You write intent in plain English; IronCurtain compiles it into deterministic rules:
297
215
 
298
- ```bash
299
- ironcurtain start "Summarize the files in the current directory"
300
216
  ```
217
+ constitution.md → [Annotate] → [Compile] → [Resolve Lists] → [Generate Scenarios] → [Verify & Repair]
218
+ │ │ │ │ │
219
+ ▼ ▼ ▼ ▼ ▼
220
+ tool-annotations compiled-policy dynamic-lists test-scenarios verified policy
221
+ .json .json .json .json (or build failure)
222
+ ```
223
+
224
+ 1. **Annotate** — Classify each MCP tool's arguments by role (read-path, write-path, delete-path, none).
225
+ 2. **Compile** — Translate the English constitution into deterministic if/then rules. Categorical references ("major news sites", "my contacts") are emitted as `@list-name` symbolic references.
226
+ 3. **Resolve Lists** — Resolve symbolic lists to concrete values via LLM knowledge or MCP tool-use (e.g., querying a contacts database). Written to `dynamic-lists.json`, user-editable. Skipped when no lists are present.
227
+ 4. **Generate Scenarios** — Create test scenarios from the constitution plus mandatory handwritten invariant tests.
228
+ 5. **Verify & Repair** — Run scenarios against the real policy engine. An LLM judge analyzes failures and generates targeted repairs (up to 2 rounds). Build fails if the policy cannot be verified.
229
+
230
+ All artifacts are content-hash cached — only changed inputs trigger recompilation.
301
231
 
302
- Or with npm scripts during development: `npm start` / `npm start "task"`.
232
+ ### What compiled rules look like
233
+
234
+ A constitution clause like:
235
+
236
+ ```markdown
237
+ - The agent may perform read-only git operations (status, diff, log) within the sandbox without approval.
238
+ - The agent must receive human approval before git push, pull, fetch, or any remote-contacting operation.
239
+ ```
303
240
 
304
- When Docker is available and `ANTHROPIC_API_KEY` is set, `ironcurtain start` automatically selects Docker mode (claude-code agent). Otherwise it falls back to the builtin agent silently. The selected mode is logged to stderr. Use `--agent builtin` or `--agent claude-code` to force a specific agent; explicit selection fails fast with a clear error if prerequisites are missing.
241
+ compiles to:
305
242
 
306
- ### 6. Signal messaging transport (optional)
243
+ ```json
244
+ [
245
+ { "tool": "git_status", "decision": "allow", "condition": { "directory": { "within": "$SANDBOX" } } },
246
+ { "tool": "git_diff", "decision": "allow", "condition": { "directory": { "within": "$SANDBOX" } } },
247
+ { "tool": "git_push", "decision": "escalate", "reason": "Remote-contacting git operations require human approval" }
248
+ ]
249
+ ```
307
250
 
308
- IronCurtain includes a Signal messaging transport that lets you interact with agent sessions from your phone. All communication is end-to-end encrypted via the Signal protocol.
251
+ Any call that doesn't match an explicit `allow` or `escalate` rule is **denied by default**.
309
252
 
310
253
  ```bash
311
- ironcurtain setup-signal # Interactive setup wizard (one-time)
312
- ironcurtain bot # Start the Signal bot daemon
254
+ ironcurtain annotate-tools # Classify MCP tool arguments (re-run when servers change)
255
+ ironcurtain compile-policy # Compile constitution into rules and verify
256
+ ironcurtain refresh-lists # Re-resolve dynamic lists without full recompilation
257
+ ironcurtain refresh-lists --list major-news # Refresh a single list
313
258
  ```
314
259
 
315
- Once running, send messages from the Signal app to control agent sessions, receive responses, and approve or deny escalations — all from your phone.
260
+ Review the generated `~/.ironcurtain/generated/compiled-policy.json` these are the exact rules enforced at runtime.
316
261
 
317
- See [TRANSPORT.md](TRANSPORT.md) for the full setup guide, architecture details, and why we chose Signal over alternatives like Telegram.
262
+ ## Web Search
318
263
 
319
- ### Session Commands
264
+ IronCurtain's fetch server includes a `web_search` tool backed by your choice of provider. Configure via `ironcurtain config` → **Web Search**, or directly in `~/.ironcurtain/config.json`:
320
265
 
321
- During an interactive session:
266
+ ```json
267
+ {
268
+ "webSearch": {
269
+ "provider": "brave",
270
+ "brave": { "apiKey": "BSA..." }
271
+ }
272
+ }
273
+ ```
322
274
 
323
- | Command | Description |
324
- |---------|-------------|
325
- | `/approve` | Approve a pending escalation request |
326
- | `/deny` | Deny a pending escalation request |
327
- | `/budget` | Show resource consumption (tokens, steps, cost) |
328
- | `/logs` | Display diagnostic events |
329
- | `/quit` | End the session |
275
+ | Provider | `provider` value | API key field | Sign up |
276
+ | -------- | ---------------- | -------------------------- | ----------------------------- |
277
+ | Brave | `"brave"` | `webSearch.brave.apiKey` | https://brave.com/search/api/ |
278
+ | Tavily | `"tavily"` | `webSearch.tavily.apiKey` | https://tavily.com/ |
279
+ | SerpAPI | `"serpapi"` | `webSearch.serpapi.apiKey` | https://serpapi.com/ |
280
+
281
+ `web_search` is available in both builtin and Docker Agent modes. If no provider is configured, calls to `web_search` return an error explaining how to set one up via `ironcurtain config`.
330
282
 
331
283
  ## Configuration
332
284
 
333
- IronCurtain stores its configuration and session data in `~/.ironcurtain/`:
285
+ IronCurtain stores configuration and session data in `~/.ironcurtain/`:
334
286
 
335
287
  ```
336
288
  ~/.ironcurtain/
@@ -339,6 +291,7 @@ IronCurtain stores its configuration and session data in `~/.ironcurtain/`:
339
291
  ├── constitution-user.md # Your policy customizations (generated by customize-policy)
340
292
  ├── generated/ # User-compiled policy artifacts (overrides package defaults)
341
293
  ├── signal-data/ # Signal transport persistent data (registration keys)
294
+ ├── pty-registry/ # Active PTY session registrations (auto-managed)
342
295
  ├── sessions/
343
296
  │ └── {sessionId}/
344
297
  │ ├── sandbox/ # Per-session filesystem sandbox
@@ -347,16 +300,24 @@ IronCurtain stores its configuration and session data in `~/.ironcurtain/`:
347
300
  │ └── session.log # Diagnostics
348
301
  ```
349
302
 
303
+ Edit configuration interactively:
304
+
305
+ ```bash
306
+ ironcurtain config
307
+ ```
308
+
309
+ See [CONFIG.md](CONFIG.md) for the full configuration reference covering all fields, defaults, and environment variable overrides.
310
+
350
311
  ### Resource Budgets
351
312
 
352
313
  Sessions enforce configurable limits to prevent runaway agents:
353
314
 
354
- | Limit | Default | Config Key |
355
- |-------|---------|------------|
356
- | Max tokens | 1,000,000 | `resourceBudget.maxTotalTokens` |
357
- | Max steps | 200 | `resourceBudget.maxSteps` |
358
- | Session timeout | 30 minutes | `resourceBudget.maxSessionSeconds` |
359
- | Cost cap | $5.00 | `resourceBudget.maxEstimatedCostUsd` |
315
+ | Limit | Default | Config Key |
316
+ | --------------- | ---------- | ------------------------------------ |
317
+ | Max tokens | 1,000,000 | `resourceBudget.maxTotalTokens` |
318
+ | Max steps | 200 | `resourceBudget.maxSteps` |
319
+ | Session timeout | 30 minutes | `resourceBudget.maxSessionSeconds` |
320
+ | Cost cap | $5.00 | `resourceBudget.maxEstimatedCostUsd` |
360
321
 
361
322
  Set any limit to `null` in `config.json` to disable it.
362
323
 
@@ -373,7 +334,11 @@ By default, all escalations require manual `/approve` or `/deny`. You can option
373
334
  }
374
335
  ```
375
336
 
376
- The auto-approver is conservative — it only approves when intent is unambiguous (e.g., "push my changes to origin" clearly authorizes `git_push`). Vague messages like "go ahead" or "fix the tests" always fall through to human approval. It can never deny — only approve or escalate. All auto-approved actions are recorded in the audit log with `autoApproved: true`.
337
+ The auto-approver is conservative — it only approves when intent is unambiguous (e.g., "push my changes to origin" clearly authorizes `git_push`). Vague messages like "go ahead" or "fix the tests" always fall through to human review. It can never deny — only approve or escalate. All auto-approved actions are recorded in the audit log with `autoApproved: true`.
338
+
339
+ ### Audit Redaction
340
+
341
+ Audit log entries may contain sensitive data passed through tool arguments or results. By default, recognized patterns (credit card numbers, SSNs, API keys) are automatically redacted before entries are written to `audit.jsonl`. Disable with `"auditRedaction": { "enabled": false }` in `config.json` for full forensic logging.
377
342
 
378
343
  ### Multi-Provider Support
379
344
 
@@ -387,37 +352,38 @@ IronCurtain supports multiple LLM providers. Use the `provider:model-name` forma
387
352
  }
388
353
  ```
389
354
 
390
- Each provider has its own API key field in the config (and corresponding environment variable):
391
-
392
- | Provider | Config Key | Environment Variable |
393
- |----------|-----------|---------------------|
394
- | Anthropic | `anthropicApiKey` | `ANTHROPIC_API_KEY` |
395
- | Google | `googleApiKey` | `GOOGLE_GENERATIVE_AI_API_KEY` |
396
- | OpenAI | `openaiApiKey` | `OPENAI_API_KEY` |
355
+ | Provider | Config Key | Environment Variable |
356
+ | --------- | ----------------- | ------------------------------ |
357
+ | Anthropic | `anthropicApiKey` | `ANTHROPIC_API_KEY` |
358
+ | Google | `googleApiKey` | `GOOGLE_GENERATIVE_AI_API_KEY` |
359
+ | OpenAI | `openaiApiKey` | `OPENAI_API_KEY` |
397
360
 
398
361
  Environment variables take precedence over config file values.
399
362
 
400
363
  ### Adding MCP Servers
401
364
 
402
- IronCurtain ships with filesystem and git MCP servers pre-configured. Adding a new server is a developer-level task that may involve changes across several files:
365
+ IronCurtain ships with filesystem, git, fetch, and GitHub MCP servers pre-configured. Adding a new server is a developer-level task:
403
366
 
404
367
  1. **Register the server** in `src/config/mcp-servers.json` with its command, arguments, and optional environment variables or sandbox settings.
405
- 2. **Extend the argument role registry** in `src/types/argument-roles.ts` if the new server's tools have argument semantics not covered by existing roles (e.g., `read-path`, `write-path`, `fetch-url`). Each role defines how values are normalized and evaluated by the policy engine.
368
+ 2. **Extend the argument role registry** in `src/types/argument-roles.ts` if the new server's tools have argument semantics not covered by existing roles (e.g., `read-path`, `write-path`, `fetch-url`).
406
369
  3. **Update the constitution** in `src/config/constitution.md` to cover the new server's capabilities.
407
- 4. **Re-run `ironcurtain annotate-tools`** (or `npm run annotate-tools`) to classify the new server's tool arguments by role.
408
- 5. **Re-run `ironcurtain compile-policy`** (or `npm run compile-policy`) to compile policy rules from your constitution. The verification stage will flag gaps.
370
+ 4. **Re-run `ironcurtain annotate-tools`** to classify the new server's tool arguments by role.
371
+ 5. **Re-run `ironcurtain compile-policy`** to compile policy rules from your constitution. The verification stage will flag gaps.
409
372
 
410
373
  After compilation, review the updated `tool-annotations.json` and `compiled-policy.json` to verify the new tools are correctly classified and covered by policy.
411
374
 
412
375
  ## Built-in Capabilities
413
376
 
414
- IronCurtain ships with three pre-configured MCP servers. All tool calls are governed by your compiled policy.
415
-
416
- **Filesystem** (14 tools) — `read_file`, `read_multiple_files`, `write_file`, `edit_file`, `create_directory`, `list_directory`, `directory_tree`, `move_file`, `search_files`, `get_file_info`, `list_allowed_directories`, `file_exists`, `read_file_lines`, `calculate_diff`.
377
+ IronCurtain ships with four pre-configured MCP servers. All tool calls are governed by your compiled policy.
417
378
 
418
- **Git** (27 tools) Full git workflow: `git_status`, `git_diff_staged`, `git_diff_unstaged`, `git_diff`, `git_log`, `git_show`, `git_add`, `git_reset`, `git_commit`, `git_checkout`, `git_create_branch`, `git_list_branches`, `git_push`, `git_pull`, `git_fetch`, `git_merge`, `git_rebase`, `git_cherry_pick`, `git_stash`, `git_stash_pop`, `git_stash_list`, `git_tag`, `git_list_tags`, `git_remote`, `git_clone`, `git_init`, `git_blame`.
379
+ | Server | Tools | Key capabilities |
380
+ | -------------- | ----- | ---------------------------------------------------------------------------------------------- |
381
+ | **Filesystem** | 14 | Read, write, edit, search files; directory tree; move; diff calculation |
382
+ | **Git** | 27 | Full git workflow: status, diff, log, commit, branch, push/pull/fetch, clone, stash, blame |
383
+ | **Fetch** | 2 | HTTP GET with HTML-to-markdown conversion; `web_search` (see [Web Search](#web-search)) |
384
+ | **GitHub** | 41 | Issues, PRs, code search, reviews via `ghcr.io/github/github-mcp-server`; requires a GitHub personal access token |
419
385
 
420
- **Fetch** (1 tool) `fetch` retrieves content from URLs with automatic HTML-to-markdown conversion.
386
+ Read-only operations are allowed by default policy; mutations (writes, pushes, PR creation) escalate for human approval.
421
387
 
422
388
  ## Security Model
423
389
 
@@ -425,42 +391,44 @@ IronCurtain is designed around a specific threat model: **the LLM goes rogue.**
425
391
 
426
392
  ### What IronCurtain enforces
427
393
 
428
- - **Filesystem containment** -- Path-based policy with symlink-aware resolution prevents path traversal and symlink-escape attacks.
429
- - **Per-tool policy** -- Each MCP tool call is evaluated against compiled rules. The policy engine classifies tool arguments by role (read-path, write-path, delete-path) to make fine-grained decisions.
430
- - **Structural invariants** -- Certain protections are hardcoded and cannot be overridden by the constitution: the agent can never modify its own policy files, audit logs, or configuration.
431
- - **Human escalation** -- When policy says "escalate," the agent pauses and the user must explicitly `/approve` or `/deny` the action. Optionally, an [LLM-based auto-approver](#auto-approve-escalations) can approve actions that clearly match the user's most recent request — it can never deny, only approve or fall through to human review.
432
- - **Audit trail** -- Every tool call and policy decision is logged to an append-only JSONL audit log.
433
- - **Resource limits** -- Token, step, time, and cost budgets prevent runaway sessions.
394
+ - **Filesystem containment** Symlink-aware path resolution prevents path traversal and symlink-escape attacks.
395
+ - **Per-tool policy** Each MCP tool call is evaluated against compiled rules. The policy engine classifies tool arguments by role (read-path, write-path, delete-path) to make fine-grained decisions.
396
+ - **Structural invariants** Certain protections are hardcoded and cannot be overridden by the constitution: the agent can never modify its own policy files, audit logs, or configuration.
397
+ - **Human escalation** When policy says "escalate," the agent pauses and the user must explicitly approve or deny. Optionally, an [LLM-based auto-approver](#auto-approve-escalations) handles unambiguous cases.
398
+ - **Audit trail** Every tool call and policy decision is logged to an append-only JSONL audit log.
399
+ - **Resource limits** Token, step, time, and cost budgets prevent runaway sessions.
434
400
 
435
401
  ### Known limitations
436
402
 
437
403
  This is a research prototype. Known gaps include:
438
404
 
439
- - **Policy compilation fidelity** -- The LLM-based compiler can misinterpret constitution intent. The verification pipeline catches many errors but is not exhaustive. Always review the compiled `compiled-policy.json`.
440
- - **V8 isolate boundaries** -- The sandbox uses V8 isolates, not OS-level virtualization. A V8 zero-day could allow escape (though the agent has no knowledge of how to exploit one).
441
- - **No outbound content inspection** -- An agent allowed to write files could encode sensitive data to bypass content-level controls. Planned: LLM-based intelligibility checks on outbound content.
442
- - **Escalation fatigue** -- Too many false-positive escalations can lead to habitual approval. Tune your constitution to minimize unnecessary prompts.
405
+ - **Policy compilation fidelity** The LLM-based compiler can misinterpret constitution intent. The verification pipeline catches many errors but is not exhaustive. Always review the compiled `compiled-policy.json`.
406
+ - **V8 isolate boundaries** Code Mode uses V8 isolates, not OS-level virtualization. A V8 zero-day could allow escape.
407
+ - **No outbound content inspection** An agent allowed to write files could encode sensitive data to bypass content-level controls. Planned: LLM-based intelligibility checks on outbound content.
408
+ - **Escalation fatigue** Too many false-positive escalations can lead to habitual approval. Tune your constitution to minimize unnecessary prompts.
443
409
 
444
410
  See [docs/SECURITY_CONCERNS.md](docs/SECURITY_CONCERNS.md) for a detailed threat analysis.
445
411
 
446
412
  ## Troubleshooting
447
413
 
448
- | Issue | Guidance |
449
- |-------|---------|
450
- | **Missing API key** | Set the environment variable (`ANTHROPIC_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY`, or `OPENAI_API_KEY`) or add the corresponding key to `~/.ironcurtain/config.json`. |
451
- | **Sandbox unavailable** | OS-level sandboxing requires `bubblewrap` and `socat`. Install both, or set `"sandboxPolicy": "warn"` in your MCP server config for development. |
452
- | **Budget exhausted** | Adjust limits in `~/.ironcurtain/config.json` under `resourceBudget`. Set any individual limit to `null` to disable it. |
453
- | **Node version errors** | Node.js 22+ is required (`isolated-vm` needs `>=22.0.0`). Maximum supported is Node 25 (`<26`). |
454
- | **Policy doesn't match intent** | Review `compiled-policy.json` to see the generated rules. Run `ironcurtain customize-policy` to refine your constitution, then `ironcurtain compile-policy` to recompile. Specific wording produces better rules — vague phrasing leads to vague policy. |
455
- | **Auto-approve not triggering** | The auto-approver only approves when the user's message explicitly authorizes the action (e.g., "push to origin" for `git_push`). Vague messages like "go ahead" always escalate to human review. Verify `autoApprove.enabled` is `true` in `config.json`. |
456
- | **Signal bot not responding** | Verify the signal-cli container is running (`docker ps \| grep ironcurtain-signal`). Check that Signal is configured (`ironcurtain setup-signal`). See [TRANSPORT.md](TRANSPORT.md) for detailed troubleshooting. |
414
+ | Issue | Guidance |
415
+ | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
416
+ | **Missing API key** | Set the environment variable (`ANTHROPIC_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY`, or `OPENAI_API_KEY`) or add the corresponding key to `~/.ironcurtain/config.json`. |
417
+ | **Sandbox unavailable** | OS-level sandboxing requires `bubblewrap` and `socat`. Install both, or set `"sandboxPolicy": "warn"` in your MCP server config for development. |
418
+ | **Budget exhausted** | Adjust limits in `~/.ironcurtain/config.json` under `resourceBudget`. Set any individual limit to `null` to disable it. |
419
+ | **Node version errors** | Node.js 22+ is required (`isolated-vm` needs `>=22.0.0`). Maximum supported is Node 25 (`<26`). |
420
+ | **Policy doesn't match intent** | Review `compiled-policy.json` to see the generated rules. Run `ironcurtain customize-policy` to refine your constitution, then `ironcurtain compile-policy` to recompile. Specific wording produces better rules — vague phrasing leads to vague policy. |
421
+ | **Auto-approve not triggering** | The auto-approver only approves when the user's message explicitly authorizes the action (e.g., "push to origin" for `git_push`). Vague messages always escalate to human review. Verify `autoApprove.enabled` is `true` in `config.json`. |
422
+ | **PTY terminal garbled after exit** | Run `reset` in the PTY terminal to restore normal mode. This is needed when the process is killed ungracefully and raw mode is not restored. |
423
+ | **Escalation listener: "already running"** | Another listener holds the lock at `~/.ironcurtain/escalation-listener.lock`. The lock is auto-cleared if the previous process is dead. If it persists, check the PID in the lock file. |
424
+ | **Signal bot not responding** | Verify the signal-cli container is running (`docker ps \| grep ironcurtain-signal`). Check that Signal is configured (`ironcurtain setup-signal`). See [TRANSPORT.md](TRANSPORT.md) for detailed troubleshooting. |
457
425
 
458
426
  ## Development
459
427
 
460
428
  ```bash
461
429
  npm test # Run all tests
462
430
  npm test -- test/policy-engine.test.ts # Run a single test file
463
- npx test -- -t "denies delete_file" # Run a single test by name
431
+ npm test -- -t "denies delete_file" # Run a single test by name
464
432
  npm run lint # Lint
465
433
  npm run build # TypeScript compilation + asset copy
466
434
  ```
@@ -472,16 +440,19 @@ See [TESTING.md](TESTING.md) for the full testing guide, including integration t
472
440
  ```
473
441
  src/
474
442
  ├── index.ts # Entry point
443
+ ├── cli.ts # CLI command dispatcher
475
444
  ├── config/ # Configuration loading, constitution, MCP server definitions
476
- │ ├── constitution.md # Your security policy in plain English
445
+ │ ├── constitution.md # Base security policy in plain English
477
446
  │ ├── mcp-servers.json # MCP server definitions
478
447
  │ └── generated/ # Compiled policy artifacts (do not edit manually)
479
448
  ├── session/ # Multi-turn session management, budgets, loop detection
480
449
  ├── sandbox/ # V8 isolated execution environment
481
- ├── trusted-process/ # Policy engine, MCP proxy, audit log, escalation
450
+ ├── trusted-process/ # Policy engine, MCP proxy, audit log, escalation handler
482
451
  ├── pipeline/ # Constitution → policy compilation pipeline
452
+ ├── escalation/ # Escalation listener: session registry, TUI dashboard, state
483
453
  ├── signal/ # Signal messaging transport (bot daemon, setup, formatting)
484
- ├── docker/ # Docker agent mode, container management
454
+ ├── docker/ # Docker agent mode, PTY session, MITM proxy, adapters
455
+ ├── servers/ # Built-in MCP servers (fetch, web search providers)
485
456
  └── types/ # Shared type definitions
486
457
  ```
487
458