@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 +164 -14
- package/assets/lbe-gates.png +0 -0
- package/assets/runtime-boundary.svg +36 -0
- package/assets/story-allow.png +0 -0
- package/assets/story-deny.png +0 -0
- package/dist/cli.js +2614 -0
- package/dist/index.js +40 -6
- package/package.json +5 -1
- package/types.d.ts +43 -12
package/README.md
CHANGED
|
@@ -1,26 +1,176 @@
|
|
|
1
1
|
# @letterblack/lbe-exec
|
|
2
2
|
|
|
3
|
-
Local
|
|
3
|
+
**Local-first execution governance for AI agents.**
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
+

|
|
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
|
+

|
|
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
|
+

|
|
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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|