@letterblack/lbe-exec 1.2.2 → 1.2.4

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.
package/README.md CHANGED
@@ -1,26 +1,252 @@
1
1
  # @letterblack/lbe-exec
2
2
 
3
- Local host-signed execution for LetterBlack LBE.
3
+ **Local-first execution governance for AI agents.**
4
4
 
5
- This package is the trusted, in-process controller layer. It accepts simple
6
- requests, creates the timestamp/nonce/signature envelope locally, validates it
7
- through the bundled WASM runtime, and executes approved file or allowlisted
8
- shell operations inside the configured project root.
5
+ Every action any agent proposes — file write, shell command, anything passes
6
+ through a local validation gate before it can execute. One package, any agent,
7
+ zero cloud dependency, no daemon required.
8
+
9
+ ---
10
+
11
+ ## The problem
12
+
13
+ AI agents are capable. They can write files, invoke tools, and change state —
14
+ and they will, the moment they are given a reason to. The host is the only
15
+ thing standing between a plausible-looking request and irreversible damage.
16
+
17
+ That is not enough.
18
+
19
+ `@letterblack/lbe-exec` puts a deterministic local gate between what the agent
20
+ proposes and what the system actually executes — fully in-process, no cloud
21
+ dependency, no daemon required.
22
+
23
+ ---
24
+
25
+ ## How every request travels
26
+
27
+ ![LBE gate sequence — Request flows through Policy, Identity, and Scope gates before reaching Action. A rejected request is routed to denial before it reaches execution.](assets/lbe-gates.png)
28
+
29
+ Every request the agent produces enters a 7-gate pipeline. A failure at any
30
+ gate returns a structured denial — the remaining gates are not evaluated.
31
+
32
+ ```
33
+ [1] Schema required fields and structural validity
34
+
35
+ [2] Timestamp permitted clock-skew window (±10 minutes)
36
+
37
+ [3] Key lifecycle trusted key, active, not expired
38
+
39
+ [4] Signature Ed25519 request authenticity (signed locally, no network)
40
+
41
+ [5] Rate limit per-requester sliding-window limit
42
+
43
+ [6] Nonce single-use replay protection
44
+
45
+ [7] Policy configured authorization (deny-wins)
46
+
47
+ allow / deny / error — structured result returned to host
48
+ ```
49
+
50
+ The executor signs every request with a host-held key before validation.
51
+ No key material leaves the process.
52
+
53
+ ---
54
+
55
+ ## When a request is approved
56
+
57
+ ![Happy path — agent proposes action, identity confirmed, policy approved, governed write executed, audit chain extended, result returned to app.](assets/story-allow.png)
58
+
59
+ When a request clears every gate:
60
+
61
+ 1. The agent calls a convenience method — `lbe.writeFile()`, `lbe.runShell()`, etc.
62
+ 2. The executor constructs and signs the request locally with a host-held Ed25519 key.
63
+ 3. All seven gates pass. The project policy approves the action.
64
+ 4. The write or command executes inside the configured project root.
65
+ 5. The audit chain is extended — every approved action appends a hash-linked
66
+ entry to `.lbe/audit.jsonl`, permanently verifiable, impossible to silently remove.
67
+ 6. A structured result returns: whether it succeeded, which rules matched, and
68
+ the audit entry identifier.
69
+
70
+ The application stays in control. The executor decides whether the action was
71
+ permitted and hands the answer back.
72
+
73
+ ---
74
+
75
+ ## When a request is blocked
76
+
77
+ ![Deny path — rogue agent bypass attempt, policy gate immediate rejection, shell untouched, filesystem unchanged, immutable audit entry written, final state clean.](assets/story-deny.png)
78
+
79
+ When a request fails any gate:
80
+
81
+ 1. The agent attempts an action — whether by mistake, misconfiguration,
82
+ or a deliberate bypass attempt.
83
+ 2. The policy gate closes immediately. The request is denied before any adapter
84
+ is reached.
85
+ 3. The shell is untouched. The filesystem is unchanged. Nothing that should not
86
+ have happened, happened.
87
+ 4. The denial is written to the immutable audit log — chain sealed, evidence
88
+ preserved.
89
+ 5. The final state is clean, verifiable, and consistent.
90
+
91
+ No partial execution. No silent failures. Denial is a first-class outcome, not
92
+ an error.
93
+
94
+ ---
95
+
96
+ ## Policy — who writes the rules
97
+
98
+ Only the host application writes policy. Agents may propose a rule — the
99
+ proposal is returned as a plain object for the host to review. Until the host
100
+ explicitly accepts and writes it, the proposal has no effect on any gate.
101
+
102
+ This separation is intentional. An agent that can write its own policy rules
103
+ is an agent that can grant itself permission.
104
+
105
+ ---
106
+
107
+ ## What this covers
108
+
109
+ | Threat | Gate |
110
+ |---|---|
111
+ | Agent writes outside the project root | Scope — sandbox path check |
112
+ | Replayed or stale request | Identity — nonce and timestamp |
113
+ | Tampered or expired key | Identity — key lifecycle |
114
+ | Excessive requests | Identity — rate limit |
115
+ | Action not permitted by project policy | Policy — deny-wins evaluation |
116
+ | Unauthorized shell command | Scope — explicit command allowlist |
117
+ | Injected payload (eval, exec, __proto__) | Content scan before pipeline |
118
+
119
+ ---
120
+
121
+ ## Observer mode — start here
122
+
123
+ Not ready to block? Start in observer mode. Every request is fully validated
124
+ and logged exactly as it would be in enforcement — but nothing is blocked.
125
+
126
+ Watch what the agent is doing before you decide what to deny. Your rules take
127
+ effect the moment you switch to enforcement.
128
+
129
+ ---
130
+
131
+ ## Install
132
+
133
+ ```bash
134
+ npm install @letterblack/lbe-exec
135
+ npx lbe-exec init
136
+ ```
137
+
138
+ `npx lbe-exec init` scans the project, writes `lbe.policy.json` in observer
139
+ mode, generates `CLAUDE.md` and `.github/copilot-instructions.md` so every AI
140
+ agent automatically discovers and follows governance, and creates
141
+ `.lbe/AGENT_CONTRACT.md` as a machine-readable contract.
142
+
143
+ Requires Node.js ≥ 20.9.0.
144
+
145
+ ---
146
+
147
+ ## Use in agent code
9
148
 
10
149
  ```js
11
150
  import { createLocalExecutor } from '@letterblack/lbe-exec';
12
151
 
13
152
  const lbe = createLocalExecutor({ rootDir: process.cwd() });
14
- const preview = await lbe.dryRun({ intent: 'write_file', target: 'report.md', content: 'draft' });
15
- const result = await lbe.execute({ intent: 'write_file', target: 'report.md', content: 'approved' });
153
+
154
+ // Every call routes through the full 7-gate pipeline
155
+ await lbe.writeFile('output/report.md', content);
156
+ await lbe.readFile('src/config.json');
157
+ await lbe.patchFile('src/index.js', patch);
158
+ await lbe.deleteFile('tmp/scratch.txt');
159
+ await lbe.runShell('node', ['scripts/build.js']);
160
+
161
+ // Result shape
162
+ const result = await lbe.writeFile('output/result.md', data);
163
+ // { ok: true, decision: 'allow', executed: true, auditId: '...' }
164
+ // { ok: false, decision: 'deny', executed: false, error: { code, message } }
16
165
  ```
17
166
 
18
- `dryRun()` is strictly non-mutating: it does not create backups, write audit
19
- records, execute commands, modify files, or change policy.
167
+ No knowledge of the pipeline, request format, or policy internals required.
168
+ All signing, validation, and auditing happens automatically.
169
+
170
+ ### Options
171
+
172
+ ```js
173
+ const lbe = createLocalExecutor({
174
+ rootDir: process.cwd(), // sandbox root — no writes escape this path
175
+ mode: 'observe', // 'observe' (log only) or 'enforce' (block)
176
+ shell: {
177
+ allowCommands: ['node', 'npm'], // only these commands may run
178
+ denyCommands: ['rm', 'curl'], // always blocked regardless of policy
179
+ maxRequests: 20 // per-minute shell rate limit
180
+ }
181
+ });
182
+ ```
183
+
184
+ ### Policy management
185
+
186
+ ```js
187
+ // Propose a rule — returns an object for the host to review, writes nothing
188
+ const proposal = lbe.policy.proposeRule({
189
+ effect: 'deny',
190
+ type: 'path',
191
+ pattern: 'secrets/**',
192
+ from: 'agent: these files should not be modified'
193
+ });
194
+
195
+ // Host accepts and writes the rule
196
+ lbe.policy.addRule(proposal);
197
+
198
+ // Read current policy
199
+ const policy = lbe.policy.read();
200
+
201
+ // Verify the audit chain has not been tampered with
202
+ lbe.audit.verify();
203
+ ```
204
+
205
+ ---
206
+
207
+ ## CLI reference
208
+
209
+ ```bash
210
+ npx lbe-exec init # create lbe.policy.json in observer mode
211
+ npx lbe-exec status # show mode, rule count, and audit entry count
212
+ npx lbe-exec enforce # switch to blocking
213
+ npx lbe-exec observe # switch back to advisory
214
+ npx lbe-exec policy # list active rules
215
+ ```
216
+
217
+ | Command | Purpose |
218
+ |---|---|
219
+ | `npx lbe-exec init` | Bootstrap governance — policy, keys, agent files |
220
+ | `npx lbe-exec status` | Show mode, rule count, audit entry count |
221
+ | `npx lbe-exec policy` | List active rules |
222
+ | `npx lbe-exec observe` | Set advisory (log-only) mode |
223
+ | `npx lbe-exec enforce` | Set blocking mode |
224
+ | `npx lbe-exec execute` | Pipe a JSON request from stdin or `--input <file>` |
225
+
226
+ ---
227
+
228
+ ## What ships
229
+
230
+ ```
231
+ dist/index.js In-process executor — createLocalExecutor()
232
+ dist/cli.js Local CLI (npx lbe-exec)
233
+ dist/lbe_engine.wasm Verified WASM runtime binary
234
+ dist/wasm.lock.json Runtime integrity lock (SHA-256 of wasm binary)
235
+ assets/lbe-gates.png Gate sequence diagram
236
+ assets/story-allow.png Approved-request storyboard
237
+ assets/story-deny.png Blocked-request storyboard
238
+ assets/runtime-boundary.svg Runtime boundary diagram
239
+ types.d.ts TypeScript declarations
240
+ ```
241
+
242
+ Source code, tests, keys, and runtime state are not included.
243
+
244
+ ---
245
+
246
+ ## Limits
20
247
 
21
- Shell execution is denied unless its command is explicitly listed in
22
- `shell.allowCommands`. File operations are constrained to `rootDir`, including
23
- against traversal and symlink escapes.
248
+ This package governs actions routed through its executor. It does not provide
249
+ kernel-level process isolation, network-egress control, multi-tenant separation,
250
+ or a hosted control plane.
24
251
 
25
- Agents may call `policy.proposeRule()`. Only the trusted host/controller should
26
- call `policy.addRule()`.
252
+ For the raw WASM runtime without a controller, see `@letterblack/lbe-sdk`.
Binary file
@@ -0,0 +1,36 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="960" height="440" viewBox="0 0 960 440" role="img" aria-labelledby="title desc">
2
+ <title id="title">LetterBlack LBE runtime boundary</title>
3
+ <desc id="desc">An application sends a serialized request to the local LBE WASM validation runtime, which returns a structured allow, deny, or error decision to the application.</desc>
4
+ <defs>
5
+ <linearGradient id="panel" x1="0" x2="1"><stop stop-color="#172033"/><stop offset="1" stop-color="#202d47"/></linearGradient>
6
+ <linearGradient id="runtime" x1="0" x2="1"><stop stop-color="#3756d6"/><stop offset="1" stop-color="#7356d6"/></linearGradient>
7
+ <marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" fill="#8fa3c9"/></marker>
8
+ </defs>
9
+ <rect width="960" height="440" rx="24" fill="#0e1524"/>
10
+ <text x="56" y="65" fill="#f5f8ff" font-family="Arial, sans-serif" font-size="28" font-weight="700">Local validation boundary</text>
11
+ <text x="56" y="98" fill="#aab9d4" font-family="Arial, sans-serif" font-size="17">The host owns execution. LBE returns a deterministic decision before the host accepts an action.</text>
12
+
13
+ <rect x="55" y="165" width="220" height="170" rx="16" fill="url(#panel)" stroke="#405273"/>
14
+ <text x="165" y="220" text-anchor="middle" fill="#fff" font-family="Arial, sans-serif" font-size="21" font-weight="700">Host application</text>
15
+ <text x="165" y="254" text-anchor="middle" fill="#b9c7df" font-family="Arial, sans-serif" font-size="15">agent, tool, or service</text>
16
+ <text x="165" y="286" text-anchor="middle" fill="#7fe1c3" font-family="Arial, sans-serif" font-size="15">serializes request</text>
17
+
18
+ <line x1="285" y1="250" x2="402" y2="250" stroke="#8fa3c9" stroke-width="4" marker-end="url(#arrow)"/>
19
+ <text x="343" y="229" text-anchor="middle" fill="#aab9d4" font-family="Arial, sans-serif" font-size="14">JSON request</text>
20
+
21
+ <rect x="415" y="132" width="290" height="236" rx="18" fill="url(#runtime)" stroke="#9a8cff" stroke-width="2"/>
22
+ <text x="560" y="183" text-anchor="middle" fill="#fff" font-family="Arial, sans-serif" font-size="23" font-weight="700">LBE WASM runtime</text>
23
+ <line x1="453" y1="205" x2="667" y2="205" stroke="#bfc8ff" opacity=".55"/>
24
+ <text x="560" y="242" text-anchor="middle" fill="#f0efff" font-family="Arial, sans-serif" font-size="16">schema · timestamp · key lifecycle</text>
25
+ <text x="560" y="271" text-anchor="middle" fill="#f0efff" font-family="Arial, sans-serif" font-size="16">signature · rate limit · nonce · policy</text>
26
+ <text x="560" y="319" text-anchor="middle" fill="#d7d1ff" font-family="Arial, sans-serif" font-size="14">local, deterministic validation</text>
27
+
28
+ <line x1="718" y1="250" x2="825" y2="250" stroke="#8fa3c9" stroke-width="4" marker-end="url(#arrow)"/>
29
+ <text x="770" y="229" text-anchor="middle" fill="#aab9d4" font-family="Arial, sans-serif" font-size="14">structured result</text>
30
+
31
+ <rect x="838" y="165" width="80" height="170" rx="16" fill="url(#panel)" stroke="#405273"/>
32
+ <text x="878" y="215" text-anchor="middle" fill="#fff" font-family="Arial, sans-serif" font-size="16" font-weight="700">Host</text>
33
+ <text x="878" y="249" text-anchor="middle" fill="#7fe1c3" font-family="Arial, sans-serif" font-size="14">allow</text>
34
+ <text x="878" y="275" text-anchor="middle" fill="#ffadad" font-family="Arial, sans-serif" font-size="14">deny</text>
35
+ <text x="878" y="301" text-anchor="middle" fill="#ffd28a" font-family="Arial, sans-serif" font-size="14">error</text>
36
+ </svg>
Binary file
Binary file