@letterblack/lbe-exec 1.2.15 → 1.2.17
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/dist/cli.js +189 -276
- package/dist/index.js +1 -1
- package/hooks/register.cjs +4 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -404,7 +404,7 @@ var init_localPolicy = __esm({
|
|
|
404
404
|
"src/core/localPolicy.js"() {
|
|
405
405
|
init_auditLog();
|
|
406
406
|
init_atomicWrite();
|
|
407
|
-
POLICY_FILE = "lbe
|
|
407
|
+
POLICY_FILE = ".lbe/policy.json";
|
|
408
408
|
AUDIT_FILE = ".lbe/audit.jsonl";
|
|
409
409
|
}
|
|
410
410
|
});
|
|
@@ -2363,12 +2363,8 @@ This project has LetterBlack LBE (Local-first execution Governance) active.
|
|
|
2363
2363
|
|
|
2364
2364
|
## Your role as an agent
|
|
2365
2365
|
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
LBE executor. The executor validates every action against the project policy,
|
|
2369
|
-
signs it, audits it, and either allows or blocks it \u2014 transparently.
|
|
2370
|
-
|
|
2371
|
-
You do not need to know how this works. You only need to use the right methods.
|
|
2366
|
+
Every file write, delete, and shell command must go through the LBE executor.
|
|
2367
|
+
The executor validates each action against the project policy and audits it.
|
|
2372
2368
|
|
|
2373
2369
|
## How to perform actions
|
|
2374
2370
|
|
|
@@ -2376,143 +2372,83 @@ You do not need to know how this works. You only need to use the right methods.
|
|
|
2376
2372
|
import { createLocalExecutor } from '@letterblack/lbe-exec';
|
|
2377
2373
|
const lbe = createLocalExecutor({ rootDir: process.cwd() });
|
|
2378
2374
|
|
|
2379
|
-
// File operations
|
|
2380
2375
|
await lbe.writeFile('output/report.md', content);
|
|
2381
2376
|
await lbe.readFile('src/config.json');
|
|
2382
2377
|
await lbe.patchFile('src/index.js', patch);
|
|
2383
2378
|
await lbe.deleteFile('tmp/scratch.txt');
|
|
2384
|
-
|
|
2385
|
-
// Shell (only explicitly allowlisted commands run)
|
|
2386
2379
|
await lbe.runShell('node', ['scripts/build.js']);
|
|
2387
2380
|
\`\`\`
|
|
2388
2381
|
|
|
2389
2382
|
## What you must never do
|
|
2390
2383
|
|
|
2391
|
-
- Call
|
|
2392
|
-
-
|
|
2393
|
-
-
|
|
2394
|
-
- Attempt to bypass, patch, or disable the executor
|
|
2395
|
-
|
|
2396
|
-
## Proposing a new policy rule
|
|
2384
|
+
- Call fs.* or child_process.* directly
|
|
2385
|
+
- Modify \`.lbe/policy.json\` directly \u2014 propose a rule via \`lbe.policy.proposeRule()\`
|
|
2386
|
+
- Attempt to bypass or disable the executor
|
|
2397
2387
|
|
|
2398
|
-
|
|
2399
|
-
controller decide:
|
|
2388
|
+
## Proposing a policy rule
|
|
2400
2389
|
|
|
2401
2390
|
\`\`\`js
|
|
2402
2391
|
const proposal = lbe.policy.proposeRule({
|
|
2403
|
-
effect: 'deny',
|
|
2404
|
-
type: 'path',
|
|
2405
|
-
pattern: 'secrets/**',
|
|
2392
|
+
effect: 'deny', type: 'path', pattern: 'secrets/**',
|
|
2406
2393
|
from: 'agent: these files should never be modified'
|
|
2407
2394
|
});
|
|
2408
|
-
// Return proposal to the user
|
|
2395
|
+
// Return proposal to the user \u2014 never call lbe.policy.addRule() yourself.
|
|
2409
2396
|
\`\`\`
|
|
2410
2397
|
|
|
2411
2398
|
## Result shape
|
|
2412
2399
|
|
|
2413
|
-
|
|
2414
|
-
\`\`\`
|
|
2415
|
-
{ ok: boolean, decision: 'allow' | 'deny' | 'observe', executed: boolean }
|
|
2416
|
-
\`\`\`
|
|
2417
|
-
|
|
2418
|
-
If \`ok\` is false, the action was blocked. Check \`result.error.message\` for reason.
|
|
2419
|
-
|
|
2420
|
-
## Current policy
|
|
2421
|
-
|
|
2422
|
-
See \`lbe.policy.json\` in the project root.
|
|
2423
|
-
See \`.lbe/audit.jsonl\` for the execution audit trail.
|
|
2424
|
-
|
|
2425
|
-
## CLI
|
|
2426
|
-
|
|
2427
|
-
\`\`\`bash
|
|
2428
|
-
npx lbe-exec status # mode, rule count, audit entry count
|
|
2429
|
-
npx lbe-exec policy # list active rules
|
|
2430
|
-
npx lbe-exec observe # switch to observer mode
|
|
2431
|
-
npx lbe-exec enforce # switch to enforcement mode
|
|
2432
|
-
\`\`\`
|
|
2433
|
-
`;
|
|
2434
|
-
}
|
|
2435
|
-
function claudeMdSection() {
|
|
2436
|
-
return `<!-- lbe-governance -->
|
|
2437
|
-
## LBE Execution Governance
|
|
2438
|
-
|
|
2439
|
-
This project has LetterBlack LBE active. All file and shell operations must
|
|
2440
|
-
route through the LBE executor \u2014 do not use file system tools directly.
|
|
2441
|
-
|
|
2442
|
-
### Use these methods
|
|
2443
|
-
|
|
2444
|
-
\`\`\`js
|
|
2445
|
-
import { createLocalExecutor } from '@letterblack/lbe-exec';
|
|
2446
|
-
const lbe = createLocalExecutor({ rootDir: process.cwd() });
|
|
2447
|
-
|
|
2448
|
-
await lbe.writeFile('path/to/file', content);
|
|
2449
|
-
await lbe.readFile('path/to/file');
|
|
2450
|
-
await lbe.patchFile('path/to/file', patch);
|
|
2451
|
-
await lbe.deleteFile('path/to/file');
|
|
2452
|
-
await lbe.runShell('node', ['script.js']);
|
|
2453
|
-
\`\`\`
|
|
2454
|
-
|
|
2455
|
-
### Rules
|
|
2456
|
-
|
|
2457
|
-
- Never call \`fs.*\` or \`child_process.*\` directly
|
|
2458
|
-
- Never write to \`lbe.policy.json\` directly \u2014 use \`lbe.policy.proposeRule()\`
|
|
2459
|
-
- Every blocked action has a reason in \`result.error.message\`
|
|
2460
|
-
- Audit trail is in \`.lbe/audit.jsonl\`
|
|
2400
|
+
\`{ ok: boolean, decision: 'allow' | 'deny' | 'observe', executed: boolean }\`
|
|
2461
2401
|
|
|
2462
|
-
|
|
2463
|
-
<!-- /lbe-governance -->`;
|
|
2464
|
-
}
|
|
2465
|
-
function copilotInstructionsContent() {
|
|
2466
|
-
return `# LBE Governance \u2014 Copilot Instructions
|
|
2467
|
-
|
|
2468
|
-
This project has LetterBlack LBE active. All file and shell operations must
|
|
2469
|
-
route through the LBE executor.
|
|
2470
|
-
|
|
2471
|
-
## Required
|
|
2472
|
-
|
|
2473
|
-
Use \`@letterblack/lbe-exec\` for all file and shell actions:
|
|
2474
|
-
|
|
2475
|
-
\`\`\`js
|
|
2476
|
-
const lbe = createLocalExecutor({ rootDir: process.cwd() });
|
|
2477
|
-
await lbe.writeFile(path, content);
|
|
2478
|
-
await lbe.readFile(path);
|
|
2479
|
-
await lbe.runShell(cmd, args);
|
|
2480
|
-
\`\`\`
|
|
2481
|
-
|
|
2482
|
-
## Forbidden
|
|
2402
|
+
## Files
|
|
2483
2403
|
|
|
2484
|
-
-
|
|
2485
|
-
-
|
|
2486
|
-
-
|
|
2487
|
-
|
|
2488
|
-
See \`.lbe/AGENT_CONTRACT.md\` for the full contract.
|
|
2404
|
+
- Policy: \`.lbe/policy.json\`
|
|
2405
|
+
- Audit: \`.lbe/audit.jsonl\`
|
|
2406
|
+
- Status: \`npx lbe-exec status\`
|
|
2489
2407
|
`;
|
|
2490
2408
|
}
|
|
2491
|
-
function
|
|
2409
|
+
function writeAgentContract(cwd) {
|
|
2492
2410
|
const lbeDir = path4.join(cwd, ".lbe");
|
|
2493
2411
|
fs4.mkdirSync(lbeDir, { recursive: true });
|
|
2494
2412
|
fs4.writeFileSync(path4.join(lbeDir, "AGENT_CONTRACT.md"), agentContractContent());
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2413
|
+
}
|
|
2414
|
+
function migrateLegacyRootFiles(cwd) {
|
|
2415
|
+
const lbeDir = path4.join(cwd, ".lbe");
|
|
2416
|
+
fs4.mkdirSync(lbeDir, { recursive: true });
|
|
2417
|
+
const migrations = [
|
|
2418
|
+
["lbe.policy.json", ".lbe/policy.json"],
|
|
2419
|
+
["lbe.workspace.json", ".lbe/workspace.json"]
|
|
2420
|
+
];
|
|
2421
|
+
const removed = [];
|
|
2422
|
+
for (const [src, dest] of migrations) {
|
|
2423
|
+
const srcPath = path4.join(cwd, src);
|
|
2424
|
+
const destPath = path4.join(cwd, dest);
|
|
2425
|
+
if (fs4.existsSync(srcPath) && !fs4.existsSync(destPath)) {
|
|
2426
|
+
fs4.renameSync(srcPath, destPath);
|
|
2427
|
+
removed.push(src + " \u2192 " + dest);
|
|
2428
|
+
} else if (fs4.existsSync(srcPath)) {
|
|
2429
|
+
fs4.unlinkSync(srcPath);
|
|
2430
|
+
removed.push(src + " (removed \u2014 .lbe/ version exists)");
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
const toDelete = ["CLAUDE.md", path4.join(".github", "copilot-instructions.md")];
|
|
2434
|
+
for (const rel of toDelete) {
|
|
2435
|
+
const p = path4.join(cwd, rel);
|
|
2436
|
+
if (fs4.existsSync(p)) {
|
|
2437
|
+
const content = fs4.readFileSync(p, "utf8");
|
|
2438
|
+
if (content.includes("lbe-governance") || content.includes("LetterBlack LBE")) {
|
|
2439
|
+
fs4.unlinkSync(p);
|
|
2440
|
+
removed.push(rel + " (removed \u2014 LBE-generated file)");
|
|
2441
|
+
}
|
|
2501
2442
|
}
|
|
2502
|
-
} else {
|
|
2503
|
-
fs4.writeFileSync(claudePath, section + "\n");
|
|
2504
|
-
}
|
|
2505
|
-
const githubDir = path4.join(cwd, ".github");
|
|
2506
|
-
fs4.mkdirSync(githubDir, { recursive: true });
|
|
2507
|
-
const copilotPath = path4.join(githubDir, "copilot-instructions.md");
|
|
2508
|
-
if (!fs4.existsSync(copilotPath)) {
|
|
2509
|
-
fs4.writeFileSync(copilotPath, copilotInstructionsContent());
|
|
2510
2443
|
}
|
|
2444
|
+
return removed;
|
|
2511
2445
|
}
|
|
2512
2446
|
async function initCommand(opts2 = {}) {
|
|
2513
2447
|
const cwd = process.cwd();
|
|
2514
2448
|
const yes = opts2.yes || opts2.y || !process.stdin.isTTY;
|
|
2515
|
-
const
|
|
2449
|
+
const lbeDir = path4.join(cwd, ".lbe");
|
|
2450
|
+
fs4.mkdirSync(lbeDir, { recursive: true });
|
|
2451
|
+
const outPath = path4.join(lbeDir, "workspace.json");
|
|
2516
2452
|
console.log("\nScanning workspace...\n");
|
|
2517
2453
|
const { projectTypes, primaryType: primaryType2, semantics, enforcement } = scanWorkspace(cwd);
|
|
2518
2454
|
console.log(formatSummary(projectTypes, semantics, enforcement));
|
|
@@ -2537,21 +2473,24 @@ async function initCommand(opts2 = {}) {
|
|
|
2537
2473
|
enforcement: finalEnforcement
|
|
2538
2474
|
};
|
|
2539
2475
|
fs4.writeFileSync(outPath, JSON.stringify(contract, null, 2));
|
|
2540
|
-
console.log("\u2713 Wrote lbe
|
|
2476
|
+
console.log("\u2713 Wrote .lbe/workspace.json");
|
|
2541
2477
|
setupCrypto(cwd);
|
|
2542
|
-
const localPolicyPath = path4.join(
|
|
2478
|
+
const localPolicyPath = path4.join(lbeDir, "policy.json");
|
|
2543
2479
|
if (!fs4.existsSync(localPolicyPath)) {
|
|
2544
2480
|
fs4.writeFileSync(localPolicyPath, JSON.stringify({ version: 1, mode: "observe", workspace: cwd, rules: [] }, null, 2) + "\n");
|
|
2545
2481
|
}
|
|
2546
|
-
const localAuditPath = path4.join(
|
|
2482
|
+
const localAuditPath = path4.join(lbeDir, "audit.jsonl");
|
|
2547
2483
|
if (!fs4.existsSync(localAuditPath)) fs4.writeFileSync(localAuditPath, "");
|
|
2548
|
-
console.log("\u2713 Keys and policy ready");
|
|
2549
|
-
|
|
2484
|
+
console.log("\u2713 Keys and policy ready (.lbe/)");
|
|
2485
|
+
writeAgentContract(cwd);
|
|
2550
2486
|
console.log("\u2713 Agent contract written \u2192 .lbe/AGENT_CONTRACT.md");
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2487
|
+
const migrated = migrateLegacyRootFiles(cwd);
|
|
2488
|
+
if (migrated.length) {
|
|
2489
|
+
console.log("\n\u2713 Migrated legacy files:");
|
|
2490
|
+
for (const m of migrated) console.log(" " + m);
|
|
2491
|
+
}
|
|
2492
|
+
console.log("\nDone. All LBE state is in .lbe/");
|
|
2493
|
+
console.log("Run npx lbe-exec status to verify.\n");
|
|
2555
2494
|
return { success: true, contract };
|
|
2556
2495
|
}
|
|
2557
2496
|
|
|
@@ -2571,14 +2510,10 @@ var opts = Object.fromEntries(
|
|
|
2571
2510
|
var positional = rest.filter((v) => !v.startsWith("--") && rest[rest.indexOf(v) - 1]?.startsWith("--") === false);
|
|
2572
2511
|
var __dir = path15.dirname(fileURLToPath2(import.meta.url));
|
|
2573
2512
|
function loadPolicy() {
|
|
2574
|
-
const
|
|
2513
|
+
const cwd = process.cwd();
|
|
2514
|
+
const p = fs14.existsSync(path15.join(cwd, ".lbe", "policy.json")) ? path15.join(cwd, ".lbe", "policy.json") : path15.join(cwd, "lbe.policy.json");
|
|
2575
2515
|
return fs14.existsSync(p) ? JSON.parse(fs14.readFileSync(p, "utf8")) : null;
|
|
2576
2516
|
}
|
|
2577
|
-
function countAudit() {
|
|
2578
|
-
const p = path15.join(process.cwd(), ".lbe", "audit.jsonl");
|
|
2579
|
-
if (!fs14.existsSync(p)) return 0;
|
|
2580
|
-
return fs14.readFileSync(p, "utf8").split("\n").filter((l) => l.trim()).length;
|
|
2581
|
-
}
|
|
2582
2517
|
function findHookPath() {
|
|
2583
2518
|
const candidates = [
|
|
2584
2519
|
path15.resolve(__dir, "../hooks/register.cjs"),
|
|
@@ -2684,8 +2619,9 @@ switch (cmd) {
|
|
|
2684
2619
|
process.exit(1);
|
|
2685
2620
|
}
|
|
2686
2621
|
const existing = process.env.NODE_OPTIONS || "";
|
|
2687
|
-
const
|
|
2688
|
-
const
|
|
2622
|
+
const hookPathFwd = hookPath.replace(/\\/g, "/");
|
|
2623
|
+
const hookFlag = '--require "' + hookPathFwd + '"';
|
|
2624
|
+
const nodeOptions = existing.includes(hookPathFwd) ? existing : (existing + " " + hookFlag).trim();
|
|
2689
2625
|
const npmArgs = rest.filter((v) => !v.startsWith("--mode") && v !== opts.mode);
|
|
2690
2626
|
const child = spawn("npm", npmArgs, {
|
|
2691
2627
|
stdio: "inherit",
|
|
@@ -2696,59 +2632,63 @@ switch (cmd) {
|
|
|
2696
2632
|
break;
|
|
2697
2633
|
}
|
|
2698
2634
|
case "status": {
|
|
2699
|
-
const
|
|
2635
|
+
const root = process.cwd();
|
|
2700
2636
|
console.log("\u2500\u2500 LBE Status \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
2701
|
-
console.log("workspace: " +
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
const
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
} catch (_) {
|
|
2726
|
-
}
|
|
2727
|
-
console.log("\nhook: " + (pidAlive ? "ACTIVE" : "stale (process exited)"));
|
|
2728
|
-
console.log("hook pid: " + hookStatus.pid + (pidAlive ? " (alive)" : " (gone)"));
|
|
2729
|
-
console.log("hook mode: " + hookStatus.mode);
|
|
2730
|
-
console.log("hook root: " + hookStatus.root);
|
|
2731
|
-
console.log("hook start: " + hookStatus.started_at);
|
|
2732
|
-
if (hookStatus.patched) {
|
|
2733
|
-
console.log("\nPatched functions:");
|
|
2734
|
-
for (const [fn, active] of Object.entries(hookStatus.patched)) {
|
|
2735
|
-
console.log(" " + (active ? "\u2713" : "\u2013") + " " + fn);
|
|
2637
|
+
console.log("workspace: " + root);
|
|
2638
|
+
const hookPath = findHookPath();
|
|
2639
|
+
console.log("hook file: " + hookPath + (fs14.existsSync(hookPath) ? " (found)" : " (MISSING)"));
|
|
2640
|
+
const lbeRoot = process.env.LBE_ROOT || "";
|
|
2641
|
+
console.log("LBE_ROOT: " + (lbeRoot || "(not set)"));
|
|
2642
|
+
const nodeOpts = process.env.NODE_OPTIONS || "";
|
|
2643
|
+
const hookInPath = nodeOpts.includes("register.cjs");
|
|
2644
|
+
console.log("NODE_OPTIONS contains hook: " + (hookInPath ? "yes" : "no"));
|
|
2645
|
+
const eventsFile = path15.join(root, ".lbe", "events.jsonl");
|
|
2646
|
+
const auditExists = fs14.existsSync(eventsFile);
|
|
2647
|
+
console.log("audit log: " + (auditExists ? eventsFile : "(none yet)"));
|
|
2648
|
+
if (auditExists) {
|
|
2649
|
+
try {
|
|
2650
|
+
const lines = fs14.readFileSync(eventsFile, "utf8").split("\n").filter((l) => l.trim());
|
|
2651
|
+
if (lines.length) {
|
|
2652
|
+
const last = JSON.parse(lines[lines.length - 1]);
|
|
2653
|
+
const ts = new Date((last.ts || 0) * 1e3).toISOString().replace("T", " ").slice(0, 19);
|
|
2654
|
+
const target = last.path || last.cmd || "?";
|
|
2655
|
+
console.log("last event: " + ts + " " + last.action + " " + target + " \u2192 " + (last.decision || "?"));
|
|
2656
|
+
} else {
|
|
2657
|
+
console.log("last event: (none)");
|
|
2658
|
+
}
|
|
2659
|
+
} catch (_) {
|
|
2660
|
+
console.log("last event: (unreadable)");
|
|
2736
2661
|
}
|
|
2737
2662
|
}
|
|
2738
|
-
const
|
|
2739
|
-
|
|
2740
|
-
|
|
2663
|
+
const statusFile = path15.join(root, ".lbe", "runtime", "hook-status.json");
|
|
2664
|
+
if (fs14.existsSync(statusFile)) {
|
|
2665
|
+
let h;
|
|
2741
2666
|
try {
|
|
2742
|
-
|
|
2743
|
-
console.log("\nactivation: " + (act.permanent ? "permanent" : "session") + " (" + act.mode + ")" + (sessionActive ? " NODE_OPTIONS \u2713" : " restart terminal to apply"));
|
|
2667
|
+
h = JSON.parse(fs14.readFileSync(statusFile, "utf8"));
|
|
2744
2668
|
} catch (_) {
|
|
2745
2669
|
}
|
|
2746
|
-
|
|
2747
|
-
|
|
2670
|
+
if (h) {
|
|
2671
|
+
let pidAlive = false;
|
|
2672
|
+
try {
|
|
2673
|
+
process.kill(h.pid, 0);
|
|
2674
|
+
pidAlive = true;
|
|
2675
|
+
} catch (_) {
|
|
2676
|
+
}
|
|
2677
|
+
console.log("\nhook process: " + (pidAlive ? "ACTIVE" : "stale (process exited)"));
|
|
2678
|
+
console.log("hook pid: " + h.pid + (pidAlive ? " (alive)" : " (gone)"));
|
|
2679
|
+
console.log("hook mode: " + h.mode);
|
|
2680
|
+
console.log("hook started: " + h.started_at);
|
|
2681
|
+
if (h.patched) {
|
|
2682
|
+
console.log("\nPatched functions:");
|
|
2683
|
+
for (const [fn, active] of Object.entries(h.patched)) {
|
|
2684
|
+
console.log(" " + (active ? "\u2713" : "\u2013") + " " + fn);
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2748
2688
|
} else {
|
|
2749
|
-
console.log("\
|
|
2689
|
+
console.log("\nhook process: inactive \u2014 run: lbe-exec run-node ./agent.js");
|
|
2690
|
+
console.log(" or: lbe-exec activate then lbe-exec shell");
|
|
2750
2691
|
}
|
|
2751
|
-
console.log("\nevents log: .lbe/events.jsonl");
|
|
2752
2692
|
break;
|
|
2753
2693
|
}
|
|
2754
2694
|
case "audit": {
|
|
@@ -2793,12 +2733,12 @@ switch (cmd) {
|
|
|
2793
2733
|
case "activate": {
|
|
2794
2734
|
const hookPath = findHookPath();
|
|
2795
2735
|
if (!fs14.existsSync(hookPath)) {
|
|
2796
|
-
console.error("Hook not found: " + hookPath
|
|
2736
|
+
console.error("Hook not found: " + hookPath);
|
|
2737
|
+
console.error("Run: npm install @letterblack/lbe-exec");
|
|
2797
2738
|
process.exit(1);
|
|
2798
2739
|
}
|
|
2799
2740
|
const mode = opts.mode || "observe";
|
|
2800
2741
|
const root = process.cwd();
|
|
2801
|
-
const reqFlag = '--require "' + hookPath + '"';
|
|
2802
2742
|
const lbeDir = path15.join(root, ".lbe");
|
|
2803
2743
|
fs14.mkdirSync(lbeDir, { recursive: true });
|
|
2804
2744
|
fs14.writeFileSync(path15.join(lbeDir, "activation.json"), JSON.stringify({
|
|
@@ -2806,111 +2746,83 @@ switch (cmd) {
|
|
|
2806
2746
|
activatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2807
2747
|
hookPath,
|
|
2808
2748
|
mode,
|
|
2809
|
-
root
|
|
2810
|
-
permanent: !!opts.permanent
|
|
2749
|
+
root
|
|
2811
2750
|
}, null, 2) + "\n");
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
console.log(" Restart your terminal or IDE to apply.\n");
|
|
2830
|
-
console.log(" Every node / npm / npx process will be governed automatically.");
|
|
2831
|
-
} else {
|
|
2832
|
-
const home = process.env.HOME || "";
|
|
2833
|
-
const rcFiles = [".bashrc", ".zshrc"].map((f) => path15.join(home, f)).filter((f) => fs14.existsSync(f));
|
|
2834
|
-
const block = `
|
|
2835
|
-
# LBE Agent Governance
|
|
2836
|
-
export NODE_OPTIONS='${reqFlag}'
|
|
2837
|
-
export LBE_ROOT="${root}"
|
|
2838
|
-
export LBE_MODE="${mode}"
|
|
2839
|
-
`;
|
|
2840
|
-
for (const rc of rcFiles) {
|
|
2841
|
-
fs14.appendFileSync(rc, block);
|
|
2842
|
-
console.log("\u2713 Appended to " + rc);
|
|
2843
|
-
}
|
|
2844
|
-
if (!rcFiles.length) console.log("No .bashrc/.zshrc found \u2014 add these manually:\n" + block);
|
|
2845
|
-
console.log(" Run: source ~/.bashrc (or restart terminal)");
|
|
2751
|
+
console.log("\u2500\u2500 LBE workspace activated \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
2752
|
+
console.log("workspace: " + root);
|
|
2753
|
+
console.log("hook: " + hookPath);
|
|
2754
|
+
console.log("mode: " + mode);
|
|
2755
|
+
console.log("\nNext: open a governed shell session:");
|
|
2756
|
+
console.log(" lbe-exec shell");
|
|
2757
|
+
console.log("\nAny Node.js agent run inside that shell is intercepted.");
|
|
2758
|
+
console.log("Python, Go, native binaries, and PowerShell are NOT governed.");
|
|
2759
|
+
break;
|
|
2760
|
+
}
|
|
2761
|
+
case "shell": {
|
|
2762
|
+
const activationFile = path15.join(process.cwd(), ".lbe", "activation.json");
|
|
2763
|
+
let activation = null;
|
|
2764
|
+
if (fs14.existsSync(activationFile)) {
|
|
2765
|
+
try {
|
|
2766
|
+
activation = JSON.parse(fs14.readFileSync(activationFile, "utf8"));
|
|
2767
|
+
} catch (_) {
|
|
2846
2768
|
}
|
|
2769
|
+
}
|
|
2770
|
+
const hookPath = activation && activation.hookPath || findHookPath();
|
|
2771
|
+
if (!fs14.existsSync(hookPath)) {
|
|
2772
|
+
console.error("Hook not found. Run: lbe-exec activate");
|
|
2773
|
+
process.exit(1);
|
|
2774
|
+
}
|
|
2775
|
+
const mode = opts.mode || activation && activation.mode || "observe";
|
|
2776
|
+
const root = activation && activation.root || process.cwd();
|
|
2777
|
+
const hookPathFwd = hookPath.replace(/\\/g, "/");
|
|
2778
|
+
const nodeOpts = '--require "' + hookPathFwd + '"';
|
|
2779
|
+
const shellEnv = { ...process.env, NODE_OPTIONS: nodeOpts, LBE_ROOT: root, LBE_MODE: mode };
|
|
2780
|
+
console.log("[lbe] Opening governed shell \u2014 mode: " + mode);
|
|
2781
|
+
console.log("[lbe] NODE_OPTIONS set. Node.js agents are intercepted.");
|
|
2782
|
+
console.log("[lbe] Python / Go / native binaries are NOT governed.");
|
|
2783
|
+
console.log('[lbe] Type "exit" to close.\n');
|
|
2784
|
+
let shellProc;
|
|
2785
|
+
if (process.platform === "win32") {
|
|
2786
|
+
const banner = [
|
|
2787
|
+
`$env:NODE_OPTIONS='--require "${hookPathFwd}"'`,
|
|
2788
|
+
`$env:LBE_ROOT='${root}'`,
|
|
2789
|
+
`$env:LBE_MODE='${mode}'`,
|
|
2790
|
+
`Write-Host '[lbe] Shell armed \u2014 mode: ${mode}' -ForegroundColor Green`
|
|
2791
|
+
].join("; ");
|
|
2792
|
+
shellProc = spawn(
|
|
2793
|
+
"powershell.exe",
|
|
2794
|
+
["-NoExit", "-Command", banner],
|
|
2795
|
+
{ stdio: "inherit", env: shellEnv }
|
|
2796
|
+
);
|
|
2847
2797
|
} else {
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
console.log("hook: " + hookPath);
|
|
2851
|
-
console.log("mode: " + mode);
|
|
2852
|
-
console.log("\nCopy and run in your terminal to arm this session:\n");
|
|
2853
|
-
if (process.platform === "win32") {
|
|
2854
|
-
console.log("# PowerShell:");
|
|
2855
|
-
console.log(`$env:NODE_OPTIONS='--require "` + hookPath + `"'`);
|
|
2856
|
-
console.log('$env:LBE_ROOT="' + root + '"');
|
|
2857
|
-
console.log('$env:LBE_MODE="' + mode + '"');
|
|
2858
|
-
console.log("\n# cmd.exe:");
|
|
2859
|
-
console.log('set NODE_OPTIONS=--require "' + hookPath + '"');
|
|
2860
|
-
console.log("set LBE_ROOT=" + root);
|
|
2861
|
-
console.log("set LBE_MODE=" + mode);
|
|
2862
|
-
} else {
|
|
2863
|
-
console.log(`export NODE_OPTIONS='--require "` + hookPath + `"'`);
|
|
2864
|
-
console.log('export LBE_ROOT="' + root + '"');
|
|
2865
|
-
console.log('export LBE_MODE="' + mode + '"');
|
|
2866
|
-
}
|
|
2867
|
-
console.log("\nAfter running: every node / npm / npx command in this session is governed.");
|
|
2868
|
-
console.log("To persist across all terminals: lbe-exec activate --permanent");
|
|
2798
|
+
const sh = process.env.SHELL || "/bin/bash";
|
|
2799
|
+
shellProc = spawn(sh, [], { stdio: "inherit", env: shellEnv });
|
|
2869
2800
|
}
|
|
2801
|
+
shellProc.on("close", (code) => {
|
|
2802
|
+
console.log("\n[lbe] Governed shell closed.");
|
|
2803
|
+
process.exit(code ?? 0);
|
|
2804
|
+
});
|
|
2870
2805
|
break;
|
|
2871
2806
|
}
|
|
2872
2807
|
case "deactivate": {
|
|
2873
|
-
const
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
], { encoding: "utf8" });
|
|
2884
|
-
const current = (psRead.stdout || "").trim();
|
|
2885
|
-
const cleaned = current.replace('--require "' + hookPath + '"', "").replace("--require " + hookPath, "").trim();
|
|
2886
|
-
spawnSync2("powershell.exe", [
|
|
2887
|
-
"-NoProfile",
|
|
2888
|
-
"-NonInteractive",
|
|
2889
|
-
"-Command",
|
|
2890
|
-
(cleaned ? `[System.Environment]::SetEnvironmentVariable('NODE_OPTIONS','${cleaned}','User');` : `[System.Environment]::SetEnvironmentVariable('NODE_OPTIONS',$null,'User');`) + `[System.Environment]::SetEnvironmentVariable('LBE_ROOT',$null,'User');[System.Environment]::SetEnvironmentVariable('LBE_MODE',$null,'User')`
|
|
2891
|
-
], { stdio: "inherit" });
|
|
2892
|
-
console.log("\u2713 LBE deactivated \u2014 user environment cleared. Restart terminal to apply.");
|
|
2893
|
-
} else {
|
|
2894
|
-
console.log("Remove these lines from your shell RC file (.bashrc / .zshrc):");
|
|
2895
|
-
console.log(" export NODE_OPTIONS=... (the --require lbe line)");
|
|
2896
|
-
console.log(" export LBE_ROOT=...");
|
|
2897
|
-
console.log(" export LBE_MODE=...");
|
|
2808
|
+
const root = process.cwd();
|
|
2809
|
+
const files = [
|
|
2810
|
+
path15.join(root, ".lbe", "activation.json"),
|
|
2811
|
+
path15.join(root, ".lbe", "runtime", "hook-status.json")
|
|
2812
|
+
];
|
|
2813
|
+
let removed = 0;
|
|
2814
|
+
for (const f of files) {
|
|
2815
|
+
if (fs14.existsSync(f)) {
|
|
2816
|
+
fs14.unlinkSync(f);
|
|
2817
|
+
removed++;
|
|
2898
2818
|
}
|
|
2819
|
+
}
|
|
2820
|
+
if (removed) {
|
|
2821
|
+
console.log("\u2713 LBE deactivated \u2014 workspace activation files removed.");
|
|
2899
2822
|
} else {
|
|
2900
|
-
console.log("
|
|
2901
|
-
if (process.platform === "win32") {
|
|
2902
|
-
console.log("# PowerShell:");
|
|
2903
|
-
console.log("Remove-Item Env:NODE_OPTIONS -ErrorAction SilentlyContinue");
|
|
2904
|
-
console.log("Remove-Item Env:LBE_ROOT -ErrorAction SilentlyContinue");
|
|
2905
|
-
console.log("Remove-Item Env:LBE_MODE -ErrorAction SilentlyContinue");
|
|
2906
|
-
console.log("\n# cmd.exe:");
|
|
2907
|
-
console.log("set NODE_OPTIONS=");
|
|
2908
|
-
console.log("set LBE_ROOT=");
|
|
2909
|
-
console.log("set LBE_MODE=");
|
|
2910
|
-
} else {
|
|
2911
|
-
console.log("unset NODE_OPTIONS LBE_ROOT LBE_MODE");
|
|
2912
|
-
}
|
|
2823
|
+
console.log("Nothing to deactivate (workspace was not activated).");
|
|
2913
2824
|
}
|
|
2825
|
+
console.log('Close any open "lbe-exec shell" sessions to fully disarm.');
|
|
2914
2826
|
break;
|
|
2915
2827
|
}
|
|
2916
2828
|
case "observe":
|
|
@@ -2923,7 +2835,7 @@ export LBE_MODE="${mode}"
|
|
|
2923
2835
|
case "policy": {
|
|
2924
2836
|
const policy = loadPolicy();
|
|
2925
2837
|
if (!policy) {
|
|
2926
|
-
console.log("No
|
|
2838
|
+
console.log("No policy found. Run: npx lbe-exec init");
|
|
2927
2839
|
break;
|
|
2928
2840
|
}
|
|
2929
2841
|
if (!policy.rules?.length) {
|
|
@@ -2964,10 +2876,11 @@ export LBE_MODE="${mode}"
|
|
|
2964
2876
|
console.log(" status Show workspace, mode, hook state, patched functions");
|
|
2965
2877
|
console.log(" audit Show unified event log (.lbe/events.jsonl)");
|
|
2966
2878
|
console.log(" policy List active policy rules");
|
|
2967
|
-
console.log(" activate
|
|
2968
|
-
console.log(" [--mode observe|enforce]
|
|
2969
|
-
console.log("
|
|
2970
|
-
console.log(" [--
|
|
2879
|
+
console.log(" activate Write workspace activation record (Node.js only)");
|
|
2880
|
+
console.log(" [--mode observe|enforce]");
|
|
2881
|
+
console.log(" shell Open a governed terminal (NODE_OPTIONS pre-set)");
|
|
2882
|
+
console.log(" [--mode observe|enforce]");
|
|
2883
|
+
console.log(" deactivate Remove workspace activation files");
|
|
2971
2884
|
console.log(" observe Switch to observer mode (log only, nothing blocked)");
|
|
2972
2885
|
console.log(" enforce Switch to enforcement mode (violations blocked)");
|
|
2973
2886
|
console.log(" execute Send a JSON request from stdin or --input file");
|
package/dist/index.js
CHANGED
|
@@ -1490,7 +1490,7 @@ function verifyAuditLogIntegrity(logPath, options = {}) {
|
|
|
1490
1490
|
import fs8 from "fs";
|
|
1491
1491
|
import path9 from "path";
|
|
1492
1492
|
import crypto4 from "crypto";
|
|
1493
|
-
var POLICY_FILE = "lbe
|
|
1493
|
+
var POLICY_FILE = ".lbe/policy.json";
|
|
1494
1494
|
var AUDIT_FILE = ".lbe/audit.jsonl";
|
|
1495
1495
|
function glob(pattern) {
|
|
1496
1496
|
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
package/hooks/register.cjs
CHANGED
|
@@ -14,7 +14,10 @@ const MODE = process.env.LBE_MODE || 'observe';
|
|
|
14
14
|
// ── Policy loader (inline CJS — ESM cannot be require()'d synchronously) ────
|
|
15
15
|
|
|
16
16
|
function loadPolicy() {
|
|
17
|
-
|
|
17
|
+
// .lbe/policy.json is canonical. Fall back to legacy lbe.policy.json in root.
|
|
18
|
+
const policyPath = fs.existsSync(path.join(ROOT_DIR, '.lbe', 'policy.json'))
|
|
19
|
+
? path.join(ROOT_DIR, '.lbe', 'policy.json')
|
|
20
|
+
: path.join(ROOT_DIR, 'lbe.policy.json');
|
|
18
21
|
try {
|
|
19
22
|
if (fs.existsSync(policyPath)) {
|
|
20
23
|
return JSON.parse(fs.readFileSync(policyPath, 'utf8'));
|