@letterblack/lbe-core 1.3.4 → 1.3.6

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 (78) hide show
  1. package/.githooks/pre-commit +2 -0
  2. package/.githooks/pre-push +2 -0
  3. package/CHANGELOG.md +81 -0
  4. package/LICENSE +1 -1
  5. package/README.md +158 -170
  6. package/RELEASE_WORKSPACE_RULES.md +179 -0
  7. package/Release-README.md +67 -0
  8. package/WORKSPACE.md +422 -0
  9. package/_proof.mjs +246 -0
  10. package/assets/runtime-boundary.svg +36 -36
  11. package/bin/lbe.js +12 -0
  12. package/config/identity.config.json +3 -0
  13. package/config/policy.default.json +24 -0
  14. package/dist/cli/lbe.js +4431 -0
  15. package/dist/hooks/register.cjs +505 -0
  16. package/dist/state/appendCentral.cjs +87 -0
  17. package/dist/state/index.cjs +101 -0
  18. package/exec/cli.js +472 -0
  19. package/exec/index.js +2 -0
  20. package/index.js +24 -0
  21. package/npm-pack.json +0 -0
  22. package/package.json +77 -45
  23. package/release/README.md +216 -0
  24. package/release/TRUST.md +90 -0
  25. package/release/exec-README.md +215 -0
  26. package/release/exec-types.d.ts +50 -0
  27. package/release-exec/LICENSE +1 -0
  28. package/release-exec/README.md +215 -0
  29. package/release-exec/assets/lbe-gates.jpg +0 -0
  30. package/release-exec/assets/lbe-gates.png +0 -0
  31. package/release-exec/assets/runtime-boundary.svg +36 -0
  32. package/release-exec/assets/story-allow.jpg +0 -0
  33. package/release-exec/assets/story-allow.png +0 -0
  34. package/release-exec/assets/story-deny.jpg +0 -0
  35. package/release-exec/assets/story-deny.png +0 -0
  36. package/release-exec/dist/cli.js +2841 -0
  37. package/release-exec/dist/index.js +1835 -0
  38. package/release-exec/dist/lbe_engine.wasm +0 -0
  39. package/{dist → release-exec/dist}/wasm.lock.json +4 -5
  40. package/release-exec/hooks/register.cjs +473 -0
  41. package/release-exec/package.json +35 -0
  42. package/release-exec/types.d.ts +50 -0
  43. package/runtime/engine.js +322 -0
  44. package/runtime/lbe_engine.wasm +0 -0
  45. package/src/cli/commands/assertConsumer.js +198 -0
  46. package/src/cli/commands/auditVerify.js +36 -0
  47. package/src/cli/commands/dryrun.js +175 -0
  48. package/src/cli/commands/health.js +153 -0
  49. package/src/cli/commands/init.js +306 -0
  50. package/src/cli/commands/integrityCheck.js +57 -0
  51. package/src/cli/commands/logs.js +53 -0
  52. package/src/cli/commands/openState.js +44 -0
  53. package/src/cli/commands/policyAdd.js +8 -0
  54. package/src/cli/commands/policyMode.js +7 -0
  55. package/src/cli/commands/policySign.js +72 -0
  56. package/src/cli/commands/proof.js +102 -0
  57. package/src/cli/commands/run.js +342 -0
  58. package/src/cli/commands/status.js +73 -0
  59. package/src/cli/commands/verify.js +144 -0
  60. package/src/cli/main.js +181 -0
  61. package/src/cli/parseArgs.js +115 -0
  62. package/src/exec/localExecutor.js +289 -0
  63. package/src/hooks/register.cjs +505 -0
  64. package/src/state/appendCentral.cjs +87 -0
  65. package/src/state/fileIndex.js +140 -0
  66. package/src/state/index.cjs +101 -0
  67. package/src/state/index.js +65 -0
  68. package/src/state/intentRegistry.js +84 -0
  69. package/src/state/migration.js +112 -0
  70. package/src/state/proofRunner.js +246 -0
  71. package/src/state/stateRoot.js +40 -0
  72. package/src/state/targetRegistry.js +109 -0
  73. package/src/state/workspaceId.js +40 -0
  74. package/src/state/workspaceRegistry.js +65 -0
  75. package/types.d.ts +175 -2
  76. package/dist/cli.js +0 -141
  77. package/dist/index.js +0 -52
  78. /package/dist/{lbe_engine.wasm → cli/lbe_engine.wasm} +0 -0
@@ -1,5 +1,4 @@
1
- {
2
- "wasm_sha256": "feb93a2ea76a02584048a588305ba0a192683fd58c1da8f539aeb49b41d0a254",
3
- "entrypoint": "lbe_execute",
4
- "contract": "execute(input:string)->string"
5
- }
1
+ {
2
+ "wasm_sha256": "feb93a2ea76a02584048a588305ba0a192683fd58c1da8f539aeb49b41d0a254",
3
+ "entrypoint": "lbe_execute"
4
+ }
@@ -0,0 +1,473 @@
1
+ 'use strict';
2
+ // LBE Agent Bridge — CJS preload hook
3
+ // Load with: node --require @letterblack/lbe-exec/hooks/register.cjs agent.js
4
+ // Or via: npx lbe-exec run-node [--mode observe|enforce] agent.js
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const { EventEmitter } = require('events');
9
+ const { Readable } = require('stream');
10
+
11
+ const ROOT_DIR = process.env.LBE_ROOT || process.cwd();
12
+ const MODE = process.env.LBE_MODE || 'observe';
13
+
14
+ // ── Policy loader (inline CJS — ESM cannot be require()'d synchronously) ────
15
+
16
+ function loadPolicy() {
17
+ const policyPath = path.join(ROOT_DIR, 'lbe.policy.json');
18
+ try {
19
+ if (fs.existsSync(policyPath)) {
20
+ return JSON.parse(fs.readFileSync(policyPath, 'utf8'));
21
+ }
22
+ } catch (e) { /* fall through to default */ }
23
+ return { version: 1, mode: MODE, workspace: ROOT_DIR, rules: [] };
24
+ }
25
+
26
+ function globToRegex(pattern) {
27
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
28
+ return new RegExp('^' + escaped
29
+ .replace(/\*\*\//g, '(?:.*/)?')
30
+ .replace(/\*\*/g, '.*')
31
+ .replace(/\*/g, '[^/]*') + '$');
32
+ }
33
+
34
+ function evaluatePolicy(action) {
35
+ const policy = loadPolicy();
36
+ const mode = policy.mode || MODE;
37
+ const rules = Array.isArray(policy.rules) ? policy.rules : [];
38
+
39
+ if (action.path) {
40
+ try {
41
+ const abs = path.resolve(ROOT_DIR, action.path);
42
+ const sep = path.sep;
43
+ if (!abs.startsWith(ROOT_DIR + sep) && abs !== ROOT_DIR) {
44
+ return { decision: 'deny', deny: true, reason: 'PATH_OUTSIDE_ROOT', matchedRules: ['path:outside_root'], mode, enforced: mode === 'enforce' };
45
+ }
46
+ const rel = path.relative(ROOT_DIR, abs).split(sep).join('/');
47
+ const matched = rules.filter(r => r.type === 'path' && globToRegex(r.pattern).test(rel));
48
+ const denied = matched.filter(r => r.effect === 'deny');
49
+ const isDeny = denied.length > 0;
50
+ return {
51
+ decision: isDeny ? 'deny' : 'allow', deny: isDeny,
52
+ matchedRules: (isDeny ? denied : matched.filter(r => r.effect === 'allow')).map(r => r.id),
53
+ mode, enforced: mode === 'enforce',
54
+ };
55
+ } catch (e) {
56
+ if (mode === 'enforce') {
57
+ return { decision: 'deny', deny: true, reason: 'PATH_RESOLUTION_ERROR', matchedRules: [], mode, enforced: true };
58
+ }
59
+ }
60
+ }
61
+
62
+ if (action.cmd) {
63
+ const matched = rules.filter(r => r.type === 'command' && globToRegex(r.pattern).test(String(action.cmd)));
64
+ const denied = matched.filter(r => r.effect === 'deny');
65
+ const isDeny = denied.length > 0;
66
+ return {
67
+ decision: isDeny ? 'deny' : 'allow', deny: isDeny,
68
+ matchedRules: (isDeny ? denied : matched.filter(r => r.effect === 'allow')).map(r => r.id),
69
+ mode, enforced: mode === 'enforce',
70
+ };
71
+ }
72
+
73
+ return { decision: 'allow', deny: false, matchedRules: [], mode, enforced: mode === 'enforce' };
74
+ }
75
+
76
+ // ── Audit writer ──────────────────────────────────────────────────────────────
77
+ // fs.appendFileSync → fs.writeFileSync (Node.js internal) → would recurse.
78
+ // fs.openSync/writeSync/closeSync are low-level bindings — never call back into JS.
79
+ // Re-entrant guard prevents recursion from any code path we missed.
80
+
81
+ var _auditInFlight = false;
82
+
83
+ function auditEvent(entry) {
84
+ if (_auditInFlight) return;
85
+ _auditInFlight = true;
86
+ try {
87
+ var eventsPath = path.join(ROOT_DIR, '.lbe', 'events.jsonl');
88
+ var dir = path.dirname(eventsPath);
89
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
90
+ var line = JSON.stringify({ ts: Math.floor(Date.now() / 1000), ...entry }) + '\n';
91
+ // Use open/write/close directly — bypasses all JS wrappers including writeFileSync
92
+ var fd = fs.openSync(eventsPath, 'a');
93
+ try { fs.writeSync(fd, line); } finally { fs.closeSync(fd); }
94
+ } catch (e) {
95
+ console.warn('[lbe] audit write failed:', e.message);
96
+ } finally {
97
+ _auditInFlight = false;
98
+ }
99
+ }
100
+
101
+ class LBEPermissionError extends Error {
102
+ constructor(decision, action) {
103
+ const target = action.path || action.cmd || 'unknown';
104
+ super('[LBE:' + decision.mode + '] DENIED ' + action.action + ' on ' + target);
105
+ this.name = 'LBEPermissionError';
106
+ this.code = 'LBE_PERMISSION_DENIED';
107
+ this.lbeDecision = decision;
108
+ }
109
+ }
110
+
111
+ // ── Originals — captured BEFORE any patch is applied ────────────────────────
112
+
113
+ const origFs = {
114
+ writeFile: fs.writeFile.bind(fs),
115
+ writeFileSync: fs.writeFileSync.bind(fs),
116
+ rm: fs.rm ? fs.rm.bind(fs) : null,
117
+ rmSync: fs.rmSync ? fs.rmSync.bind(fs) : null,
118
+ unlink: fs.unlink.bind(fs),
119
+ unlinkSync: fs.unlinkSync.bind(fs),
120
+ rename: fs.rename.bind(fs),
121
+ renameSync: fs.renameSync.bind(fs),
122
+ };
123
+
124
+ const origPromises = {
125
+ writeFile: fs.promises.writeFile.bind(fs.promises),
126
+ rm: fs.promises.rm ? fs.promises.rm.bind(fs.promises) : null,
127
+ unlink: fs.promises.unlink.bind(fs.promises),
128
+ rename: fs.promises.rename.bind(fs.promises),
129
+ };
130
+
131
+ const cp = require('child_process');
132
+ const origCp = {
133
+ spawn: cp.spawn.bind(cp),
134
+ spawnSync: cp.spawnSync.bind(cp),
135
+ exec: cp.exec.bind(cp),
136
+ execSync: cp.execSync.bind(cp),
137
+ };
138
+
139
+ // ── Denied spawn stub — EventEmitter-compatible ───────────────────────────────
140
+
141
+ function makeFailedChildProcess(err) {
142
+ const emitter = new EventEmitter();
143
+ emitter.pid = null;
144
+ emitter.killed = false;
145
+ emitter.exitCode = 1;
146
+ emitter.signalCode = null;
147
+ emitter.stdout = new Readable({ read() {} });
148
+ emitter.stderr = new Readable({ read() {} });
149
+ emitter.stdin = { write() { return false; }, end() {}, destroy() {} };
150
+ emitter.kill = () => false;
151
+ emitter.ref = () => emitter;
152
+ emitter.unref = () => emitter;
153
+ process.nextTick(function () {
154
+ emitter.stdout.push(null);
155
+ emitter.stderr.push(null);
156
+ emitter.emit('error', err);
157
+ emitter.emit('close', 1, null);
158
+ emitter.emit('exit', 1, null);
159
+ });
160
+ return emitter;
161
+ }
162
+
163
+ // ── Decision + pre-block audit ────────────────────────────────────────────────
164
+
165
+ function decide(action) {
166
+ var decision;
167
+ try {
168
+ decision = evaluatePolicy(action);
169
+ } catch (e) {
170
+ if (MODE === 'enforce') {
171
+ var failErr = new LBEPermissionError({ mode: 'enforce', enforced: true }, action);
172
+ try { auditEvent({ action: action.action, path: action.path, cmd: action.cmd, actor: 'agent:lbe-hooks', decision: 'deny', mode: 'enforce', enforced: true, executed: false, matched_rules: [], error: e.message }); } catch (_) {}
173
+ return { blocked: true, error: failErr };
174
+ }
175
+ console.warn('[lbe] policy evaluation failed (observe mode, allowing):', e.message);
176
+ return { blocked: false, decision: { decision: 'allow', deny: false, matchedRules: [], mode: 'observe', enforced: false } };
177
+ }
178
+
179
+ if (decision.deny && decision.enforced) {
180
+ var err = new LBEPermissionError(decision, action);
181
+ try { auditEvent({ action: action.action, path: action.path, cmd: action.cmd, actor: 'agent:lbe-hooks', decision: 'deny', mode: decision.mode, enforced: true, executed: false, matched_rules: decision.matchedRules }); } catch (_) {}
182
+ return { blocked: true, error: err };
183
+ }
184
+
185
+ return { blocked: false, decision: decision };
186
+ }
187
+
188
+ function postAudit(action, decision, executed, extra) {
189
+ try {
190
+ auditEvent(Object.assign({ action: action.action, path: action.path, cmd: action.cmd, actor: 'agent:lbe-hooks', decision: decision.decision, mode: decision.mode, enforced: decision.enforced, executed: executed, matched_rules: decision.matchedRules }, extra || {}));
191
+ } catch (e) {
192
+ console.warn('[lbe] post-action audit failed (result unaffected):', e.message);
193
+ }
194
+ }
195
+
196
+ // ── Patch fs callbacks ────────────────────────────────────────────────────────
197
+
198
+ fs.writeFile = function lbeWriteFile(filePath, data, options, callback) {
199
+ if (typeof options === 'function') { callback = options; options = undefined; }
200
+ var action = { action: 'file_write', path: String(filePath) };
201
+ var check = decide(action);
202
+ if (check.blocked) { process.nextTick(function () { callback(check.error); }); return; }
203
+ function done(err) {
204
+ postAudit(action, check.decision, !err, err ? { error: err.message } : null);
205
+ callback(err);
206
+ }
207
+ if (options !== undefined) { origFs.writeFile(filePath, data, options, done); }
208
+ else { origFs.writeFile(filePath, data, done); }
209
+ };
210
+
211
+ fs.writeFileSync = function lbeWriteFileSync(filePath, data, options) {
212
+ var action = { action: 'file_write', path: String(filePath) };
213
+ var check = decide(action);
214
+ if (check.blocked) { throw check.error; }
215
+ try {
216
+ var result = options !== undefined ? origFs.writeFileSync(filePath, data, options) : origFs.writeFileSync(filePath, data);
217
+ postAudit(action, check.decision, true);
218
+ return result;
219
+ } catch (e) {
220
+ postAudit(action, check.decision, false, { error: e.message });
221
+ throw e;
222
+ }
223
+ };
224
+
225
+ if (origFs.rm) {
226
+ fs.rm = function lbeRm(filePath, options, callback) {
227
+ if (typeof options === 'function') { callback = options; options = undefined; }
228
+ var action = { action: 'file_delete', path: String(filePath) };
229
+ var check = decide(action);
230
+ if (check.blocked) { process.nextTick(function () { callback(check.error); }); return; }
231
+ function done(err) {
232
+ postAudit(action, check.decision, !err, err ? { error: err.message } : null);
233
+ callback(err);
234
+ }
235
+ if (options !== undefined) { origFs.rm(filePath, options, done); }
236
+ else { origFs.rm(filePath, done); }
237
+ };
238
+ }
239
+
240
+ if (origFs.rmSync) {
241
+ fs.rmSync = function lbeRmSync(filePath, options) {
242
+ var action = { action: 'file_delete', path: String(filePath) };
243
+ var check = decide(action);
244
+ if (check.blocked) { throw check.error; }
245
+ try {
246
+ var result = options !== undefined ? origFs.rmSync(filePath, options) : origFs.rmSync(filePath);
247
+ postAudit(action, check.decision, true);
248
+ return result;
249
+ } catch (e) {
250
+ postAudit(action, check.decision, false, { error: e.message });
251
+ throw e;
252
+ }
253
+ };
254
+ }
255
+
256
+ fs.unlink = function lbeUnlink(filePath, options, callback) {
257
+ if (typeof options === 'function') { callback = options; options = undefined; }
258
+ var action = { action: 'file_delete', path: String(filePath) };
259
+ var check = decide(action);
260
+ if (check.blocked) { process.nextTick(function () { callback(check.error); }); return; }
261
+ origFs.unlink(filePath, function done(err) {
262
+ postAudit(action, check.decision, !err, err ? { error: err.message } : null);
263
+ callback(err);
264
+ });
265
+ };
266
+
267
+ fs.unlinkSync = function lbeUnlinkSync(filePath) {
268
+ var action = { action: 'file_delete', path: String(filePath) };
269
+ var check = decide(action);
270
+ if (check.blocked) { throw check.error; }
271
+ try {
272
+ var result = origFs.unlinkSync(filePath);
273
+ postAudit(action, check.decision, true);
274
+ return result;
275
+ } catch (e) {
276
+ postAudit(action, check.decision, false, { error: e.message });
277
+ throw e;
278
+ }
279
+ };
280
+
281
+ fs.rename = function lbeRename(oldPath, newPath, options, callback) {
282
+ if (typeof options === 'function') { callback = options; options = undefined; }
283
+ var action = { action: 'file_rename', path: String(oldPath), dest: String(newPath) };
284
+ var check = decide(action);
285
+ if (check.blocked) { process.nextTick(function () { callback(check.error); }); return; }
286
+ origFs.rename(oldPath, newPath, function done(err) {
287
+ postAudit(action, check.decision, !err, err ? { error: err.message } : null);
288
+ callback(err);
289
+ });
290
+ };
291
+
292
+ fs.renameSync = function lbeRenameSync(oldPath, newPath) {
293
+ var action = { action: 'file_rename', path: String(oldPath), dest: String(newPath) };
294
+ var check = decide(action);
295
+ if (check.blocked) { throw check.error; }
296
+ try {
297
+ var result = origFs.renameSync(oldPath, newPath);
298
+ postAudit(action, check.decision, true);
299
+ return result;
300
+ } catch (e) {
301
+ postAudit(action, check.decision, false, { error: e.message });
302
+ throw e;
303
+ }
304
+ };
305
+
306
+ // ── Patch fs.promises ─────────────────────────────────────────────────────────
307
+
308
+ fs.promises.writeFile = async function lbePromisesWriteFile(filePath, data, options) {
309
+ var action = { action: 'file_write', path: String(filePath) };
310
+ var check = decide(action);
311
+ if (check.blocked) { throw check.error; }
312
+ try {
313
+ var result = options !== undefined
314
+ ? await origPromises.writeFile(filePath, data, options)
315
+ : await origPromises.writeFile(filePath, data);
316
+ postAudit(action, check.decision, true);
317
+ return result;
318
+ } catch (e) {
319
+ postAudit(action, check.decision, false, { error: e.message });
320
+ throw e;
321
+ }
322
+ };
323
+
324
+ if (origPromises.rm) {
325
+ fs.promises.rm = async function lbePromisesRm(filePath, options) {
326
+ var action = { action: 'file_delete', path: String(filePath) };
327
+ var check = decide(action);
328
+ if (check.blocked) { throw check.error; }
329
+ try {
330
+ var result = options !== undefined
331
+ ? await origPromises.rm(filePath, options)
332
+ : await origPromises.rm(filePath);
333
+ postAudit(action, check.decision, true);
334
+ return result;
335
+ } catch (e) {
336
+ postAudit(action, check.decision, false, { error: e.message });
337
+ throw e;
338
+ }
339
+ };
340
+ }
341
+
342
+ fs.promises.unlink = async function lbePromisesUnlink(filePath) {
343
+ var action = { action: 'file_delete', path: String(filePath) };
344
+ var check = decide(action);
345
+ if (check.blocked) { throw check.error; }
346
+ try {
347
+ var result = await origPromises.unlink(filePath);
348
+ postAudit(action, check.decision, true);
349
+ return result;
350
+ } catch (e) {
351
+ postAudit(action, check.decision, false, { error: e.message });
352
+ throw e;
353
+ }
354
+ };
355
+
356
+ fs.promises.rename = async function lbePromisesRename(oldPath, newPath) {
357
+ var action = { action: 'file_rename', path: String(oldPath), dest: String(newPath) };
358
+ var check = decide(action);
359
+ if (check.blocked) { throw check.error; }
360
+ try {
361
+ var result = await origPromises.rename(oldPath, newPath);
362
+ postAudit(action, check.decision, true);
363
+ return result;
364
+ } catch (e) {
365
+ postAudit(action, check.decision, false, { error: e.message });
366
+ throw e;
367
+ }
368
+ };
369
+
370
+ // ── Patch child_process ───────────────────────────────────────────────────────
371
+
372
+ cp.spawn = function lbeSpawn(cmd, args, options) {
373
+ if (args && !Array.isArray(args)) { options = args; args = []; }
374
+ var cwd = options && options.cwd ? String(options.cwd) : ROOT_DIR;
375
+ var shell = !!(options && options.shell);
376
+ var action = { action: 'run_shell', cmd: String(cmd), args: args || [], cwd: cwd, shell: shell };
377
+ var check = decide(action);
378
+ if (check.blocked) { return makeFailedChildProcess(check.error); }
379
+ var child = origCp.spawn(cmd, args || [], options || {});
380
+ child.on('close', function (code) { postAudit(action, check.decision, code === 0, { exit_code: code }); });
381
+ return child;
382
+ };
383
+
384
+ cp.spawnSync = function lbeSpawnSync(cmd, args, options) {
385
+ if (args && !Array.isArray(args)) { options = args; args = []; }
386
+ var cwd = options && options.cwd ? String(options.cwd) : ROOT_DIR;
387
+ var shell = !!(options && options.shell);
388
+ var action = { action: 'run_shell', cmd: String(cmd), args: args || [], cwd: cwd, shell: shell };
389
+ var check = decide(action);
390
+ if (check.blocked) {
391
+ return { pid: 0, output: [null, Buffer.alloc(0), Buffer.alloc(0)], stdout: Buffer.alloc(0), stderr: Buffer.alloc(0), status: 1, signal: null, error: check.error };
392
+ }
393
+ var result = origCp.spawnSync(cmd, args || [], options || {});
394
+ postAudit(action, check.decision, result.status === 0, { exit_code: result.status });
395
+ return result;
396
+ };
397
+
398
+ cp.exec = function lbeExec(command, options, callback) {
399
+ if (typeof options === 'function') { callback = options; options = undefined; }
400
+ var cwd = options && options.cwd ? String(options.cwd) : ROOT_DIR;
401
+ var action = { action: 'run_shell', cmd: String(command), args: [], cwd: cwd, shell: true };
402
+ var check = decide(action);
403
+ if (check.blocked) {
404
+ process.nextTick(function () { if (callback) callback(check.error, '', ''); });
405
+ return makeFailedChildProcess(check.error);
406
+ }
407
+ var cb = function done(err, stdout, stderr) {
408
+ postAudit(action, check.decision, !err, err ? { error: err.message } : null);
409
+ if (callback) callback(err, stdout, stderr);
410
+ };
411
+ return options !== undefined ? origCp.exec(command, options, cb) : origCp.exec(command, cb);
412
+ };
413
+
414
+ cp.execSync = function lbeExecSync(command, options) {
415
+ var cwd = options && options.cwd ? String(options.cwd) : ROOT_DIR;
416
+ var action = { action: 'run_shell', cmd: String(command), args: [], cwd: cwd, shell: true };
417
+ var check = decide(action);
418
+ if (check.blocked) { throw check.error; }
419
+ try {
420
+ var result = options !== undefined ? origCp.execSync(command, options) : origCp.execSync(command);
421
+ postAudit(action, check.decision, true);
422
+ return result;
423
+ } catch (e) {
424
+ postAudit(action, check.decision, false, { error: e.message });
425
+ throw e;
426
+ }
427
+ };
428
+
429
+ // ── Write hook-status.json (uses origFs — captured before patching) ───────────
430
+
431
+ process.env.LBE_HOOK_ACTIVE = '1';
432
+
433
+ try {
434
+ var statusDir = path.join(ROOT_DIR, '.lbe', 'runtime');
435
+ if (!fs.existsSync(statusDir)) fs.mkdirSync(statusDir, { recursive: true });
436
+ var status = {
437
+ active: true,
438
+ pid: process.pid,
439
+ started_at: new Date().toISOString(),
440
+ mode: MODE,
441
+ root: ROOT_DIR,
442
+ patched: {
443
+ 'fs.writeFile': true,
444
+ 'fs.writeFileSync': true,
445
+ 'fs.rm': !!origFs.rm,
446
+ 'fs.rmSync': !!origFs.rmSync,
447
+ 'fs.unlink': true,
448
+ 'fs.unlinkSync': true,
449
+ 'fs.rename': true,
450
+ 'fs.renameSync': true,
451
+ 'fs.promises.writeFile': true,
452
+ 'fs.promises.rm': !!origPromises.rm,
453
+ 'fs.promises.unlink': true,
454
+ 'fs.promises.rename': true,
455
+ 'child_process.spawn': true,
456
+ 'child_process.spawnSync': true,
457
+ 'child_process.exec': true,
458
+ 'child_process.execSync': true,
459
+ }
460
+ };
461
+ // Use original writeFileSync (pre-patch) to avoid triggering our own hook
462
+ origFs.writeFileSync(path.join(statusDir, 'hook-status.json'), JSON.stringify(status, null, 2) + '\n', 'utf8');
463
+ } catch (e) {
464
+ console.warn('[lbe] could not write hook-status.json:', e.message);
465
+ }
466
+
467
+ // ── Banner ────────────────────────────────────────────────────────────────────
468
+
469
+ if (MODE === 'observe') {
470
+ process.stderr.write('[lbe] OBSERVE mode — no actions blocked, all writes logged to .lbe/events.jsonl\n');
471
+ } else if (MODE === 'enforce') {
472
+ process.stderr.write('[lbe] ENFORCE mode — policy denials will block execution\n');
473
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@letterblack/lbe-exec",
3
+ "version": "1.2.20",
4
+ "description": "Local host-signed execution layer for LetterBlack LBE.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "types.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./types.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./hooks/register.cjs": "./hooks/register.cjs"
14
+ },
15
+ "bin": {
16
+ "lbe-exec": "dist/cli.js"
17
+ },
18
+ "files": [
19
+ "dist/",
20
+ "hooks/",
21
+ "assets/",
22
+ "README.md",
23
+ "TRUST.md",
24
+ "types.d.ts",
25
+ "LICENSE"
26
+ ],
27
+ "dependencies": {
28
+ "tweetnacl": "^1.0.3",
29
+ "json-canonicalize": "^1.0.4"
30
+ },
31
+ "license": "SEE LICENSE IN LICENSE",
32
+ "engines": {
33
+ "node": ">=20.9.0"
34
+ }
35
+ }
@@ -0,0 +1,50 @@
1
+ export interface LBEResult {
2
+ ok: boolean;
3
+ decision: 'allow' | 'deny' | 'observe';
4
+ executed: boolean;
5
+ dryRun: boolean;
6
+ error?: { code: string; message: string; recoverable: boolean };
7
+ matchedRules?: string[];
8
+ auditId?: string;
9
+ rollback?: { available: boolean; performed: boolean; backupId?: string };
10
+ }
11
+
12
+ export interface LBEPolicyRule {
13
+ effect: 'allow' | 'deny';
14
+ type: 'path' | 'command';
15
+ pattern: string;
16
+ from: string;
17
+ }
18
+
19
+ export interface LocalExecutor {
20
+ rootDir: string;
21
+
22
+ // High-level API — use these in agent code
23
+ writeFile(target: string, content: string): Promise<LBEResult>;
24
+ readFile(target: string): Promise<LBEResult>;
25
+ patchFile(target: string, content: string): Promise<LBEResult>;
26
+ deleteFile(target: string): Promise<LBEResult>;
27
+ runShell(cmd: string, args?: string[], opts?: { cwd?: string; timeoutMs?: number; maxOutputBytes?: number }): Promise<LBEResult>;
28
+
29
+ // Policy management
30
+ policy: {
31
+ read(): unknown;
32
+ proposeRule(rule: LBEPolicyRule): unknown;
33
+ addRule(rule: LBEPolicyRule): unknown;
34
+ };
35
+
36
+ // Audit
37
+ audit: { verify(): unknown };
38
+
39
+ // Low-level — for advanced / non-standard use only
40
+ validate(request: unknown): Promise<LBEResult>;
41
+ dryRun(request: unknown): Promise<LBEResult>;
42
+ execute(request: unknown): Promise<LBEResult>;
43
+ }
44
+
45
+ export function createLocalExecutor(options?: {
46
+ rootDir?: string;
47
+ keyId?: string;
48
+ mode?: 'observe' | 'enforce';
49
+ shell?: { allowCommands?: string[]; denyCommands?: string[]; maxRequests?: number };
50
+ }): LocalExecutor;