@letterblack/lbe-exec 1.2.2 → 1.2.3

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,176 @@
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 SDK, any agent,
7
+ zero cloud dependency.
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 gate sequence:
30
+
31
+ **Policy** — does the current project policy allow this action? Deny rules
32
+ always win. If any deny rule matches, the gate closes before any other check
33
+ runs.
34
+
35
+ **Identity** — is the request signed by a trusted, active key? The executor
36
+ signs every request locally with an Ed25519 key it holds in memory. Expired
37
+ keys, tampered signatures, and replayed nonces are all rejected.
38
+
39
+ **Scope** — is the target inside the allowed workspace? Paths outside the
40
+ project root, symlink escapes, and commands outside the explicit allowlist
41
+ are blocked regardless of policy.
42
+
43
+ Only a request that clears all three gates reaches **Action** — the point
44
+ where something is actually written to disk or a command actually runs.
45
+
46
+ ---
47
+
48
+ ## When a request is approved
49
+
50
+ ![Happy path — agent proposes action, identity confirmed, policy approved, governed write executed, audit chain extended, result returned to app.](assets/story-allow.png)
51
+
52
+ When a request clears every gate:
53
+
54
+ 1. The agent produces an action proposal — intent, target, content.
55
+ 2. The executor signs it locally with a host-held Ed25519 key. No key material
56
+ leaves the process.
57
+ 3. The project policy is evaluated. The action is approved.
58
+ 4. The write or command executes inside the configured project root.
59
+ 5. The audit chain is extended. Every approved action appends a hash-linked
60
+ entry to `lbe.audit.jsonl` — permanently verifiable, impossible to silently
61
+ remove.
62
+ 6. A structured result is returned: whether it succeeded, which rules matched,
63
+ and the audit entry identifier.
64
+
65
+ The application stays in control. The executor does not decide what to do with
66
+ the result — it decides whether the action was permitted and hands the answer
67
+ back.
68
+
69
+ ---
70
+
71
+ ## When a request is blocked
72
+
73
+ ![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)
74
+
75
+ When a request fails any gate:
76
+
77
+ 1. The agent attempts an action — whether by mistake, misconfiguration,
78
+ or a deliberate bypass attempt.
79
+ 2. The policy gate closes immediately. The request is denied before any adapter
80
+ is reached.
81
+ 3. The shell is untouched. The filesystem is unchanged. Nothing that should not
82
+ have happened, happened.
83
+ 4. The denial is written to the immutable audit log — chain sealed, evidence
84
+ preserved.
85
+ 5. The final state is clean, verifiable, and consistent.
86
+
87
+ No partial execution. No silent failures. Denial is a first-class outcome, not
88
+ an error.
89
+
90
+ ---
91
+
92
+ ## Policy — who writes the rules
93
+
94
+ Only the host application writes policy. Agents may propose a rule — the
95
+ proposal is returned as a plain object for the host to review. Until the host
96
+ explicitly accepts and writes it, the proposal has no effect on any gate.
97
+
98
+ This separation is intentional. An agent that can write its own policy rules
99
+ is an agent that can grant itself permission.
100
+
101
+ ---
102
+
103
+ ## Observer mode — start here
104
+
105
+ Not ready to block? Start in observer mode. Every request is fully validated
106
+ and logged exactly as it would be in enforcement — but nothing is blocked.
107
+
108
+ Watch what the agent is doing before you decide what to deny. Your rules take
109
+ effect the moment you switch to enforcement.
110
+
111
+ ```bash
112
+ npx lbe-exec init # create lbe.policy.json — starts in observer mode
113
+ npx lbe-exec status # see mode, rule count, audit entry count
114
+ npx lbe-exec enforce # switch to blocking
115
+ ```
116
+
117
+ ---
118
+
119
+ ## What this covers
120
+
121
+ | Threat | Gate |
122
+ |---|---|
123
+ | Agent writes outside the project root | Scope — path check |
124
+ | Replayed or stale request | Identity — nonce and timestamp |
125
+ | Tampered or expired key | Identity — key lifecycle |
126
+ | Excessive requests | Identity — rate limit |
127
+ | Action not permitted by project policy | Policy — deny-wins evaluation |
128
+ | Unauthorized shell command | Scope — explicit command allowlist |
129
+
130
+ ---
131
+
132
+ ## Install
133
+
134
+ ```bash
135
+ npm install @letterblack/lbe-exec
136
+ npx lbe-exec init
137
+ ```
138
+
139
+ `npx lbe-exec init` scans the project, writes `lbe.policy.json` in observer mode,
140
+ generates `CLAUDE.md` and `.github/copilot-instructions.md` so every AI agent
141
+ automatically discovers and follows governance without the user explaining it,
142
+ and creates `.lbe/AGENT_CONTRACT.md` as a machine-readable contract.
143
+
144
+ Requires Node.js ≥ 20.9.0.
145
+
146
+ ## Use in agent code
9
147
 
10
148
  ```js
11
149
  import { createLocalExecutor } from '@letterblack/lbe-exec';
12
150
 
13
151
  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' });
152
+
153
+ await lbe.writeFile('output/report.md', content);
154
+ await lbe.readFile('src/config.json');
155
+ await lbe.patchFile('src/index.js', patch);
156
+ await lbe.deleteFile('tmp/scratch.txt');
157
+ await lbe.runShell('node', ['scripts/build.js']);
158
+
159
+ const result = await lbe.writeFile('output/result.md', data);
160
+ // { ok: true, decision: 'allow', executed: true }
161
+ // { ok: false, decision: 'deny', error: { message: '...' } }
16
162
  ```
17
163
 
18
- `dryRun()` is strictly non-mutating: it does not create backups, write audit
19
- records, execute commands, modify files, or change policy.
164
+ No knowledge of the validation pipeline, request format, or policy internals
165
+ required. Every method routes through the full gate sequence and returns a
166
+ structured result.
167
+
168
+ ---
169
+
170
+ ## What ships
20
171
 
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.
172
+ This package contains the in-process execution controller and the WASM
173
+ validation runtime. It does not contain a hosted service, daemon, or network
174
+ dependency.
24
175
 
25
- Agents may call `policy.proposeRule()`. Only the trusted host/controller should
26
- call `policy.addRule()`.
176
+ Source code, tests, keys, and runtime state are never included.
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