@letterblack/lbe-exec 1.2.17 → 1.2.19
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/TRUST.md +90 -0
- package/dist/cli.js +41 -2845
- package/dist/index.js +14 -1835
- package/hooks/register.cjs +5 -476
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -1,2363 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
import { canonicalize } from "json-canonicalize";
|
|
15
|
-
function
|
|
16
|
-
return
|
|
17
|
-
}
|
|
18
|
-
function toBase64(bytes) {
|
|
19
|
-
return Buffer.from(bytes).toString("base64");
|
|
20
|
-
}
|
|
21
|
-
function verifyEd25519({ payloadObj, sigB64, pubKeyB64 }) {
|
|
22
|
-
try {
|
|
23
|
-
const msg = Buffer.from(canonicalize(payloadObj), "utf8");
|
|
24
|
-
const sig = bytesFromBase64(sigB64);
|
|
25
|
-
const pub = bytesFromBase64(pubKeyB64);
|
|
26
|
-
const isValid = nacl.sign.detached.verify(
|
|
27
|
-
new Uint8Array(msg),
|
|
28
|
-
new Uint8Array(sig),
|
|
29
|
-
new Uint8Array(pub)
|
|
30
|
-
);
|
|
31
|
-
return {
|
|
32
|
-
valid: isValid,
|
|
33
|
-
message: isValid ? "Signature verified" : "Signature verification failed"
|
|
34
|
-
};
|
|
35
|
-
} catch (err) {
|
|
36
|
-
return {
|
|
37
|
-
valid: false,
|
|
38
|
-
message: `Signature verification error: ${err.message}`
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
function generateKeyPair() {
|
|
43
|
-
const keyPair = nacl.sign.keyPair();
|
|
44
|
-
return {
|
|
45
|
-
publicKey: toBase64(keyPair.publicKey),
|
|
46
|
-
secretKey: toBase64(keyPair.secretKey)
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
function signEd25519({ payloadObj, secretKeyB64 }) {
|
|
50
|
-
try {
|
|
51
|
-
const msg = Buffer.from(canonicalize(payloadObj), "utf8");
|
|
52
|
-
const secretKey = bytesFromBase64(secretKeyB64);
|
|
53
|
-
const sig = nacl.sign.detached(new Uint8Array(msg), new Uint8Array(secretKey));
|
|
54
|
-
return {
|
|
55
|
-
signature: toBase64(sig),
|
|
56
|
-
error: null
|
|
57
|
-
};
|
|
58
|
-
} catch (err) {
|
|
59
|
-
return {
|
|
60
|
-
signature: null,
|
|
61
|
-
error: `Signing failed: ${err.message}`
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
var init_signature = __esm({
|
|
66
|
-
"src/core/signature.js"() {
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
// src/core/atomicWrite.js
|
|
71
|
-
import fs5 from "fs";
|
|
72
|
-
import path5 from "path";
|
|
73
|
-
import crypto from "crypto";
|
|
74
|
-
function _lockPathFor(targetPath) {
|
|
75
|
-
return targetPath + ".lock";
|
|
76
|
-
}
|
|
77
|
-
function _tryAcquire(lockPath) {
|
|
78
|
-
try {
|
|
79
|
-
const fd = fs5.openSync(lockPath, "wx");
|
|
80
|
-
fs5.writeSync(fd, `pid:${process.pid}:${Date.now()}`);
|
|
81
|
-
fs5.closeSync(fd);
|
|
82
|
-
return true;
|
|
83
|
-
} catch (err) {
|
|
84
|
-
if (err.code === "EEXIST" || err.code === "EPERM" || err.code === "EBUSY" || err.code === "EACCES") {
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
throw err;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
function _removeIfStale(lockPath, staleMs) {
|
|
91
|
-
try {
|
|
92
|
-
const stat = fs5.statSync(lockPath);
|
|
93
|
-
const ageMs = Date.now() - stat.mtimeMs;
|
|
94
|
-
if (ageMs > staleMs) {
|
|
95
|
-
try {
|
|
96
|
-
fs5.unlinkSync(lockPath);
|
|
97
|
-
} catch {
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
} catch {
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
function _sleepSync(ms) {
|
|
104
|
-
const end = Date.now() + ms;
|
|
105
|
-
while (Date.now() < end) {
|
|
106
|
-
try {
|
|
107
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, Math.max(1, end - Date.now()));
|
|
108
|
-
} catch {
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
function withFileLock(targetPath, optsOrFn, maybeFn) {
|
|
113
|
-
const fn = typeof optsOrFn === "function" ? optsOrFn : maybeFn;
|
|
114
|
-
const opts2 = typeof optsOrFn === "function" ? {} : optsOrFn || {};
|
|
115
|
-
const { timeoutMs, pollMs, staleMs } = { ...DEFAULT_LOCK_OPTS, ...opts2 };
|
|
116
|
-
const dir = path5.dirname(targetPath);
|
|
117
|
-
if (!fs5.existsSync(dir)) {
|
|
118
|
-
fs5.mkdirSync(dir, { recursive: true });
|
|
119
|
-
}
|
|
120
|
-
const lockPath = _lockPathFor(targetPath);
|
|
121
|
-
const deadline = Date.now() + timeoutMs;
|
|
122
|
-
let acquired = false;
|
|
123
|
-
while (!acquired) {
|
|
124
|
-
acquired = _tryAcquire(lockPath);
|
|
125
|
-
if (acquired) break;
|
|
126
|
-
if (Date.now() >= deadline) {
|
|
127
|
-
_removeIfStale(lockPath, staleMs);
|
|
128
|
-
acquired = _tryAcquire(lockPath);
|
|
129
|
-
if (acquired) break;
|
|
130
|
-
const err = new Error(`withFileLock: timeout acquiring ${lockPath} after ${timeoutMs}ms`);
|
|
131
|
-
err.code = "ELOCKTIMEOUT";
|
|
132
|
-
throw err;
|
|
133
|
-
}
|
|
134
|
-
_removeIfStale(lockPath, staleMs);
|
|
135
|
-
const jitter = Math.floor(Math.random() * pollMs);
|
|
136
|
-
_sleepSync(pollMs + jitter);
|
|
137
|
-
}
|
|
138
|
-
try {
|
|
139
|
-
return fn();
|
|
140
|
-
} finally {
|
|
141
|
-
try {
|
|
142
|
-
fs5.unlinkSync(lockPath);
|
|
143
|
-
} catch {
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
function atomicWriteFileSync(filePath, data, options = {}) {
|
|
148
|
-
const dir = path5.dirname(filePath);
|
|
149
|
-
if (!fs5.existsSync(dir)) {
|
|
150
|
-
fs5.mkdirSync(dir, { recursive: true });
|
|
151
|
-
}
|
|
152
|
-
const tempFile = path5.join(dir, `.tmp-${Date.now()}-${crypto.randomBytes(4).toString("hex")}`);
|
|
153
|
-
try {
|
|
154
|
-
fs5.writeFileSync(tempFile, data, options);
|
|
155
|
-
fs5.renameSync(tempFile, filePath);
|
|
156
|
-
} catch (error2) {
|
|
157
|
-
try {
|
|
158
|
-
if (fs5.existsSync(tempFile)) {
|
|
159
|
-
fs5.unlinkSync(tempFile);
|
|
160
|
-
}
|
|
161
|
-
} catch (cleanupError) {
|
|
162
|
-
}
|
|
163
|
-
throw error2;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
var DEFAULT_LOCK_OPTS;
|
|
167
|
-
var init_atomicWrite = __esm({
|
|
168
|
-
"src/core/atomicWrite.js"() {
|
|
169
|
-
DEFAULT_LOCK_OPTS = {
|
|
170
|
-
timeoutMs: 5e3,
|
|
171
|
-
// total wait before giving up
|
|
172
|
-
pollMs: 15,
|
|
173
|
-
// base poll interval (jittered)
|
|
174
|
-
staleMs: 3e4
|
|
175
|
-
// lock files older than this are presumed orphaned
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
// src/core/auditLog.js
|
|
181
|
-
import fs6 from "fs";
|
|
182
|
-
import path6 from "path";
|
|
183
|
-
import crypto2 from "crypto";
|
|
184
|
-
function sha256(str) {
|
|
185
|
-
return crypto2.createHash("sha256").update(str).digest("hex");
|
|
186
|
-
}
|
|
187
|
-
function getLastHash(logPath) {
|
|
188
|
-
try {
|
|
189
|
-
if (!fs6.existsSync(logPath)) return "GENESIS";
|
|
190
|
-
const content = fs6.readFileSync(logPath, "utf8").trim();
|
|
191
|
-
if (!content) return "GENESIS";
|
|
192
|
-
const lines = content.split("\n");
|
|
193
|
-
const lastLine = lines[lines.length - 1];
|
|
194
|
-
try {
|
|
195
|
-
const lastEntry = JSON.parse(lastLine);
|
|
196
|
-
return lastEntry.hash || "GENESIS";
|
|
197
|
-
} catch (err) {
|
|
198
|
-
return "GENESIS";
|
|
199
|
-
}
|
|
200
|
-
} catch (err) {
|
|
201
|
-
return "GENESIS";
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
function appendAudit(logPath, entry) {
|
|
205
|
-
const dir = path6.dirname(logPath);
|
|
206
|
-
if (!fs6.existsSync(dir)) {
|
|
207
|
-
fs6.mkdirSync(dir, { recursive: true });
|
|
208
|
-
}
|
|
209
|
-
let result;
|
|
210
|
-
withFileLock(logPath, () => {
|
|
211
|
-
const prevHash = getLastHash(logPath);
|
|
212
|
-
const record = {
|
|
213
|
-
...entry,
|
|
214
|
-
prevHash,
|
|
215
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
216
|
-
};
|
|
217
|
-
delete record.hash;
|
|
218
|
-
const recordStr = JSON.stringify(record);
|
|
219
|
-
const hash = sha256(recordStr);
|
|
220
|
-
const final = JSON.stringify({ ...record, hash });
|
|
221
|
-
let existingContent = "";
|
|
222
|
-
if (fs6.existsSync(logPath)) {
|
|
223
|
-
existingContent = fs6.readFileSync(logPath, "utf8");
|
|
224
|
-
}
|
|
225
|
-
try {
|
|
226
|
-
atomicWriteFileSync(logPath, existingContent + final + "\n", { encoding: "utf8" });
|
|
227
|
-
} catch (err) {
|
|
228
|
-
throw new Error(`Audit log write failed: ${err.message}`);
|
|
229
|
-
}
|
|
230
|
-
result = {
|
|
231
|
-
success: true,
|
|
232
|
-
hash,
|
|
233
|
-
prevHash,
|
|
234
|
-
message: "Audit entry appended"
|
|
235
|
-
};
|
|
236
|
-
});
|
|
237
|
-
return result;
|
|
238
|
-
}
|
|
239
|
-
function verifyAuditLogIntegrity(logPath, options = {}) {
|
|
240
|
-
const failFast = options.failFast !== false;
|
|
241
|
-
const maxEntries = Number.isFinite(options.maxEntries) && options.maxEntries > 0 ? Math.floor(options.maxEntries) : null;
|
|
242
|
-
const response = {
|
|
243
|
-
ok: true,
|
|
244
|
-
file: path6.resolve(logPath),
|
|
245
|
-
entries: 0,
|
|
246
|
-
valid: true,
|
|
247
|
-
firstInvalidIndex: null,
|
|
248
|
-
reason: null,
|
|
249
|
-
errors: [],
|
|
250
|
-
message: "Audit log verified"
|
|
251
|
-
};
|
|
252
|
-
try {
|
|
253
|
-
if (!fs6.existsSync(logPath)) {
|
|
254
|
-
response.message = "Audit log file not found (treated as empty)";
|
|
255
|
-
return response;
|
|
256
|
-
}
|
|
257
|
-
const raw = fs6.readFileSync(logPath, "utf8").trim();
|
|
258
|
-
if (!raw) {
|
|
259
|
-
response.message = "Empty audit log";
|
|
260
|
-
return response;
|
|
261
|
-
}
|
|
262
|
-
const allLines = raw.split("\n");
|
|
263
|
-
const lines = maxEntries ? allLines.slice(0, maxEntries) : allLines;
|
|
264
|
-
response.entries = lines.length;
|
|
265
|
-
let expectedPrevHash = "GENESIS";
|
|
266
|
-
for (let i = 0; i < lines.length; i++) {
|
|
267
|
-
let entry;
|
|
268
|
-
try {
|
|
269
|
-
entry = JSON.parse(lines[i]);
|
|
270
|
-
} catch {
|
|
271
|
-
const errObj = {
|
|
272
|
-
index: i,
|
|
273
|
-
reason: "INVALID_JSON_LINE",
|
|
274
|
-
message: `Line ${i} is not valid JSON`
|
|
275
|
-
};
|
|
276
|
-
response.valid = false;
|
|
277
|
-
response.ok = false;
|
|
278
|
-
response.firstInvalidIndex ??= i;
|
|
279
|
-
response.reason ??= errObj.reason;
|
|
280
|
-
response.errors.push(errObj);
|
|
281
|
-
if (failFast) break;
|
|
282
|
-
continue;
|
|
283
|
-
}
|
|
284
|
-
if (entry.prevHash !== expectedPrevHash) {
|
|
285
|
-
const errObj = {
|
|
286
|
-
index: i,
|
|
287
|
-
reason: "PREV_HASH_MISMATCH",
|
|
288
|
-
message: `Expected prevHash '${expectedPrevHash}', got '${entry.prevHash}'`
|
|
289
|
-
};
|
|
290
|
-
response.valid = false;
|
|
291
|
-
response.ok = false;
|
|
292
|
-
response.firstInvalidIndex ??= i;
|
|
293
|
-
response.reason ??= errObj.reason;
|
|
294
|
-
response.errors.push(errObj);
|
|
295
|
-
if (failFast) break;
|
|
296
|
-
}
|
|
297
|
-
const recordCopy = { ...entry };
|
|
298
|
-
const recordHash = recordCopy.hash;
|
|
299
|
-
delete recordCopy.hash;
|
|
300
|
-
const expectedHash = sha256(JSON.stringify(recordCopy));
|
|
301
|
-
if (recordHash !== expectedHash) {
|
|
302
|
-
const errObj = {
|
|
303
|
-
index: i,
|
|
304
|
-
reason: "HASH_MISMATCH",
|
|
305
|
-
message: `Expected hash '${expectedHash}', got '${recordHash}'`
|
|
306
|
-
};
|
|
307
|
-
response.valid = false;
|
|
308
|
-
response.ok = false;
|
|
309
|
-
response.firstInvalidIndex ??= i;
|
|
310
|
-
response.reason ??= errObj.reason;
|
|
311
|
-
response.errors.push(errObj);
|
|
312
|
-
if (failFast) break;
|
|
313
|
-
}
|
|
314
|
-
expectedPrevHash = recordHash;
|
|
315
|
-
}
|
|
316
|
-
response.message = response.valid ? `Audit log verified: ${response.entries} entries` : `Audit log integrity failed at index ${response.firstInvalidIndex}`;
|
|
317
|
-
return response;
|
|
318
|
-
} catch (err) {
|
|
319
|
-
return {
|
|
320
|
-
ok: false,
|
|
321
|
-
file: path6.resolve(logPath),
|
|
322
|
-
entries: 0,
|
|
323
|
-
valid: false,
|
|
324
|
-
firstInvalidIndex: null,
|
|
325
|
-
reason: "AUDIT_VERIFY_ERROR",
|
|
326
|
-
errors: [{ index: null, reason: "AUDIT_VERIFY_ERROR", message: err.message }],
|
|
327
|
-
message: `Integrity check failed: ${err.message}`
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
var init_auditLog = __esm({
|
|
332
|
-
"src/core/auditLog.js"() {
|
|
333
|
-
init_atomicWrite();
|
|
334
|
-
}
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
// src/core/localPolicy.js
|
|
338
|
-
import fs7 from "fs";
|
|
339
|
-
import path7 from "path";
|
|
340
|
-
import crypto3 from "crypto";
|
|
341
|
-
function glob(pattern) {
|
|
342
|
-
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
343
|
-
return new RegExp("^" + escaped.replace(/\*\*\//g, "(?:.*/)?").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*") + "$");
|
|
344
|
-
}
|
|
345
|
-
function relative(root, value) {
|
|
346
|
-
const rel = path7.relative(root, path7.resolve(value));
|
|
347
|
-
return rel.split(path7.sep).join("/");
|
|
348
|
-
}
|
|
349
|
-
function localPolicyPaths(rootDir) {
|
|
350
|
-
const root = path7.resolve(rootDir || process.cwd());
|
|
351
|
-
return { root, policyPath: path7.join(root, POLICY_FILE), auditPath: path7.join(root, AUDIT_FILE) };
|
|
352
|
-
}
|
|
353
|
-
function loadLocalPolicy(rootDir, mode = "observe") {
|
|
354
|
-
const paths = localPolicyPaths(rootDir);
|
|
355
|
-
if (!fs7.existsSync(paths.policyPath)) {
|
|
356
|
-
return { ...paths, policy: { version: 1, mode, workspace: paths.root, rules: [] } };
|
|
357
|
-
}
|
|
358
|
-
const policy = JSON.parse(fs7.readFileSync(paths.policyPath, "utf8"));
|
|
359
|
-
if (policy?.version !== 1 || !["observe", "enforce"].includes(policy.mode) || !Array.isArray(policy.rules)) {
|
|
360
|
-
throw new Error(`Invalid ${POLICY_FILE}`);
|
|
361
|
-
}
|
|
362
|
-
return { ...paths, policy };
|
|
363
|
-
}
|
|
364
|
-
function writeLocalPolicy(rootDir, policy) {
|
|
365
|
-
const { policyPath, root } = localPolicyPaths(rootDir);
|
|
366
|
-
const next = { ...policy, version: 1, workspace: root, rules: Array.isArray(policy.rules) ? policy.rules : [] };
|
|
367
|
-
atomicWriteFileSync(policyPath, JSON.stringify(next, null, 2) + "\n", { encoding: "utf8" });
|
|
368
|
-
return next;
|
|
369
|
-
}
|
|
370
|
-
function addLocalPolicyRule(rootDir, rule, mode) {
|
|
371
|
-
if (!rule || !["allow", "deny"].includes(rule.effect) || !["path", "command"].includes(rule.type) || typeof rule.pattern !== "string" || !rule.pattern || typeof rule.from !== "string" || !rule.from) {
|
|
372
|
-
throw new Error("Rule requires effect, type, pattern, and from");
|
|
373
|
-
}
|
|
374
|
-
const loaded = loadLocalPolicy(rootDir, mode);
|
|
375
|
-
const entry = {
|
|
376
|
-
id: rule.id || crypto3.randomUUID(),
|
|
377
|
-
effect: rule.effect,
|
|
378
|
-
type: rule.type,
|
|
379
|
-
pattern: rule.pattern,
|
|
380
|
-
from: rule.from,
|
|
381
|
-
at: rule.at || (/* @__PURE__ */ new Date()).toISOString()
|
|
382
|
-
};
|
|
383
|
-
writeLocalPolicy(loaded.root, { ...loaded.policy, mode: mode || loaded.policy.mode, rules: [...loaded.policy.rules, entry] });
|
|
384
|
-
return { id: entry.id, added: true, rule: entry };
|
|
385
|
-
}
|
|
386
|
-
function proposePolicyRule(rule) {
|
|
387
|
-
return { ...rule, proposed: true, at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
388
|
-
}
|
|
389
|
-
function evaluateLocalPolicy(policy, rootDir, { target, command } = {}) {
|
|
390
|
-
const root = path7.resolve(rootDir);
|
|
391
|
-
const candidates = [];
|
|
392
|
-
if (target) candidates.push({ type: "path", value: relative(root, target) });
|
|
393
|
-
if (command) candidates.push({ type: "command", value: command });
|
|
394
|
-
const matched = policy.rules.filter((rule) => candidates.some((c) => c.type === rule.type && glob(rule.pattern).test(c.value)));
|
|
395
|
-
const denied = matched.filter((rule) => rule.effect === "deny");
|
|
396
|
-
return { allowed: denied.length === 0, matched, winningRules: denied.length ? denied : matched.filter((r) => r.effect === "allow"), reason: denied.length ? "LOCAL_POLICY_DENY" : null };
|
|
397
|
-
}
|
|
398
|
-
function auditLocalPolicy(rootDir, entry) {
|
|
399
|
-
const { auditPath } = localPolicyPaths(rootDir);
|
|
400
|
-
appendAudit(auditPath, { kind: "local_policy", timestamp: (/* @__PURE__ */ new Date()).toISOString(), ...entry });
|
|
401
|
-
}
|
|
402
|
-
var POLICY_FILE, AUDIT_FILE;
|
|
403
|
-
var init_localPolicy = __esm({
|
|
404
|
-
"src/core/localPolicy.js"() {
|
|
405
|
-
init_auditLog();
|
|
406
|
-
init_atomicWrite();
|
|
407
|
-
POLICY_FILE = ".lbe/policy.json";
|
|
408
|
-
AUDIT_FILE = ".lbe/audit.jsonl";
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
// src/core/policyVersionGuard.js
|
|
413
|
-
import fs8 from "fs";
|
|
414
|
-
import path8 from "path";
|
|
415
|
-
function parsePolicyVersion(version) {
|
|
416
|
-
if (typeof version === "number" && Number.isFinite(version)) {
|
|
417
|
-
return { ok: true, kind: "int", parts: [Math.floor(version)], raw: String(version) };
|
|
418
|
-
}
|
|
419
|
-
if (typeof version !== "string" || !version.trim()) {
|
|
420
|
-
return { ok: false, reason: "POLICY_VERSION_INVALID", message: "Policy version is required" };
|
|
421
|
-
}
|
|
422
|
-
const trimmed = version.trim();
|
|
423
|
-
if (/^\d+$/.test(trimmed)) {
|
|
424
|
-
return { ok: true, kind: "int", parts: [Number(trimmed)], raw: trimmed };
|
|
425
|
-
}
|
|
426
|
-
const semver = trimmed.replace(/^v/i, "");
|
|
427
|
-
if (/^\d+(\.\d+){0,2}$/.test(semver)) {
|
|
428
|
-
const parsed = semver.split(".").map((n) => Number(n));
|
|
429
|
-
while (parsed.length < 3) {
|
|
430
|
-
parsed.push(0);
|
|
431
|
-
}
|
|
432
|
-
return { ok: true, kind: "semver", parts: parsed, raw: trimmed };
|
|
433
|
-
}
|
|
434
|
-
return {
|
|
435
|
-
ok: false,
|
|
436
|
-
reason: "POLICY_VERSION_INVALID",
|
|
437
|
-
message: `Unsupported policy version format '${version}' (use integer or semver)`
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
function compareVersions(a, b) {
|
|
441
|
-
const len = Math.max(a.parts.length, b.parts.length);
|
|
442
|
-
for (let i = 0; i < len; i++) {
|
|
443
|
-
const av = a.parts[i] ?? 0;
|
|
444
|
-
const bv = b.parts[i] ?? 0;
|
|
445
|
-
if (av > bv) return 1;
|
|
446
|
-
if (av < bv) return -1;
|
|
447
|
-
}
|
|
448
|
-
return 0;
|
|
449
|
-
}
|
|
450
|
-
function parseCreatedAt(createdAt) {
|
|
451
|
-
if (typeof createdAt === "number" && Number.isFinite(createdAt)) {
|
|
452
|
-
const sec = createdAt > 1e12 ? Math.floor(createdAt / 1e3) : Math.floor(createdAt);
|
|
453
|
-
return { ok: true, epochSec: sec };
|
|
454
|
-
}
|
|
455
|
-
if (typeof createdAt !== "string" || !createdAt.trim()) {
|
|
456
|
-
return {
|
|
457
|
-
ok: false,
|
|
458
|
-
reason: "POLICY_CREATED_AT_INVALID",
|
|
459
|
-
message: "Policy createdAt is required"
|
|
460
|
-
};
|
|
461
|
-
}
|
|
462
|
-
const ts = Date.parse(createdAt);
|
|
463
|
-
if (Number.isNaN(ts)) {
|
|
464
|
-
return {
|
|
465
|
-
ok: false,
|
|
466
|
-
reason: "POLICY_CREATED_AT_INVALID",
|
|
467
|
-
message: `Invalid policy createdAt '${createdAt}'`
|
|
468
|
-
};
|
|
469
|
-
}
|
|
470
|
-
return { ok: true, epochSec: Math.floor(ts / 1e3) };
|
|
471
|
-
}
|
|
472
|
-
function loadPolicyState(statePath) {
|
|
473
|
-
if (!fs8.existsSync(statePath)) {
|
|
474
|
-
return {
|
|
475
|
-
schemaVersion: "1",
|
|
476
|
-
lastAccepted: null,
|
|
477
|
-
updatedAt: null
|
|
478
|
-
};
|
|
479
|
-
}
|
|
480
|
-
try {
|
|
481
|
-
const parsed = JSON.parse(fs8.readFileSync(statePath, "utf8"));
|
|
482
|
-
if (!parsed || typeof parsed !== "object") {
|
|
483
|
-
throw new Error("Policy state file has invalid structure");
|
|
484
|
-
}
|
|
485
|
-
return {
|
|
486
|
-
schemaVersion: String(parsed.schemaVersion || "1"),
|
|
487
|
-
lastAccepted: parsed.lastAccepted && typeof parsed.lastAccepted === "object" ? parsed.lastAccepted : null,
|
|
488
|
-
updatedAt: parsed.updatedAt || null
|
|
489
|
-
};
|
|
490
|
-
} catch (err) {
|
|
491
|
-
throw new Error(`Policy state at ${statePath} is corrupt or unreadable: ${err.message}`);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
function savePolicyState(statePath, stateObj) {
|
|
495
|
-
const payload = JSON.stringify(stateObj, null, 2);
|
|
496
|
-
atomicWriteFileSync(statePath, payload, { encoding: "utf8" });
|
|
497
|
-
}
|
|
498
|
-
function validateAndUpdatePolicyVersionState({
|
|
499
|
-
policyObj,
|
|
500
|
-
statePath = path8.resolve(".lbe/data/policy.state.json"),
|
|
501
|
-
maxCreatedAtSkewSec = 31536e3,
|
|
502
|
-
nowSec = Math.floor(Date.now() / 1e3),
|
|
503
|
-
persist = true
|
|
504
|
-
}) {
|
|
505
|
-
const version = parsePolicyVersion(policyObj?.version);
|
|
506
|
-
if (!version.ok) {
|
|
507
|
-
return {
|
|
508
|
-
ok: false,
|
|
509
|
-
reason: version.reason,
|
|
510
|
-
message: version.message,
|
|
511
|
-
updated: false
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
const createdAt = parseCreatedAt(policyObj?.createdAt);
|
|
515
|
-
if (!createdAt.ok) {
|
|
516
|
-
return {
|
|
517
|
-
ok: false,
|
|
518
|
-
reason: createdAt.reason,
|
|
519
|
-
message: createdAt.message,
|
|
520
|
-
updated: false
|
|
521
|
-
};
|
|
522
|
-
}
|
|
523
|
-
const skew = Math.abs(nowSec - createdAt.epochSec);
|
|
524
|
-
const allowedSkew = Number.isFinite(maxCreatedAtSkewSec) && maxCreatedAtSkewSec > 0 ? Math.floor(maxCreatedAtSkewSec) : 31536e3;
|
|
525
|
-
if (skew > allowedSkew) {
|
|
526
|
-
return {
|
|
527
|
-
ok: false,
|
|
528
|
-
reason: "POLICY_CREATED_AT_SKEW_EXCEEDED",
|
|
529
|
-
message: `Policy createdAt skew ${skew}s exceeds allowed ${allowedSkew}s`,
|
|
530
|
-
updated: false
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
let state;
|
|
534
|
-
try {
|
|
535
|
-
state = loadPolicyState(statePath);
|
|
536
|
-
} catch (err) {
|
|
537
|
-
return {
|
|
538
|
-
ok: false,
|
|
539
|
-
reason: "POLICY_STATE_CORRUPT",
|
|
540
|
-
message: err.message,
|
|
541
|
-
updated: false
|
|
542
|
-
};
|
|
543
|
-
}
|
|
544
|
-
const previous = state.lastAccepted;
|
|
545
|
-
let previousVersion = null;
|
|
546
|
-
let previousCreatedAt = null;
|
|
547
|
-
let versionCompare = 0;
|
|
548
|
-
if (previous) {
|
|
549
|
-
previousVersion = parsePolicyVersion(previous.version);
|
|
550
|
-
previousCreatedAt = parseCreatedAt(previous.createdAt);
|
|
551
|
-
if (previousVersion.ok && previousCreatedAt.ok) {
|
|
552
|
-
versionCompare = compareVersions(version, previousVersion);
|
|
553
|
-
if (versionCompare < 0) {
|
|
554
|
-
return {
|
|
555
|
-
ok: false,
|
|
556
|
-
reason: "POLICY_VERSION_REGRESSION",
|
|
557
|
-
message: `Policy version regression: current '${version.raw}' < last '${previousVersion.raw}'`,
|
|
558
|
-
updated: false
|
|
559
|
-
};
|
|
560
|
-
}
|
|
561
|
-
if (versionCompare === 0 && createdAt.epochSec < previousCreatedAt.epochSec) {
|
|
562
|
-
return {
|
|
563
|
-
ok: false,
|
|
564
|
-
reason: "POLICY_CREATED_AT_REGRESSION",
|
|
565
|
-
message: `Policy createdAt regression: current '${policyObj.createdAt}' < last '${previous.createdAt}'`,
|
|
566
|
-
updated: false
|
|
567
|
-
};
|
|
568
|
-
}
|
|
569
|
-
if (versionCompare > 0 && createdAt.epochSec < previousCreatedAt.epochSec) {
|
|
570
|
-
return {
|
|
571
|
-
ok: false,
|
|
572
|
-
reason: "POLICY_CREATED_AT_REGRESSION",
|
|
573
|
-
message: `Policy createdAt must be monotonic when version increases`,
|
|
574
|
-
updated: false
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
const shouldUpdate = !previous || !previousVersion?.ok || !previousCreatedAt?.ok || versionCompare > 0 || versionCompare === 0 && createdAt.epochSec > previousCreatedAt.epochSec;
|
|
580
|
-
if (persist && shouldUpdate) {
|
|
581
|
-
const nextState = {
|
|
582
|
-
schemaVersion: "1",
|
|
583
|
-
lastAccepted: {
|
|
584
|
-
version: policyObj.version,
|
|
585
|
-
createdAt: policyObj.createdAt,
|
|
586
|
-
environment: policyObj.environment || null
|
|
587
|
-
},
|
|
588
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
589
|
-
};
|
|
590
|
-
savePolicyState(statePath, nextState);
|
|
591
|
-
}
|
|
592
|
-
return {
|
|
593
|
-
ok: true,
|
|
594
|
-
reason: null,
|
|
595
|
-
message: "Policy version guard passed",
|
|
596
|
-
updated: shouldUpdate
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
|
-
var init_policyVersionGuard = __esm({
|
|
600
|
-
"src/core/policyVersionGuard.js"() {
|
|
601
|
-
init_atomicWrite();
|
|
602
|
-
}
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
// runtime/engine.js
|
|
606
|
-
import fs9 from "fs";
|
|
607
|
-
import path9 from "path";
|
|
608
|
-
import { fileURLToPath } from "url";
|
|
609
|
-
function wasm() {
|
|
610
|
-
if (_instance) return _instance;
|
|
611
|
-
if (!fs9.existsSync(wasmPath)) throw new Error(`LBE engine missing: ${wasmPath}`);
|
|
612
|
-
const bytes = fs9.readFileSync(wasmPath);
|
|
613
|
-
_instance = new WebAssembly.Instance(new WebAssembly.Module(bytes), {});
|
|
614
|
-
return _instance;
|
|
615
|
-
}
|
|
616
|
-
function memory() {
|
|
617
|
-
return new Uint8Array(wasm().exports.memory.buffer);
|
|
618
|
-
}
|
|
619
|
-
function inPtr() {
|
|
620
|
-
return wasm().exports.lbe_in_ptr();
|
|
621
|
-
}
|
|
622
|
-
function outPtr() {
|
|
623
|
-
return wasm().exports.lbe_out_ptr();
|
|
624
|
-
}
|
|
625
|
-
function bufSize() {
|
|
626
|
-
return wasm().exports.lbe_buf_size();
|
|
627
|
-
}
|
|
628
|
-
function writeIn(str) {
|
|
629
|
-
const enc = new TextEncoder().encode(str);
|
|
630
|
-
const mem = memory();
|
|
631
|
-
const ptr = inPtr();
|
|
632
|
-
mem.set(enc, ptr);
|
|
633
|
-
mem[ptr + enc.length] = 0;
|
|
634
|
-
}
|
|
635
|
-
function readOut() {
|
|
636
|
-
const mem = memory();
|
|
637
|
-
const ptr = outPtr();
|
|
638
|
-
let end = ptr;
|
|
639
|
-
while (mem[end] !== 0 && end - ptr < bufSize()) end++;
|
|
640
|
-
return new TextDecoder().decode(mem.slice(ptr, end));
|
|
641
|
-
}
|
|
642
|
-
function writePipelineInput(fields) {
|
|
643
|
-
const mem = memory();
|
|
644
|
-
const ptr = inPtr();
|
|
645
|
-
const view = new DataView(mem.buffer, ptr);
|
|
646
|
-
fields.forEach((v, i) => view.setUint32(i * 4, v >>> 0, true));
|
|
647
|
-
}
|
|
648
|
-
function readPipelineOutput() {
|
|
649
|
-
const mem = memory();
|
|
650
|
-
const ptr = outPtr();
|
|
651
|
-
const view = new DataView(mem.buffer, ptr);
|
|
652
|
-
return { stage: view.getUint32(0, true), code: view.getUint32(4, true) };
|
|
653
|
-
}
|
|
654
|
-
function runValidationPipeline(flags) {
|
|
655
|
-
writePipelineInput([
|
|
656
|
-
// Schema flags [0..24]
|
|
657
|
-
flags.hasId ? 1 : 0,
|
|
658
|
-
flags.idValid ? 1 : 0,
|
|
659
|
-
flags.hasCommandId ? 1 : 0,
|
|
660
|
-
flags.commandIdValid ? 1 : 0,
|
|
661
|
-
flags.hasRequesterId ? 1 : 0,
|
|
662
|
-
flags.requesterIdValid ? 1 : 0,
|
|
663
|
-
flags.hasSessionId ? 1 : 0,
|
|
664
|
-
flags.sessionIdValid ? 1 : 0,
|
|
665
|
-
flags.hasTimestamp ? 1 : 0,
|
|
666
|
-
flags.timestampValid ? 1 : 0,
|
|
667
|
-
flags.hasNonce ? 1 : 0,
|
|
668
|
-
flags.nonceValid ? 1 : 0,
|
|
669
|
-
flags.hasRequires ? 1 : 0,
|
|
670
|
-
flags.requiresValid ? 1 : 0,
|
|
671
|
-
flags.hasPayload ? 1 : 0,
|
|
672
|
-
flags.hasPayloadAdapter ? 1 : 0,
|
|
673
|
-
flags.payloadAdapterValid ? 1 : 0,
|
|
674
|
-
flags.hasSignature ? 1 : 0,
|
|
675
|
-
flags.hasSignatureAlg ? 1 : 0,
|
|
676
|
-
flags.signatureAlgValid ? 1 : 0,
|
|
677
|
-
flags.hasSignatureKeyId ? 1 : 0,
|
|
678
|
-
flags.hasSignatureSig ? 1 : 0,
|
|
679
|
-
flags.signatureSigValid ? 1 : 0,
|
|
680
|
-
flags.hasRisk ? 1 : 0,
|
|
681
|
-
flags.riskValid ? 1 : 0,
|
|
682
|
-
// Timestamp [25..27]
|
|
683
|
-
flags.cmdTimestamp >>> 0,
|
|
684
|
-
flags.nowSec >>> 0,
|
|
685
|
-
flags.maxClockSkewSec >>> 0,
|
|
686
|
-
// Key lifecycle [28..34]
|
|
687
|
-
flags.keyIdFormatValid ? 1 : 0,
|
|
688
|
-
flags.keyFound ? 1 : 0,
|
|
689
|
-
flags.keyNotDeprecated ? 1 : 0,
|
|
690
|
-
flags.keyRequesterMatches ? 1 : 0,
|
|
691
|
-
flags.keyNotBeforeOk ? 1 : 0,
|
|
692
|
-
flags.keyNotExpired ? 1 : 0,
|
|
693
|
-
flags.keyLifecycleFieldsPresent ? 1 : 0,
|
|
694
|
-
// Signature [35]
|
|
695
|
-
flags.signatureValid ? 1 : 0,
|
|
696
|
-
// Rate limit [36..37]
|
|
697
|
-
flags.rateLimitOk ? 1 : 0,
|
|
698
|
-
flags.rateLimitRetryAfterSec >>> 0,
|
|
699
|
-
// Nonce [38]
|
|
700
|
-
flags.nonceOk ? 1 : 0,
|
|
701
|
-
// Policy [39..48]
|
|
702
|
-
flags.policyConfigured ? 1 : 0,
|
|
703
|
-
flags.requesterConfigured ? 1 : 0,
|
|
704
|
-
flags.commandAllowed ? 1 : 0,
|
|
705
|
-
flags.adapterAllowed ? 1 : 0,
|
|
706
|
-
flags.filesystemRequired ? 1 : 0,
|
|
707
|
-
flags.filesystemRootsDefined ? 1 : 0,
|
|
708
|
-
flags.filesystemOk ? 1 : 0,
|
|
709
|
-
flags.pathDenied ? 1 : 0,
|
|
710
|
-
flags.shellRequired ? 1 : 0,
|
|
711
|
-
flags.shellCommandOk ? 1 : 0
|
|
712
|
-
]);
|
|
713
|
-
wasm().exports.lbe_validate_pipeline();
|
|
714
|
-
const { stage, code } = readPipelineOutput();
|
|
715
|
-
const ok = stage === 255;
|
|
716
|
-
return {
|
|
717
|
-
ok,
|
|
718
|
-
stage,
|
|
719
|
-
stageLabel: PIPELINE_STAGES[stage] || "unknown",
|
|
720
|
-
code,
|
|
721
|
-
schemaError: stage === 0 ? SCHEMA_MESSAGES[code]?.error || "Schema invalid" : null,
|
|
722
|
-
keyReason: stage === 2 ? KEY_REASONS[code] || "KEY_ERROR" : null,
|
|
723
|
-
policyResult: stage === 6 ? { ...POLICY_MESSAGES[code] || POLICY_MESSAGES[1], code } : null,
|
|
724
|
-
retryAfterSec: stage === 4 ? code : 0,
|
|
725
|
-
skewSec: stage === 1 ? code : 0
|
|
726
|
-
};
|
|
727
|
-
}
|
|
728
|
-
function checkNonce({ ttlSec, nowSec, newKey, existingEntries }) {
|
|
729
|
-
const lines = [`${ttlSec}:${nowSec}`, newKey, ...existingEntries].join("\n") + "\n";
|
|
730
|
-
writeIn(lines);
|
|
731
|
-
const isReplay = wasm().exports.lbe_nonce_check() !== 0;
|
|
732
|
-
if (isReplay) return { ok: false, updatedEntriesText: null };
|
|
733
|
-
const out = readOut();
|
|
734
|
-
return { ok: true, updatedEntriesText: out.startsWith("OK\n") ? out.slice(3) : out };
|
|
735
|
-
}
|
|
736
|
-
function checkRateLimit({ windowSec, maxRequests, nowSec, requesterId, existingEntries }) {
|
|
737
|
-
const lines = [
|
|
738
|
-
`${windowSec}:${maxRequests}:${nowSec}`,
|
|
739
|
-
requesterId,
|
|
740
|
-
...existingEntries
|
|
741
|
-
].join("\n") + "\n";
|
|
742
|
-
writeIn(lines);
|
|
743
|
-
const exceeded = wasm().exports.lbe_rate_check() !== 0;
|
|
744
|
-
const out = readOut();
|
|
745
|
-
if (exceeded) {
|
|
746
|
-
const retryAfterSec = parseInt(out.match(/^EXCEEDED:(\d+)/)?.[1] ?? "1", 10);
|
|
747
|
-
const entriesText = out.replace(/^EXCEEDED:\d+\n/, "");
|
|
748
|
-
return { ok: false, retryAfterSec, updatedEntriesText: entriesText };
|
|
749
|
-
}
|
|
750
|
-
return { ok: true, retryAfterSec: 0, updatedEntriesText: out.startsWith("OK\n") ? out.slice(3) : out };
|
|
751
|
-
}
|
|
752
|
-
function classifyRisk(commandId, shellCmdIsRm = false) {
|
|
753
|
-
const typeCode = COMMAND_TYPE[commandId] ?? 0;
|
|
754
|
-
const code = wasm().exports.lbe_classify_risk(typeCode, shellCmdIsRm ? 1 : 0);
|
|
755
|
-
return RISK_LABELS[code] ?? "LOW";
|
|
756
|
-
}
|
|
757
|
-
var runtimeDir, wasmPath, POLICY_MESSAGES, SCHEMA_MESSAGES, KEY_REASONS, PIPELINE_STAGES, RISK_LABELS, COMMAND_TYPE, _instance;
|
|
758
|
-
var init_engine = __esm({
|
|
759
|
-
"runtime/engine.js"() {
|
|
760
|
-
runtimeDir = path9.dirname(fileURLToPath(import.meta.url));
|
|
761
|
-
wasmPath = path9.join(runtimeDir, "lbe_engine.wasm");
|
|
762
|
-
POLICY_MESSAGES = {
|
|
763
|
-
0: { allowed: true, reason: null, message: "Policy check passed" },
|
|
764
|
-
1: { allowed: false, reason: "POLICY_NOT_CONFIGURED", message: "No policy configured" },
|
|
765
|
-
2: { allowed: false, reason: "REQUESTER_NOT_ALLOWED", message: "Requester not in policy" },
|
|
766
|
-
3: { allowed: false, reason: "COMMAND_NOT_ALLOWED", message: "Command not allowed for requester" },
|
|
767
|
-
4: { allowed: false, reason: "ADAPTER_NOT_ALLOWED", message: "Adapter not allowed" },
|
|
768
|
-
5: { allowed: false, reason: "NO_FILESYSTEM_ROOTS_DEFINED", message: "No filesystem roots defined for requester" },
|
|
769
|
-
6: { allowed: false, reason: "CWD_OUTSIDE_ALLOWED_ROOT", message: "Path not under allowed roots" },
|
|
770
|
-
7: { allowed: false, reason: "PATH_DENIED_BY_PATTERN", message: "Path matches deny pattern" },
|
|
771
|
-
8: { allowed: false, reason: "SHELL_CMD_DENIED", message: "Shell command not allowed" }
|
|
772
|
-
};
|
|
773
|
-
SCHEMA_MESSAGES = {
|
|
774
|
-
0: { valid: true, error: null },
|
|
775
|
-
1: { valid: false, error: "Missing required field: id" },
|
|
776
|
-
2: { valid: false, error: "Missing required field: commandId" },
|
|
777
|
-
3: { valid: false, error: "Missing required field: requesterId" },
|
|
778
|
-
4: { valid: false, error: "Missing required field: sessionId" },
|
|
779
|
-
5: { valid: false, error: "Missing required field: timestamp" },
|
|
780
|
-
6: { valid: false, error: "Missing required field: nonce" },
|
|
781
|
-
7: { valid: false, error: "Missing required field: requires" },
|
|
782
|
-
8: { valid: false, error: "Missing required field: payload" },
|
|
783
|
-
9: { valid: false, error: "Missing required field: signature" },
|
|
784
|
-
10: { valid: false, error: "Field 'id' is invalid" },
|
|
785
|
-
11: { valid: false, error: "Field 'commandId' is invalid" },
|
|
786
|
-
12: { valid: false, error: "Field 'requesterId' is invalid" },
|
|
787
|
-
13: { valid: false, error: "Field 'sessionId' is invalid" },
|
|
788
|
-
14: { valid: false, error: "Field 'timestamp' is invalid" },
|
|
789
|
-
15: { valid: false, error: "Field 'nonce' is invalid" },
|
|
790
|
-
16: { valid: false, error: "Field 'requires' is invalid" },
|
|
791
|
-
17: { valid: false, error: "payload: missing required field: adapter" },
|
|
792
|
-
18: { valid: false, error: "payload: field 'adapter' is invalid" },
|
|
793
|
-
19: { valid: false, error: "signature: missing required field: alg" },
|
|
794
|
-
20: { valid: false, error: "signature: missing required field: keyId" },
|
|
795
|
-
21: { valid: false, error: "signature: missing required field: sig" },
|
|
796
|
-
22: { valid: false, error: "signature: field 'alg' must be ed25519" },
|
|
797
|
-
23: { valid: false, error: "signature: field 'sig' is invalid" },
|
|
798
|
-
24: { valid: false, error: "Field 'risk' is invalid" }
|
|
799
|
-
};
|
|
800
|
-
KEY_REASONS = {
|
|
801
|
-
1: "KEY_ID_INVALID",
|
|
802
|
-
2: "KEY_NOT_TRUSTED",
|
|
803
|
-
3: "KEY_DEPRECATED",
|
|
804
|
-
4: "KEY_REQUESTER_MISMATCH",
|
|
805
|
-
5: "KEY_LIFECYCLE_INVALID",
|
|
806
|
-
6: "KEY_NOT_YET_VALID",
|
|
807
|
-
7: "KEY_EXPIRED"
|
|
808
|
-
};
|
|
809
|
-
PIPELINE_STAGES = {
|
|
810
|
-
0: "schema",
|
|
811
|
-
1: "timestamp",
|
|
812
|
-
2: "key",
|
|
813
|
-
3: "signature",
|
|
814
|
-
4: "rate_limit",
|
|
815
|
-
5: "nonce",
|
|
816
|
-
6: "policy",
|
|
817
|
-
255: "ok"
|
|
818
|
-
};
|
|
819
|
-
RISK_LABELS = ["LOW", "MEDIUM", "HIGH", "CRITICAL"];
|
|
820
|
-
COMMAND_TYPE = { ECHO: 0, READ_FILE: 1, WRITE_FILE: 2, PATCH_FILE: 3, DELETE_FILE: 4, RUN_SHELL: 5 };
|
|
821
|
-
_instance = null;
|
|
822
|
-
}
|
|
823
|
-
});
|
|
824
|
-
|
|
825
|
-
// src/core/validator.js
|
|
826
|
-
import path10 from "path";
|
|
827
|
-
function extractSchemaFlags(cmd2) {
|
|
828
|
-
const has = (k) => cmd2 != null && Object.prototype.hasOwnProperty.call(cmd2, k);
|
|
829
|
-
const isStr = (v) => typeof v === "string";
|
|
830
|
-
const p = cmd2?.payload;
|
|
831
|
-
const sig = cmd2?.signature;
|
|
832
|
-
return {
|
|
833
|
-
hasId: has("id"),
|
|
834
|
-
idValid: isStr(cmd2?.id) && /^[A-Z_]+$/.test(cmd2.id) && cmd2.id.length >= 1 && cmd2.id.length <= 50,
|
|
835
|
-
hasCommandId: has("commandId"),
|
|
836
|
-
commandIdValid: isStr(cmd2?.commandId) && /^[a-f0-9-]+$/.test(cmd2.commandId) && cmd2.commandId.length === 36,
|
|
837
|
-
hasRequesterId: has("requesterId"),
|
|
838
|
-
requesterIdValid: isStr(cmd2?.requesterId) && cmd2.requesterId.length >= 3 && cmd2.requesterId.length <= 100,
|
|
839
|
-
hasSessionId: has("sessionId"),
|
|
840
|
-
sessionIdValid: isStr(cmd2?.sessionId) && cmd2.sessionId.length >= 3,
|
|
841
|
-
hasTimestamp: has("timestamp"),
|
|
842
|
-
timestampValid: typeof cmd2?.timestamp === "number" && cmd2.timestamp >= 1e9,
|
|
843
|
-
hasNonce: has("nonce"),
|
|
844
|
-
nonceValid: isStr(cmd2?.nonce) && cmd2.nonce.length >= 32 && cmd2.nonce.length <= 128,
|
|
845
|
-
hasRequires: has("requires"),
|
|
846
|
-
requiresValid: Array.isArray(cmd2?.requires) && cmd2.requires.length >= 1 && cmd2.requires.every(isStr),
|
|
847
|
-
hasPayload: has("payload") && typeof p === "object" && p !== null && !Array.isArray(p),
|
|
848
|
-
hasPayloadAdapter: p != null && Object.prototype.hasOwnProperty.call(p, "adapter"),
|
|
849
|
-
payloadAdapterValid: isStr(p?.adapter),
|
|
850
|
-
hasSignature: has("signature") && typeof sig === "object" && sig !== null && !Array.isArray(sig),
|
|
851
|
-
hasSignatureAlg: sig != null && Object.prototype.hasOwnProperty.call(sig, "alg"),
|
|
852
|
-
signatureAlgValid: sig?.alg === "ed25519",
|
|
853
|
-
hasSignatureKeyId: sig != null && Object.prototype.hasOwnProperty.call(sig, "keyId"),
|
|
854
|
-
hasSignatureSig: sig != null && Object.prototype.hasOwnProperty.call(sig, "sig"),
|
|
855
|
-
signatureSigValid: isStr(sig?.sig) && sig.sig.length >= 10,
|
|
856
|
-
hasRisk: has("risk"),
|
|
857
|
-
riskValid: ["LOW", "MEDIUM", "HIGH", "CRITICAL"].includes(cmd2?.risk)
|
|
858
|
-
};
|
|
859
|
-
}
|
|
860
|
-
function extractPolicyFlags(policy, cmd2) {
|
|
861
|
-
const hasPolicy = !!(policy && policy.default === "DENY" && policy.requesters && typeof policy.requesters === "object");
|
|
862
|
-
const rp = policy?.requesters?.[cmd2.requesterId];
|
|
863
|
-
const cmdId = cmd2.id?.toLowerCase() ?? "";
|
|
864
|
-
const commandAllowed = !!rp?.allowCommands?.some((c) => c.toLowerCase() === cmdId);
|
|
865
|
-
const adapterAllowed = !!rp?.allowAdapters?.includes(cmd2.payload?.adapter);
|
|
866
|
-
const filesystemRequired = !!cmd2.payload?.cwd;
|
|
867
|
-
let filesystemRootsDefined = false;
|
|
868
|
-
let filesystemOk = false;
|
|
869
|
-
let pathDenied = false;
|
|
870
|
-
if (filesystemRequired) {
|
|
871
|
-
const roots = rp?.filesystem?.roots ?? [];
|
|
872
|
-
filesystemRootsDefined = roots.length > 0;
|
|
873
|
-
if (filesystemRootsDefined) {
|
|
874
|
-
const cwd = path10.resolve(cmd2.payload.cwd);
|
|
875
|
-
filesystemOk = roots.some((r) => {
|
|
876
|
-
const rr = path10.resolve(r);
|
|
877
|
-
return cwd === rr || cwd.startsWith(rr + path10.sep);
|
|
878
|
-
});
|
|
879
|
-
const denyPatterns = rp?.filesystem?.denyPatterns ?? [];
|
|
880
|
-
pathDenied = denyPatterns.some((pattern) => {
|
|
881
|
-
const re = new RegExp("^" + pattern.replace(/\./g, "\\.").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*") + "$");
|
|
882
|
-
return re.test(cwd);
|
|
883
|
-
});
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
let shellRequired = false;
|
|
887
|
-
let shellCommandOk = true;
|
|
888
|
-
if (cmd2.id === "RUN_SHELL") {
|
|
889
|
-
shellRequired = true;
|
|
890
|
-
const allowCmds = rp?.exec?.allowCmds ?? [];
|
|
891
|
-
const denyCmds = rp?.exec?.denyCmds ?? [];
|
|
892
|
-
const shellCmd = cmd2.payload?.cmd;
|
|
893
|
-
if (denyCmds.includes(shellCmd)) {
|
|
894
|
-
shellCommandOk = false;
|
|
895
|
-
} else {
|
|
896
|
-
shellCommandOk = allowCmds.length === 0 || allowCmds.includes(shellCmd);
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
return {
|
|
900
|
-
policyConfigured: hasPolicy,
|
|
901
|
-
requesterConfigured: !!rp,
|
|
902
|
-
commandAllowed,
|
|
903
|
-
adapterAllowed,
|
|
904
|
-
filesystemRequired,
|
|
905
|
-
filesystemRootsDefined,
|
|
906
|
-
filesystemOk,
|
|
907
|
-
pathDenied,
|
|
908
|
-
shellRequired,
|
|
909
|
-
shellCommandOk
|
|
910
|
-
};
|
|
911
|
-
}
|
|
912
|
-
function extractKeyFlags(keyStore, keyId, requesterId, now = /* @__PURE__ */ new Date()) {
|
|
913
|
-
if (!keyStore || !keyId) {
|
|
914
|
-
return {
|
|
915
|
-
keyIdFormatValid: false,
|
|
916
|
-
keyFound: false,
|
|
917
|
-
keyNotDeprecated: false,
|
|
918
|
-
keyRequesterMatches: false,
|
|
919
|
-
keyNotBeforeOk: false,
|
|
920
|
-
keyNotExpired: false,
|
|
921
|
-
keyLifecycleFieldsPresent: false,
|
|
922
|
-
publicKey: null
|
|
923
|
-
};
|
|
924
|
-
}
|
|
925
|
-
const KEY_ID_RE = /^[A-Za-z0-9:_-]{3,128}$/;
|
|
926
|
-
const keyIdFormatValid = KEY_ID_RE.test(keyId) && keyId !== "default";
|
|
927
|
-
if (!keyIdFormatValid) {
|
|
928
|
-
return {
|
|
929
|
-
keyIdFormatValid,
|
|
930
|
-
keyFound: false,
|
|
931
|
-
keyNotDeprecated: false,
|
|
932
|
-
keyRequesterMatches: false,
|
|
933
|
-
keyNotBeforeOk: false,
|
|
934
|
-
keyNotExpired: false,
|
|
935
|
-
keyLifecycleFieldsPresent: false,
|
|
936
|
-
publicKey: null
|
|
937
|
-
};
|
|
938
|
-
}
|
|
939
|
-
const entry = keyStore.trustedKeys?.[keyId];
|
|
940
|
-
const keyFound = !!entry;
|
|
941
|
-
if (!keyFound) {
|
|
942
|
-
return {
|
|
943
|
-
keyIdFormatValid,
|
|
944
|
-
keyFound,
|
|
945
|
-
keyNotDeprecated: false,
|
|
946
|
-
keyRequesterMatches: false,
|
|
947
|
-
keyNotBeforeOk: false,
|
|
948
|
-
keyNotExpired: false,
|
|
949
|
-
keyLifecycleFieldsPresent: false,
|
|
950
|
-
publicKey: null
|
|
951
|
-
};
|
|
952
|
-
}
|
|
953
|
-
const keyNotDeprecated = !entry.deprecated;
|
|
954
|
-
const keyRequesterMatches = !entry.requesterId || entry.requesterId === requesterId;
|
|
955
|
-
const notBefore = entry.notBefore || entry.validFrom;
|
|
956
|
-
const expiresAt = entry.expiresAt || entry.validUntil;
|
|
957
|
-
const keyLifecycleFieldsPresent = typeof notBefore === "string" && typeof expiresAt === "string";
|
|
958
|
-
let keyNotBeforeOk = false;
|
|
959
|
-
let keyNotExpired = false;
|
|
960
|
-
if (keyLifecycleFieldsPresent) {
|
|
961
|
-
const nb = new Date(notBefore);
|
|
962
|
-
const exp = new Date(expiresAt);
|
|
963
|
-
if (!isNaN(nb.getTime()) && !isNaN(exp.getTime()) && nb < exp) {
|
|
964
|
-
keyNotBeforeOk = now >= nb;
|
|
965
|
-
keyNotExpired = now < exp;
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
return {
|
|
969
|
-
keyIdFormatValid,
|
|
970
|
-
keyFound,
|
|
971
|
-
keyNotDeprecated,
|
|
972
|
-
keyRequesterMatches,
|
|
973
|
-
keyNotBeforeOk,
|
|
974
|
-
keyNotExpired,
|
|
975
|
-
keyLifecycleFieldsPresent,
|
|
976
|
-
publicKey: entry.publicKey ?? null
|
|
977
|
-
};
|
|
978
|
-
}
|
|
979
|
-
function nonceEntriesToText(db) {
|
|
980
|
-
return (db?.entries ?? []).map((e) => `${e.key}:${e.timestamp}`);
|
|
981
|
-
}
|
|
982
|
-
function textToNonceEntries(text) {
|
|
983
|
-
return text.split("\n").filter(Boolean).map((line) => {
|
|
984
|
-
const lastColon = line.lastIndexOf(":");
|
|
985
|
-
return {
|
|
986
|
-
key: line.slice(0, lastColon),
|
|
987
|
-
timestamp: parseInt(line.slice(lastColon + 1), 10) || 0
|
|
988
|
-
};
|
|
989
|
-
});
|
|
990
|
-
}
|
|
991
|
-
function rateEntriesToText(db) {
|
|
992
|
-
return (db?.entries ?? []).map((e) => `${e.requesterId}:${e.timestamp}`);
|
|
993
|
-
}
|
|
994
|
-
function textToRateEntries(text) {
|
|
995
|
-
return text.split("\n").filter(Boolean).map((line) => {
|
|
996
|
-
const lastColon = line.lastIndexOf(":");
|
|
997
|
-
return {
|
|
998
|
-
requesterId: line.slice(0, lastColon),
|
|
999
|
-
timestamp: parseInt(line.slice(lastColon + 1), 10) || 0
|
|
1000
|
-
};
|
|
1001
|
-
});
|
|
1002
|
-
}
|
|
1003
|
-
function validateCommand({
|
|
1004
|
-
commandObj,
|
|
1005
|
-
pubKeyB64,
|
|
1006
|
-
keyStore,
|
|
1007
|
-
nonceDb,
|
|
1008
|
-
policy,
|
|
1009
|
-
rateLimiter,
|
|
1010
|
-
policyStatePath
|
|
1011
|
-
}) {
|
|
1012
|
-
const result = {
|
|
1013
|
-
valid: false,
|
|
1014
|
-
commandId: commandObj?.commandId,
|
|
1015
|
-
checks: {},
|
|
1016
|
-
errors: []
|
|
1017
|
-
};
|
|
1018
|
-
const nowSec = Math.floor(Date.now() / 1e3);
|
|
1019
|
-
const now = /* @__PURE__ */ new Date();
|
|
1020
|
-
const maxClockSkewSec = Number.isFinite(policy?.security?.maxClockSkewSec) ? policy.security.maxClockSkewSec : 600;
|
|
1021
|
-
if (policyStatePath && policy?.version !== void 0) {
|
|
1022
|
-
try {
|
|
1023
|
-
const vCheck = validateAndUpdatePolicyVersionState({ policyObj: policy, statePath: policyStatePath });
|
|
1024
|
-
result.checks.policyVersion = vCheck.ok;
|
|
1025
|
-
if (!vCheck.ok) {
|
|
1026
|
-
result.errors.push({ type: "POLICY_VERSION_INVALID", message: vCheck.message });
|
|
1027
|
-
return result;
|
|
1028
|
-
}
|
|
1029
|
-
} catch {
|
|
1030
|
-
result.checks.policyVersion = true;
|
|
1031
|
-
}
|
|
1032
|
-
} else {
|
|
1033
|
-
result.checks.policyVersion = true;
|
|
1034
|
-
}
|
|
1035
|
-
const schemaFlags = extractSchemaFlags(commandObj);
|
|
1036
|
-
const keyId = commandObj?.signature?.keyId;
|
|
1037
|
-
const keyFlags = extractKeyFlags(keyStore, keyId, commandObj?.requesterId, now);
|
|
1038
|
-
let signatureValid = false;
|
|
1039
|
-
let effectivePubKey = keyFlags.publicKey;
|
|
1040
|
-
if (!effectivePubKey && pubKeyB64) effectivePubKey = pubKeyB64;
|
|
1041
|
-
if (effectivePubKey) {
|
|
1042
|
-
const bodyWithoutSig = { ...commandObj };
|
|
1043
|
-
delete bodyWithoutSig.signature;
|
|
1044
|
-
const sigCheck = verifyEd25519({
|
|
1045
|
-
payloadObj: bodyWithoutSig,
|
|
1046
|
-
sigB64: commandObj?.signature?.sig,
|
|
1047
|
-
pubKeyB64: effectivePubKey
|
|
1048
|
-
});
|
|
1049
|
-
signatureValid = sigCheck.valid;
|
|
1050
|
-
}
|
|
1051
|
-
let rateLimitOk = true;
|
|
1052
|
-
let rateLimitRetryAfterSec = 0;
|
|
1053
|
-
if (signatureValid && rateLimiter && typeof rateLimiter.db !== "undefined") {
|
|
1054
|
-
const rateCfg = policy?.requesters?.[commandObj.requesterId]?.rateLimit || {};
|
|
1055
|
-
const dfltCfg = policy?.security?.defaultRateLimit || {};
|
|
1056
|
-
const windowSec = rateCfg.windowSec ?? dfltCfg.windowSec ?? 60;
|
|
1057
|
-
const maxRequests = rateCfg.maxRequests ?? dfltCfg.maxRequests ?? 30;
|
|
1058
|
-
const rateResult = checkRateLimit({
|
|
1059
|
-
windowSec,
|
|
1060
|
-
maxRequests,
|
|
1061
|
-
nowSec,
|
|
1062
|
-
requesterId: commandObj.requesterId,
|
|
1063
|
-
existingEntries: rateEntriesToText(rateLimiter.db)
|
|
1064
|
-
});
|
|
1065
|
-
rateLimitOk = rateResult.ok;
|
|
1066
|
-
rateLimitRetryAfterSec = rateResult.retryAfterSec;
|
|
1067
|
-
if (rateResult.ok) {
|
|
1068
|
-
rateLimiter.db.entries = textToRateEntries(rateResult.updatedEntriesText);
|
|
1069
|
-
}
|
|
1070
|
-
} else if (signatureValid && rateLimiter && typeof rateLimiter.checkAndRecord === "function") {
|
|
1071
|
-
const rateCfg = policy?.requesters?.[commandObj.requesterId]?.rateLimit || {};
|
|
1072
|
-
const dfltCfg = policy?.security?.defaultRateLimit || {};
|
|
1073
|
-
const rateCheck = rateLimiter.checkAndRecord({
|
|
1074
|
-
requesterId: commandObj.requesterId,
|
|
1075
|
-
nowSec,
|
|
1076
|
-
windowSec: rateCfg.windowSec ?? dfltCfg.windowSec ?? 60,
|
|
1077
|
-
maxRequests: rateCfg.maxRequests ?? dfltCfg.maxRequests ?? 30
|
|
1078
|
-
});
|
|
1079
|
-
rateLimitOk = rateCheck.ok;
|
|
1080
|
-
rateLimitRetryAfterSec = rateCheck.retryAfterSec ?? 0;
|
|
1081
|
-
}
|
|
1082
|
-
let nonceOk = true;
|
|
1083
|
-
const nonceKey = `${commandObj?.requesterId}|${commandObj?.sessionId}|${commandObj?.nonce}`;
|
|
1084
|
-
const ttlSec = 3600;
|
|
1085
|
-
if (signatureValid && rateLimitOk && nonceDb) {
|
|
1086
|
-
if (typeof nonceDb.checkAndRecord === "function") {
|
|
1087
|
-
if (nonceDb.db) {
|
|
1088
|
-
const nonceResult = checkNonce({
|
|
1089
|
-
ttlSec,
|
|
1090
|
-
nowSec,
|
|
1091
|
-
newKey: nonceKey,
|
|
1092
|
-
existingEntries: nonceEntriesToText(nonceDb.db)
|
|
1093
|
-
});
|
|
1094
|
-
nonceOk = nonceResult.ok;
|
|
1095
|
-
if (nonceResult.ok) {
|
|
1096
|
-
nonceDb.db.entries = textToNonceEntries(nonceResult.updatedEntriesText);
|
|
1097
|
-
}
|
|
1098
|
-
} else {
|
|
1099
|
-
const r = nonceDb.checkAndRecord({
|
|
1100
|
-
requesterId: commandObj.requesterId,
|
|
1101
|
-
sessionId: commandObj.sessionId,
|
|
1102
|
-
nonce: commandObj.nonce
|
|
1103
|
-
});
|
|
1104
|
-
nonceOk = r.ok;
|
|
1105
|
-
}
|
|
1106
|
-
} else {
|
|
1107
|
-
const nonceResult = checkNonce({
|
|
1108
|
-
ttlSec,
|
|
1109
|
-
nowSec,
|
|
1110
|
-
newKey: nonceKey,
|
|
1111
|
-
existingEntries: nonceEntriesToText(nonceDb)
|
|
1112
|
-
});
|
|
1113
|
-
nonceOk = nonceResult.ok;
|
|
1114
|
-
if (nonceResult.ok) {
|
|
1115
|
-
nonceDb.entries = textToNonceEntries(nonceResult.updatedEntriesText);
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
const policyFlags = extractPolicyFlags(policy, commandObj ?? {});
|
|
1120
|
-
const pipeline = runValidationPipeline({
|
|
1121
|
-
...schemaFlags,
|
|
1122
|
-
cmdTimestamp: commandObj?.timestamp ?? 0,
|
|
1123
|
-
nowSec,
|
|
1124
|
-
maxClockSkewSec,
|
|
1125
|
-
...keyFlags,
|
|
1126
|
-
signatureValid,
|
|
1127
|
-
rateLimitOk,
|
|
1128
|
-
rateLimitRetryAfterSec,
|
|
1129
|
-
nonceOk,
|
|
1130
|
-
...policyFlags
|
|
1131
|
-
});
|
|
1132
|
-
const s = pipeline.stage;
|
|
1133
|
-
result.checks.schema = s !== 0;
|
|
1134
|
-
if (s >= 1) result.checks.timestamp = s !== 1;
|
|
1135
|
-
if (s >= 2) result.checks.keyId = s !== 2;
|
|
1136
|
-
if (s >= 2) result.checks.signature = s !== 2 && s !== 3;
|
|
1137
|
-
if (s >= 4) result.checks.rateLimit = s !== 4;
|
|
1138
|
-
if (s >= 5) result.checks.nonce = s !== 5;
|
|
1139
|
-
if (s >= 6 || pipeline.ok) result.checks.policy = s !== 6;
|
|
1140
|
-
if (!pipeline.ok) {
|
|
1141
|
-
const stage = pipeline.stageLabel;
|
|
1142
|
-
if (stage === "schema") {
|
|
1143
|
-
result.errors.push({ type: "SCHEMA_ERROR", message: pipeline.schemaError || "Schema invalid" });
|
|
1144
|
-
} else if (stage === "timestamp") {
|
|
1145
|
-
result.errors.push({ type: "TIMESTAMP_SKEW_EXCEEDED", message: `Command timestamp skew ${pipeline.skewSec}s exceeds allowed ${maxClockSkewSec}s` });
|
|
1146
|
-
} else if (stage === "key") {
|
|
1147
|
-
const reason = pipeline.keyReason || "KEY_ERROR";
|
|
1148
|
-
const msgs = {
|
|
1149
|
-
KEY_ID_INVALID: `Invalid keyId '${keyId}'`,
|
|
1150
|
-
KEY_NOT_TRUSTED: `Key '${keyId}' is not in trusted key store`,
|
|
1151
|
-
KEY_DEPRECATED: `Key '${keyId}' is deprecated`,
|
|
1152
|
-
KEY_REQUESTER_MISMATCH: `Key '${keyId}' is not authorized for requester '${commandObj?.requesterId}'`,
|
|
1153
|
-
KEY_LIFECYCLE_INVALID: `Key '${keyId}' must define notBefore and expiresAt`,
|
|
1154
|
-
KEY_NOT_YET_VALID: `Key '${keyId}' is not yet valid`,
|
|
1155
|
-
KEY_EXPIRED: `Key '${keyId}' has expired`
|
|
1156
|
-
};
|
|
1157
|
-
result.errors.push({ type: reason, message: msgs[reason] || reason });
|
|
1158
|
-
} else if (stage === "signature") {
|
|
1159
|
-
result.errors.push({ type: "SIGNATURE_INVALID", message: effectivePubKey ? "Signature verification failed" : "No public key available" });
|
|
1160
|
-
} else if (stage === "rate_limit") {
|
|
1161
|
-
result.errors.push({ type: "RATE_LIMIT_EXCEEDED", message: `Rate limit exceeded. Retry after ${pipeline.retryAfterSec}s` });
|
|
1162
|
-
} else if (stage === "nonce") {
|
|
1163
|
-
result.errors.push({ type: "REPLAY_NONCE", message: "Nonce has already been used" });
|
|
1164
|
-
} else if (stage === "policy" && pipeline.policyResult) {
|
|
1165
|
-
result.errors.push({ type: pipeline.policyResult.reason, message: pipeline.policyResult.message });
|
|
1166
|
-
} else {
|
|
1167
|
-
result.errors.push({ type: "VALIDATION_FAILED", message: `Failed at stage: ${stage}` });
|
|
1168
|
-
}
|
|
1169
|
-
return result;
|
|
1170
|
-
}
|
|
1171
|
-
result.valid = true;
|
|
1172
|
-
result.risk = classifyRisk(commandObj.id, commandObj.payload?.cmd === "rm");
|
|
1173
|
-
result.message = "Command validation successful";
|
|
1174
|
-
return result;
|
|
1175
|
-
}
|
|
1176
|
-
var init_validator = __esm({
|
|
1177
|
-
"src/core/validator.js"() {
|
|
1178
|
-
init_signature();
|
|
1179
|
-
init_policyVersionGuard();
|
|
1180
|
-
init_engine();
|
|
1181
|
-
}
|
|
1182
|
-
});
|
|
1183
|
-
|
|
1184
|
-
// src/adapters/noopAdapter.js
|
|
1185
|
-
async function noopAdapter(cmd2) {
|
|
1186
|
-
return {
|
|
1187
|
-
adapter: "noop",
|
|
1188
|
-
commandId: cmd2.commandId || "unknown",
|
|
1189
|
-
command: cmd2.id || "unknown",
|
|
1190
|
-
status: "completed",
|
|
1191
|
-
output: `[NOOP] Would execute: ${cmd2.id || "unknown"} on adapter: ${cmd2.payload?.adapter || "unknown"}`,
|
|
1192
|
-
exitCode: 0,
|
|
1193
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1194
|
-
};
|
|
1195
|
-
}
|
|
1196
|
-
var init_noopAdapter = __esm({
|
|
1197
|
-
"src/adapters/noopAdapter.js"() {
|
|
1198
|
-
}
|
|
1199
|
-
});
|
|
1200
|
-
|
|
1201
|
-
// src/adapters/shellAdapter.js
|
|
1202
|
-
import { spawnSync } from "child_process";
|
|
1203
|
-
import path11 from "path";
|
|
1204
|
-
import fs10 from "fs";
|
|
1205
|
-
function physicalPath(candidate) {
|
|
1206
|
-
try {
|
|
1207
|
-
return fs10.realpathSync(path11.resolve(candidate));
|
|
1208
|
-
} catch {
|
|
1209
|
-
return path11.resolve(candidate);
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
function normalizeArgs(args) {
|
|
1213
|
-
if (args === void 0) return { ok: true, args: [] };
|
|
1214
|
-
if (!Array.isArray(args)) {
|
|
1215
|
-
return { ok: false, error: "payload.args must be an array" };
|
|
1216
|
-
}
|
|
1217
|
-
const normalized = [];
|
|
1218
|
-
for (const arg of args) {
|
|
1219
|
-
if (typeof arg !== "string" && typeof arg !== "number" && typeof arg !== "boolean") {
|
|
1220
|
-
return { ok: false, error: "payload.args may only contain string, number, or boolean values" };
|
|
1221
|
-
}
|
|
1222
|
-
normalized.push(String(arg));
|
|
1223
|
-
}
|
|
1224
|
-
return { ok: true, args: normalized };
|
|
1225
|
-
}
|
|
1226
|
-
async function shellAdapter(cmd2, policy, requester) {
|
|
1227
|
-
const payload = cmd2.payload;
|
|
1228
|
-
const timeout = Math.min(Math.max(Number(payload.timeoutMs) || 3e4, 1), 3e4);
|
|
1229
|
-
const maxOutputSize = Math.min(Math.max(Number(payload.maxOutputBytes) || 1024 * 1024, 1024), 1024 * 1024);
|
|
1230
|
-
if (payload.adapter !== "shell") {
|
|
1231
|
-
return {
|
|
1232
|
-
adapter: "shell",
|
|
1233
|
-
commandId: cmd2.commandId,
|
|
1234
|
-
status: "error",
|
|
1235
|
-
error: "Adapter mismatch",
|
|
1236
|
-
exitCode: 1
|
|
1237
|
-
};
|
|
1238
|
-
}
|
|
1239
|
-
const allowedCmds = requester?.exec?.allowCmds || [];
|
|
1240
|
-
const deniedCmds = requester?.exec?.denyCmds || [];
|
|
1241
|
-
if (deniedCmds.includes(payload.cmd)) {
|
|
1242
|
-
return {
|
|
1243
|
-
adapter: "shell",
|
|
1244
|
-
commandId: cmd2.commandId,
|
|
1245
|
-
status: "blocked",
|
|
1246
|
-
error: `Command '${payload.cmd}' is denied`,
|
|
1247
|
-
exitCode: 2
|
|
1248
|
-
};
|
|
1249
|
-
}
|
|
1250
|
-
if (allowedCmds.length > 0 && !allowedCmds.includes(payload.cmd)) {
|
|
1251
|
-
return {
|
|
1252
|
-
adapter: "shell",
|
|
1253
|
-
commandId: cmd2.commandId,
|
|
1254
|
-
status: "blocked",
|
|
1255
|
-
error: `Command '${payload.cmd}' not in allowlist`,
|
|
1256
|
-
exitCode: 2
|
|
1257
|
-
};
|
|
1258
|
-
}
|
|
1259
|
-
const roots = requester?.filesystem?.roots || [];
|
|
1260
|
-
const cwdAllow = roots.some((r) => {
|
|
1261
|
-
const resolvedRoot = physicalPath(r);
|
|
1262
|
-
const norm = physicalPath(payload.cwd);
|
|
1263
|
-
return norm === resolvedRoot || norm.startsWith(resolvedRoot + path11.sep);
|
|
1264
|
-
});
|
|
1265
|
-
if (!cwdAllow) {
|
|
1266
|
-
return {
|
|
1267
|
-
adapter: "shell",
|
|
1268
|
-
commandId: cmd2.commandId,
|
|
1269
|
-
status: "blocked",
|
|
1270
|
-
error: `CWD '${payload.cwd}' not authorized`,
|
|
1271
|
-
exitCode: 2
|
|
1272
|
-
};
|
|
1273
|
-
}
|
|
1274
|
-
const argCheck = normalizeArgs(payload.args);
|
|
1275
|
-
if (!argCheck.ok) {
|
|
1276
|
-
return {
|
|
1277
|
-
adapter: "shell",
|
|
1278
|
-
commandId: cmd2.commandId,
|
|
1279
|
-
status: "blocked",
|
|
1280
|
-
error: argCheck.error,
|
|
1281
|
-
exitCode: 2
|
|
1282
|
-
};
|
|
1283
|
-
}
|
|
1284
|
-
try {
|
|
1285
|
-
const result = spawnSync(payload.cmd, argCheck.args, {
|
|
1286
|
-
cwd: payload.cwd,
|
|
1287
|
-
timeout,
|
|
1288
|
-
encoding: "utf8",
|
|
1289
|
-
maxBuffer: maxOutputSize,
|
|
1290
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1291
|
-
shell: false
|
|
1292
|
-
});
|
|
1293
|
-
if (result.error) {
|
|
1294
|
-
throw result.error;
|
|
1295
|
-
}
|
|
1296
|
-
const output = `${result.stdout || ""}${result.stderr || ""}`;
|
|
1297
|
-
const exitCode = result.status ?? 1;
|
|
1298
|
-
if (exitCode !== 0) {
|
|
1299
|
-
return {
|
|
1300
|
-
adapter: "shell",
|
|
1301
|
-
commandId: cmd2.commandId,
|
|
1302
|
-
command: payload.cmd,
|
|
1303
|
-
status: "error",
|
|
1304
|
-
error: output.substring(0, maxOutputSize) || `Command exited with code ${exitCode}`,
|
|
1305
|
-
exitCode,
|
|
1306
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1307
|
-
};
|
|
1308
|
-
}
|
|
1309
|
-
return {
|
|
1310
|
-
adapter: "shell",
|
|
1311
|
-
commandId: cmd2.commandId,
|
|
1312
|
-
command: payload.cmd,
|
|
1313
|
-
status: "completed",
|
|
1314
|
-
output: output.substring(0, maxOutputSize),
|
|
1315
|
-
exitCode: 0,
|
|
1316
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1317
|
-
};
|
|
1318
|
-
} catch (err) {
|
|
1319
|
-
return {
|
|
1320
|
-
adapter: "shell",
|
|
1321
|
-
commandId: cmd2.commandId,
|
|
1322
|
-
command: payload.cmd,
|
|
1323
|
-
status: "error",
|
|
1324
|
-
error: err.message,
|
|
1325
|
-
exitCode: err.status || 1,
|
|
1326
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1327
|
-
};
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
var init_shellAdapter = __esm({
|
|
1331
|
-
"src/adapters/shellAdapter.js"() {
|
|
1332
|
-
}
|
|
1333
|
-
});
|
|
1334
|
-
|
|
1335
|
-
// src/core/backup.js
|
|
1336
|
-
import fs11 from "fs";
|
|
1337
|
-
import path12 from "path";
|
|
1338
|
-
import crypto4 from "crypto";
|
|
1339
|
-
function createBackup(filePath, backupDir) {
|
|
1340
|
-
const dir = backupDir || path12.resolve(".lbe/data/backups");
|
|
1341
|
-
if (!fs11.existsSync(dir)) {
|
|
1342
|
-
fs11.mkdirSync(dir, { recursive: true });
|
|
1343
|
-
}
|
|
1344
|
-
const target = path12.resolve(filePath);
|
|
1345
|
-
const existed = fs11.existsSync(target);
|
|
1346
|
-
let content = null;
|
|
1347
|
-
let hash = null;
|
|
1348
|
-
if (existed) {
|
|
1349
|
-
content = fs11.readFileSync(target);
|
|
1350
|
-
hash = crypto4.createHash("sha256").update(content).digest("hex");
|
|
1351
|
-
}
|
|
1352
|
-
const basename = path12.basename(target).replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
1353
|
-
const backupName = `${Date.now()}-${hash ? hash.slice(0, 8) : "new"}-${basename}`;
|
|
1354
|
-
const backupPath = existed ? path12.join(dir, backupName) : null;
|
|
1355
|
-
if (existed && content !== null) {
|
|
1356
|
-
atomicWriteFileSync(backupPath, content);
|
|
1357
|
-
}
|
|
1358
|
-
return {
|
|
1359
|
-
originalPath: target,
|
|
1360
|
-
backupPath,
|
|
1361
|
-
existed,
|
|
1362
|
-
hash,
|
|
1363
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1364
|
-
};
|
|
1365
|
-
}
|
|
1366
|
-
function restoreBackup(backupMeta) {
|
|
1367
|
-
if (!backupMeta) return { restored: false, error: "No backup metadata" };
|
|
1368
|
-
const { originalPath, backupPath, existed } = backupMeta;
|
|
1369
|
-
if (!existed) {
|
|
1370
|
-
try {
|
|
1371
|
-
if (fs11.existsSync(originalPath)) fs11.unlinkSync(originalPath);
|
|
1372
|
-
return { restored: true, action: "deleted" };
|
|
1373
|
-
} catch (e) {
|
|
1374
|
-
return { restored: false, error: e.message };
|
|
1375
|
-
}
|
|
1376
|
-
}
|
|
1377
|
-
if (!backupPath || !fs11.existsSync(backupPath)) {
|
|
1378
|
-
return { restored: false, error: "Backup file not found at: " + backupPath };
|
|
1379
|
-
}
|
|
1380
|
-
try {
|
|
1381
|
-
const content = fs11.readFileSync(backupPath);
|
|
1382
|
-
atomicWriteFileSync(originalPath, content);
|
|
1383
|
-
return { restored: true, action: "restored" };
|
|
1384
|
-
} catch (e) {
|
|
1385
|
-
return { restored: false, error: e.message };
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
var init_backup = __esm({
|
|
1389
|
-
"src/core/backup.js"() {
|
|
1390
|
-
init_atomicWrite();
|
|
1391
|
-
}
|
|
1392
|
-
});
|
|
1393
|
-
|
|
1394
|
-
// src/adapters/fileAdapter.js
|
|
1395
|
-
import fs12 from "fs";
|
|
1396
|
-
import path13 from "path";
|
|
1397
|
-
function resolvedTarget(target, cwd) {
|
|
1398
|
-
if (!target) return null;
|
|
1399
|
-
return path13.isAbsolute(target) ? path13.resolve(target) : path13.resolve(cwd || process.cwd(), target);
|
|
1400
|
-
}
|
|
1401
|
-
function isUnderRoot(targetPath, roots) {
|
|
1402
|
-
const norm = resolvePhysicalPath(targetPath);
|
|
1403
|
-
return roots.some((r) => {
|
|
1404
|
-
const root = resolvePhysicalPath(r);
|
|
1405
|
-
return norm === root || norm.startsWith(root + path13.sep);
|
|
1406
|
-
});
|
|
1407
|
-
}
|
|
1408
|
-
function resolvePhysicalPath(candidate) {
|
|
1409
|
-
let current = path13.resolve(candidate);
|
|
1410
|
-
const suffix = [];
|
|
1411
|
-
while (!fs12.existsSync(current)) {
|
|
1412
|
-
const parent = path13.dirname(current);
|
|
1413
|
-
if (parent === current) break;
|
|
1414
|
-
suffix.unshift(path13.basename(current));
|
|
1415
|
-
current = parent;
|
|
1416
|
-
}
|
|
1417
|
-
try {
|
|
1418
|
-
current = fs12.realpathSync(current);
|
|
1419
|
-
} catch {
|
|
1420
|
-
}
|
|
1421
|
-
return path13.join(current, ...suffix);
|
|
1422
|
-
}
|
|
1423
|
-
function matchesDenyPattern(str, patterns) {
|
|
1424
|
-
for (const pattern of patterns || []) {
|
|
1425
|
-
const rx = new RegExp(
|
|
1426
|
-
"^" + pattern.replace(/\./g, "\\.").replace(/\*\*/g, ".*").replace(/\*/g, "[^/\\\\]*") + "$"
|
|
1427
|
-
);
|
|
1428
|
-
if (rx.test(str)) return pattern;
|
|
1429
|
-
}
|
|
1430
|
-
return null;
|
|
1431
|
-
}
|
|
1432
|
-
function blocked(cmd2, code, message, exitCode = 2) {
|
|
1433
|
-
return {
|
|
1434
|
-
adapter: "file",
|
|
1435
|
-
commandId: cmd2.commandId,
|
|
1436
|
-
status: "blocked",
|
|
1437
|
-
errorCode: code,
|
|
1438
|
-
error: message,
|
|
1439
|
-
exitCode
|
|
1440
|
-
};
|
|
1441
|
-
}
|
|
1442
|
-
function fail(cmd2, code, message, backup = null, exitCode = 1) {
|
|
1443
|
-
return {
|
|
1444
|
-
adapter: "file",
|
|
1445
|
-
commandId: cmd2.commandId,
|
|
1446
|
-
status: "error",
|
|
1447
|
-
errorCode: code,
|
|
1448
|
-
error: message,
|
|
1449
|
-
backup: backup ? summariseBackup(backup) : null,
|
|
1450
|
-
exitCode
|
|
1451
|
-
};
|
|
1452
|
-
}
|
|
1453
|
-
function summariseBackup(b) {
|
|
1454
|
-
return b ? { path: b.backupPath, existed: b.existed, hash: b.hash, createdAt: b.createdAt } : null;
|
|
1455
|
-
}
|
|
1456
|
-
async function fileAdapter(cmd2, policy, requester) {
|
|
1457
|
-
const payload = cmd2.payload;
|
|
1458
|
-
const action = payload.action;
|
|
1459
|
-
const cwd = payload.cwd || process.cwd();
|
|
1460
|
-
const target = resolvedTarget(payload.target, cwd);
|
|
1461
|
-
if (!action) return blocked(cmd2, "FILE_NO_ACTION", "payload.action is required");
|
|
1462
|
-
if (!target && action !== "noop") return blocked(cmd2, "FILE_NO_TARGET", "payload.target is required");
|
|
1463
|
-
const roots = requester?.filesystem?.roots || [];
|
|
1464
|
-
if (roots.length === 0) return blocked(cmd2, "FILE_NO_ROOTS", "No filesystem roots defined for requester");
|
|
1465
|
-
if (!isUnderRoot(target, roots)) return blocked(cmd2, "FILE_OUTSIDE_ROOT", `'${target}' is outside allowed roots`);
|
|
1466
|
-
const denied = matchesDenyPattern(target, requester?.filesystem?.denyPatterns);
|
|
1467
|
-
if (denied) return blocked(cmd2, "FILE_PATH_DENIED", `'${target}' matches deny pattern: ${denied}`);
|
|
1468
|
-
switch (action) {
|
|
1469
|
-
case "read":
|
|
1470
|
-
return doRead(cmd2, target);
|
|
1471
|
-
case "write":
|
|
1472
|
-
return doWrite(cmd2, target, payload);
|
|
1473
|
-
case "patch":
|
|
1474
|
-
return doPatch(cmd2, target, payload);
|
|
1475
|
-
case "delete":
|
|
1476
|
-
return doDelete(cmd2, target);
|
|
1477
|
-
default:
|
|
1478
|
-
return blocked(cmd2, "FILE_UNKNOWN_ACTION", `Unknown action: '${action}'`);
|
|
1479
|
-
}
|
|
1480
|
-
}
|
|
1481
|
-
function doRead(cmd2, target) {
|
|
1482
|
-
if (!fs12.existsSync(target)) return fail(cmd2, "FILE_NOT_FOUND", `Not found: ${target}`);
|
|
1483
|
-
try {
|
|
1484
|
-
const stat = fs12.statSync(target);
|
|
1485
|
-
if (stat.size > MAX_READ_BYTES) return fail(cmd2, "FILE_TOO_LARGE", "File exceeds 10 MB read limit");
|
|
1486
|
-
const content = fs12.readFileSync(target, "utf8");
|
|
1487
|
-
return {
|
|
1488
|
-
adapter: "file",
|
|
1489
|
-
action: "read",
|
|
1490
|
-
commandId: cmd2.commandId,
|
|
1491
|
-
status: "completed",
|
|
1492
|
-
target,
|
|
1493
|
-
output: content,
|
|
1494
|
-
bytesRead: stat.size,
|
|
1495
|
-
exitCode: 0
|
|
1496
|
-
};
|
|
1497
|
-
} catch (e) {
|
|
1498
|
-
return fail(cmd2, "FILE_READ_ERROR", e.message);
|
|
1499
|
-
}
|
|
1500
|
-
}
|
|
1501
|
-
function doWrite(cmd2, target, payload) {
|
|
1502
|
-
const content = payload.content;
|
|
1503
|
-
if (content === void 0 || content === null) {
|
|
1504
|
-
return fail(cmd2, "FILE_MISSING_CONTENT", "payload.content is required for write");
|
|
1505
|
-
}
|
|
1506
|
-
const backup = tryBackup(target);
|
|
1507
|
-
try {
|
|
1508
|
-
atomicWriteFileSync(target, content, { encoding: "utf8" });
|
|
1509
|
-
return {
|
|
1510
|
-
adapter: "file",
|
|
1511
|
-
action: "write",
|
|
1512
|
-
commandId: cmd2.commandId,
|
|
1513
|
-
status: "completed",
|
|
1514
|
-
target,
|
|
1515
|
-
backup: summariseBackup(backup),
|
|
1516
|
-
output: `Wrote ${Buffer.byteLength(content, "utf8")} bytes to ${target}`,
|
|
1517
|
-
exitCode: 0
|
|
1518
|
-
};
|
|
1519
|
-
} catch (e) {
|
|
1520
|
-
restoreBackup(backup);
|
|
1521
|
-
return fail(cmd2, "FILE_WRITE_ERROR", e.message, backup);
|
|
1522
|
-
}
|
|
1523
|
-
}
|
|
1524
|
-
function doPatch(cmd2, target, payload) {
|
|
1525
|
-
const content = payload.content;
|
|
1526
|
-
if (content === void 0 || content === null) {
|
|
1527
|
-
return fail(cmd2, "FILE_MISSING_CONTENT", "payload.content is required for patch");
|
|
1528
|
-
}
|
|
1529
|
-
const backup = tryBackup(target);
|
|
1530
|
-
try {
|
|
1531
|
-
atomicWriteFileSync(target, content, { encoding: "utf8" });
|
|
1532
|
-
return {
|
|
1533
|
-
adapter: "file",
|
|
1534
|
-
action: "patch",
|
|
1535
|
-
commandId: cmd2.commandId,
|
|
1536
|
-
status: "completed",
|
|
1537
|
-
target,
|
|
1538
|
-
backup: summariseBackup(backup),
|
|
1539
|
-
output: `Patched ${target} (${Buffer.byteLength(content, "utf8")} bytes)`,
|
|
1540
|
-
exitCode: 0
|
|
1541
|
-
};
|
|
1542
|
-
} catch (e) {
|
|
1543
|
-
restoreBackup(backup);
|
|
1544
|
-
return fail(cmd2, "FILE_PATCH_ERROR", e.message, backup);
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
function doDelete(cmd2, target) {
|
|
1548
|
-
if (!fs12.existsSync(target)) return fail(cmd2, "FILE_NOT_FOUND", `Not found: ${target}`);
|
|
1549
|
-
const backup = tryBackup(target);
|
|
1550
|
-
try {
|
|
1551
|
-
fs12.unlinkSync(target);
|
|
1552
|
-
return {
|
|
1553
|
-
adapter: "file",
|
|
1554
|
-
action: "delete",
|
|
1555
|
-
commandId: cmd2.commandId,
|
|
1556
|
-
status: "completed",
|
|
1557
|
-
target,
|
|
1558
|
-
backup: summariseBackup(backup),
|
|
1559
|
-
output: `Deleted ${target}`,
|
|
1560
|
-
exitCode: 0
|
|
1561
|
-
};
|
|
1562
|
-
} catch (e) {
|
|
1563
|
-
restoreBackup(backup);
|
|
1564
|
-
return fail(cmd2, "FILE_DELETE_ERROR", e.message, backup);
|
|
1565
|
-
}
|
|
1566
|
-
}
|
|
1567
|
-
function tryBackup(target) {
|
|
1568
|
-
try {
|
|
1569
|
-
return createBackup(target);
|
|
1570
|
-
} catch {
|
|
1571
|
-
return null;
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
var MAX_READ_BYTES;
|
|
1575
|
-
var init_fileAdapter = __esm({
|
|
1576
|
-
"src/adapters/fileAdapter.js"() {
|
|
1577
|
-
init_atomicWrite();
|
|
1578
|
-
init_backup();
|
|
1579
|
-
MAX_READ_BYTES = 10 * 1024 * 1024;
|
|
1580
|
-
}
|
|
1581
|
-
});
|
|
1582
|
-
|
|
1583
|
-
// src/adapters/index.js
|
|
1584
|
-
function getAdapter(name) {
|
|
1585
|
-
return ADAPTERS[name];
|
|
1586
|
-
}
|
|
1587
|
-
async function executeAdapter(adapterName, cmd2, policy, requester) {
|
|
1588
|
-
const adapter = getAdapter(adapterName);
|
|
1589
|
-
if (!adapter) {
|
|
1590
|
-
return {
|
|
1591
|
-
adapter: adapterName,
|
|
1592
|
-
commandId: cmd2.commandId,
|
|
1593
|
-
status: "error",
|
|
1594
|
-
error: `Adapter '${adapterName}' not found`,
|
|
1595
|
-
exitCode: 1
|
|
1596
|
-
};
|
|
1597
|
-
}
|
|
1598
|
-
try {
|
|
1599
|
-
return await adapter(cmd2, policy, requester);
|
|
1600
|
-
} catch (err) {
|
|
1601
|
-
return {
|
|
1602
|
-
adapter: adapterName,
|
|
1603
|
-
commandId: cmd2.commandId,
|
|
1604
|
-
status: "error",
|
|
1605
|
-
error: `Adapter execution failed: ${err.message}`,
|
|
1606
|
-
exitCode: 9
|
|
1607
|
-
};
|
|
1608
|
-
}
|
|
1609
|
-
}
|
|
1610
|
-
var ADAPTERS, AVAILABLE_ADAPTERS;
|
|
1611
|
-
var init_adapters = __esm({
|
|
1612
|
-
"src/adapters/index.js"() {
|
|
1613
|
-
init_noopAdapter();
|
|
1614
|
-
init_shellAdapter();
|
|
1615
|
-
init_fileAdapter();
|
|
1616
|
-
ADAPTERS = {
|
|
1617
|
-
noop: noopAdapter,
|
|
1618
|
-
shell: shellAdapter,
|
|
1619
|
-
file: fileAdapter
|
|
1620
|
-
};
|
|
1621
|
-
AVAILABLE_ADAPTERS = Object.keys(ADAPTERS);
|
|
1622
|
-
}
|
|
1623
|
-
});
|
|
1624
|
-
|
|
1625
|
-
// src/exec/localExecutor.js
|
|
1626
|
-
var localExecutor_exports = {};
|
|
1627
|
-
__export(localExecutor_exports, {
|
|
1628
|
-
createLocalExecutor: () => createLocalExecutor
|
|
1629
|
-
});
|
|
1630
|
-
import crypto5 from "crypto";
|
|
1631
|
-
import fs13 from "fs";
|
|
1632
|
-
import path14 from "path";
|
|
1633
|
-
function error(code, message, recoverable = false) {
|
|
1634
|
-
return { ok: false, decision: "deny", executed: false, dryRun: false, error: { code, message, recoverable } };
|
|
1635
|
-
}
|
|
1636
|
-
function commandPolicy(rootDir, actor, shell = {}) {
|
|
1637
|
-
const now = /* @__PURE__ */ new Date();
|
|
1638
|
-
const expires = new Date(now.getTime() + 365 * 24 * 60 * 60 * 1e3);
|
|
1639
|
-
return {
|
|
1640
|
-
version: 1,
|
|
1641
|
-
default: "DENY",
|
|
1642
|
-
requesters: {
|
|
1643
|
-
[actor]: {
|
|
1644
|
-
allowCommands: Object.values(INTENTS).map((item) => item.id),
|
|
1645
|
-
allowAdapters: ["file", "shell"],
|
|
1646
|
-
filesystem: { roots: [rootDir], denyPatterns: [] },
|
|
1647
|
-
exec: { allowCmds: shell.allowCommands || [], denyCmds: shell.denyCommands || [] },
|
|
1648
|
-
rateLimit: { windowSec: 60, maxRequests: shell.maxRequests || 60 }
|
|
1649
|
-
}
|
|
1650
|
-
},
|
|
1651
|
-
security: { maxClockSkewSec: 600, defaultRateLimit: { windowSec: 60, maxRequests: 60 } },
|
|
1652
|
-
_keyWindow: { notBefore: now.toISOString(), expiresAt: expires.toISOString() }
|
|
1653
|
-
};
|
|
1654
|
-
}
|
|
1655
|
-
function physicalPath2(candidate) {
|
|
1656
|
-
let current = path14.resolve(candidate);
|
|
1657
|
-
const suffix = [];
|
|
1658
|
-
while (!fs13.existsSync(current)) {
|
|
1659
|
-
const parent = path14.dirname(current);
|
|
1660
|
-
if (parent === current) break;
|
|
1661
|
-
suffix.unshift(path14.basename(current));
|
|
1662
|
-
current = parent;
|
|
1663
|
-
}
|
|
1664
|
-
try {
|
|
1665
|
-
current = fs13.realpathSync(current);
|
|
1666
|
-
} catch {
|
|
1667
|
-
}
|
|
1668
|
-
return path14.join(current, ...suffix);
|
|
1669
|
-
}
|
|
1670
|
-
function underRoot(candidate, root) {
|
|
1671
|
-
const target = physicalPath2(candidate);
|
|
1672
|
-
const resolvedRoot = physicalPath2(root);
|
|
1673
|
-
return target === resolvedRoot || target.startsWith(resolvedRoot + path14.sep);
|
|
1674
|
-
}
|
|
1675
|
-
function scanContent(value, fieldName) {
|
|
1676
|
-
if (typeof value !== "string") return null;
|
|
1677
|
-
for (const pattern of FORBIDDEN_CONTENT) {
|
|
1678
|
-
if (pattern.test(value)) {
|
|
1679
|
-
return error("PAYLOAD_CONTENT_REJECTED", `Forbidden pattern in ${fieldName}: ${pattern}`);
|
|
1680
|
-
}
|
|
1681
|
-
}
|
|
1682
|
-
return null;
|
|
1683
|
-
}
|
|
1684
|
-
function normalize(rootDir, request, shell = {}) {
|
|
1685
|
-
if (!request || typeof request !== "object") return { error: error("REQUEST_INVALID", "request must be an object") };
|
|
1686
|
-
const detail = INTENTS[request.intent];
|
|
1687
|
-
if (!detail) return { error: error("INTENT_UNSUPPORTED", `Unsupported intent '${request.intent}'`) };
|
|
1688
|
-
const actor = typeof request.actor === "string" && request.actor ? request.actor : "agent:local";
|
|
1689
|
-
let target = null;
|
|
1690
|
-
if (detail.adapter === "file") {
|
|
1691
|
-
if (typeof request.target !== "string" || !request.target) return { error: error("TARGET_REQUIRED", "target is required for file intents") };
|
|
1692
|
-
target = path14.resolve(rootDir, request.target);
|
|
1693
|
-
if (!underRoot(target, rootDir)) return { error: error("PATH_OUTSIDE_ROOT", "target is outside project root") };
|
|
1694
|
-
if (["write_file", "patch_file"].includes(request.intent) && typeof request.content !== "string") {
|
|
1695
|
-
return { error: error("CONTENT_REQUIRED", "content is required for write and patch") };
|
|
1696
|
-
}
|
|
1697
|
-
const contentScan = scanContent(request.content, "content");
|
|
1698
|
-
if (contentScan) return { error: contentScan };
|
|
1699
|
-
}
|
|
1700
|
-
let command = null;
|
|
1701
|
-
if (detail.adapter === "shell") {
|
|
1702
|
-
command = request.command;
|
|
1703
|
-
if (!command || typeof command.cmd !== "string" || !Array.isArray(command.args) || command.args.some((arg) => typeof arg !== "string")) {
|
|
1704
|
-
return { error: error("COMMAND_INVALID", "command requires cmd and string args") };
|
|
1705
|
-
}
|
|
1706
|
-
const cwd = path14.resolve(rootDir, command.cwd || ".");
|
|
1707
|
-
if (!underRoot(cwd, rootDir)) return { error: error("CWD_OUTSIDE_ROOT", "command cwd is outside project root") };
|
|
1708
|
-
if (!Array.isArray(shell.allowCommands) || !shell.allowCommands.includes(command.cmd)) {
|
|
1709
|
-
return { error: error("SHELL_NOT_ALLOWLISTED", `command '${command.cmd}' is not explicitly allowlisted`) };
|
|
1710
|
-
}
|
|
1711
|
-
if (shell.denyCommands?.includes(command.cmd)) return { error: error("SHELL_DENIED", `command '${command.cmd}' is denied`) };
|
|
1712
|
-
command = { ...command, cwd, timeoutMs: Math.min(Math.max(command.timeoutMs || 3e4, 1), 3e4), maxOutputBytes: Math.min(Math.max(command.maxOutputBytes || 1024 * 1024, 1024), 1024 * 1024) };
|
|
1713
|
-
}
|
|
1714
|
-
return { actor, detail, target, command, request };
|
|
1715
|
-
}
|
|
1716
|
-
function envelope(normalized, keyId, secretKey) {
|
|
1717
|
-
const { actor, detail, target, command, request } = normalized;
|
|
1718
|
-
const body = {
|
|
1719
|
-
id: detail.id,
|
|
1720
|
-
risk: MUTATIONS.has(request.intent) ? "MEDIUM" : "LOW",
|
|
1721
|
-
commandId: crypto5.randomUUID(),
|
|
1722
|
-
requesterId: actor,
|
|
1723
|
-
sessionId: "local-host",
|
|
1724
|
-
timestamp: Math.floor(Date.now() / 1e3),
|
|
1725
|
-
nonce: crypto5.randomBytes(32).toString("hex"),
|
|
1726
|
-
requires: ["policy", "signature"],
|
|
1727
|
-
payload: {
|
|
1728
|
-
adapter: detail.adapter,
|
|
1729
|
-
action: detail.action,
|
|
1730
|
-
target,
|
|
1731
|
-
content: request.content,
|
|
1732
|
-
cmd: command?.cmd,
|
|
1733
|
-
args: command?.args,
|
|
1734
|
-
timeoutMs: command?.timeoutMs,
|
|
1735
|
-
maxOutputBytes: command?.maxOutputBytes,
|
|
1736
|
-
cwd: command?.cwd || (target ? path14.dirname(target) : process.cwd())
|
|
1737
|
-
}
|
|
1738
|
-
};
|
|
1739
|
-
const signed = signEd25519({ payloadObj: body, secretKeyB64: secretKey });
|
|
1740
|
-
if (signed.error) throw new Error(signed.error);
|
|
1741
|
-
return { ...body, signature: { alg: "ed25519", keyId, sig: signed.signature } };
|
|
1742
|
-
}
|
|
1743
|
-
function createLocalExecutor(options = {}) {
|
|
1744
|
-
const rootDir = path14.resolve(options.rootDir || process.cwd());
|
|
1745
|
-
const keyId = options.keyId || "host:local-exec";
|
|
1746
|
-
const keyPair = options.keyPair || generateKeyPair();
|
|
1747
|
-
const shell = options.shell || {};
|
|
1748
|
-
function prepare(request, { recordNonce = false } = {}) {
|
|
1749
|
-
const normalized = normalize(rootDir, request, shell);
|
|
1750
|
-
if (normalized.error) return normalized;
|
|
1751
|
-
const local = loadLocalPolicy(rootDir, options.mode || "enforce");
|
|
1752
|
-
const localDecision = evaluateLocalPolicy(local.policy, rootDir, { target: normalized.target, command: normalized.command?.cmd });
|
|
1753
|
-
const localBlocked = local.policy.mode === "enforce" && !localDecision.allowed;
|
|
1754
|
-
if (localBlocked) return { error: error("LOCAL_POLICY_DENY", `Blocked by rule(s): ${localDecision.winningRules.map((rule) => rule.id).join(", ")}`), local, localDecision, normalized };
|
|
1755
|
-
const policy = commandPolicy(rootDir, normalized.actor, shell);
|
|
1756
|
-
const keyStore = { defaultKeyId: keyId, trustedKeys: { [keyId]: { publicKey: keyPair.publicKey, notBefore: policy._keyWindow.notBefore, expiresAt: policy._keyWindow.expiresAt, deprecated: false } } };
|
|
1757
|
-
delete policy._keyWindow;
|
|
1758
|
-
const proposal = envelope(normalized, keyId, keyPair.secretKey);
|
|
1759
|
-
const nonceDb = { entries: [] };
|
|
1760
|
-
const validation = validateCommand({ commandObj: proposal, keyStore, nonceDb: recordNonce ? nonceDb : { entries: [] }, policy });
|
|
1761
|
-
if (!validation.valid) return { error: error(validation.errors[0]?.type || "VALIDATION_FAILED", validation.errors[0]?.message || "Validation failed"), local, localDecision, normalized, proposal, policy, validation };
|
|
1762
|
-
return { local, localDecision, normalized, proposal, policy, validation };
|
|
1763
|
-
}
|
|
1764
|
-
function evaluateSync(action) {
|
|
1765
|
-
const local = loadLocalPolicy(rootDir, options.mode || "observe");
|
|
1766
|
-
const mode = local.policy.mode;
|
|
1767
|
-
let target = null;
|
|
1768
|
-
let command = null;
|
|
1769
|
-
if (action.path) {
|
|
1770
|
-
try {
|
|
1771
|
-
target = path14.resolve(rootDir, action.path);
|
|
1772
|
-
if (!underRoot(target, rootDir)) {
|
|
1773
|
-
return { decision: "deny", deny: true, matchedRules: ["path:outside_root"], mode, enforced: mode === "enforce", reason: "PATH_OUTSIDE_ROOT" };
|
|
1774
|
-
}
|
|
1775
|
-
} catch (e) {
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1778
|
-
if (action.cmd) command = action.cmd;
|
|
1779
|
-
const localDecision = evaluateLocalPolicy(local.policy, rootDir, { target, command });
|
|
1780
|
-
const isDeny = !localDecision.allowed;
|
|
1781
|
-
return {
|
|
1782
|
-
decision: isDeny ? "deny" : "allow",
|
|
1783
|
-
deny: isDeny,
|
|
1784
|
-
matchedRules: localDecision.winningRules.map((r) => r.id),
|
|
1785
|
-
mode,
|
|
1786
|
-
enforced: mode === "enforce"
|
|
1787
|
-
};
|
|
1788
|
-
}
|
|
1789
|
-
function auditSync(entry) {
|
|
1790
|
-
const eventsPath = path14.join(rootDir, ".lbe", "events.jsonl");
|
|
1791
|
-
const dir = path14.dirname(eventsPath);
|
|
1792
|
-
if (!fs13.existsSync(dir)) fs13.mkdirSync(dir, { recursive: true });
|
|
1793
|
-
const line = JSON.stringify({ ts: Math.floor(Date.now() / 1e3), ...entry }) + "\n";
|
|
1794
|
-
const fd = fs13.openSync(eventsPath, "a");
|
|
1795
|
-
try {
|
|
1796
|
-
fs13.writeSync(fd, line);
|
|
1797
|
-
} finally {
|
|
1798
|
-
fs13.closeSync(fd);
|
|
1799
|
-
}
|
|
1800
|
-
}
|
|
1801
|
-
async function dryRun(request) {
|
|
1802
|
-
const prepared = prepare(request);
|
|
1803
|
-
if (prepared.error) return { ...prepared.error, dryRun: true };
|
|
1804
|
-
return {
|
|
1805
|
-
ok: true,
|
|
1806
|
-
decision: prepared.local.policy.mode === "observe" ? "observe" : "allow",
|
|
1807
|
-
executed: false,
|
|
1808
|
-
dryRun: true,
|
|
1809
|
-
matchedRules: prepared.localDecision.winningRules.map((rule) => rule.id),
|
|
1810
|
-
rollback: { available: MUTATIONS.has(prepared.normalized.request.intent), performed: false }
|
|
1811
|
-
};
|
|
1812
|
-
}
|
|
1813
|
-
async function execute(request) {
|
|
1814
|
-
const prepared = prepare(request, { recordNonce: true });
|
|
1815
|
-
if (prepared.error) {
|
|
1816
|
-
auditLocalPolicy(rootDir, { action: request?.intent, actor: request?.actor || "agent:local", decision: "deny", error: prepared.error.error.code });
|
|
1817
|
-
return prepared.error;
|
|
1818
|
-
}
|
|
1819
|
-
if (prepared.local.policy.mode === "observe") {
|
|
1820
|
-
appendAudit(path14.join(rootDir, ".lbe/audit.jsonl"), {
|
|
1821
|
-
kind: "local_execution",
|
|
1822
|
-
commandId: prepared.proposal.commandId,
|
|
1823
|
-
requesterId: prepared.normalized.actor,
|
|
1824
|
-
intent: prepared.normalized.request.intent,
|
|
1825
|
-
decision: "observe",
|
|
1826
|
-
status: "observed"
|
|
1827
|
-
});
|
|
1828
|
-
return {
|
|
1829
|
-
ok: true,
|
|
1830
|
-
decision: "observe",
|
|
1831
|
-
executed: false,
|
|
1832
|
-
dryRun: false,
|
|
1833
|
-
matchedRules: prepared.localDecision.winningRules.map((r) => r.id),
|
|
1834
|
-
rollback: { available: false, performed: false }
|
|
1835
|
-
};
|
|
1836
|
-
}
|
|
1837
|
-
const requester = prepared.policy.requesters[prepared.normalized.actor];
|
|
1838
|
-
const adapterResult = await executeAdapter(prepared.normalized.detail.adapter, prepared.proposal, prepared.policy, requester);
|
|
1839
|
-
const ok = adapterResult.status === "completed";
|
|
1840
|
-
const audit = appendAudit(path14.join(rootDir, ".lbe/audit.jsonl"), {
|
|
1841
|
-
kind: "local_execution",
|
|
1842
|
-
commandId: prepared.proposal.commandId,
|
|
1843
|
-
requesterId: prepared.normalized.actor,
|
|
1844
|
-
intent: prepared.normalized.request.intent,
|
|
1845
|
-
decision: ok ? "allow" : "deny",
|
|
1846
|
-
status: adapterResult.status
|
|
1847
|
-
});
|
|
1848
|
-
return {
|
|
1849
|
-
ok,
|
|
1850
|
-
decision: ok ? "allow" : "deny",
|
|
1851
|
-
executed: ok,
|
|
1852
|
-
dryRun: false,
|
|
1853
|
-
matchedRules: prepared.localDecision.winningRules.map((rule) => rule.id),
|
|
1854
|
-
auditId: audit.hash,
|
|
1855
|
-
rollback: { available: MUTATIONS.has(prepared.normalized.request.intent), performed: false, backupId: adapterResult.backup?.hash },
|
|
1856
|
-
...ok ? {} : { error: { code: adapterResult.errorCode || "EXECUTION_FAILED", message: adapterResult.error || "Execution failed", recoverable: true } }
|
|
1857
|
-
};
|
|
1858
|
-
}
|
|
1859
|
-
const writeFile = (target, content) => execute({ intent: "write_file", target, content });
|
|
1860
|
-
const readFile = (target) => execute({ intent: "read_file", target });
|
|
1861
|
-
const patchFile = (target, content) => execute({ intent: "patch_file", target, content });
|
|
1862
|
-
const deleteFile = (target) => execute({ intent: "delete_file", target });
|
|
1863
|
-
const runShell = (cmd2, args = [], opts2 = {}) => execute({ intent: "run_shell", command: { cmd: cmd2, args, ...opts2 } });
|
|
1864
|
-
return {
|
|
1865
|
-
rootDir,
|
|
1866
|
-
// High-level API — use these
|
|
1867
|
-
writeFile,
|
|
1868
|
-
readFile,
|
|
1869
|
-
patchFile,
|
|
1870
|
-
deleteFile,
|
|
1871
|
-
runShell,
|
|
1872
|
-
// Low-level API — for advanced use
|
|
1873
|
-
validate: async (request) => {
|
|
1874
|
-
const preview = await dryRun(request);
|
|
1875
|
-
return { ...preview, dryRun: false, executed: false };
|
|
1876
|
-
},
|
|
1877
|
-
dryRun,
|
|
1878
|
-
execute,
|
|
1879
|
-
policy: {
|
|
1880
|
-
read: () => loadLocalPolicy(rootDir, options.mode || "enforce").policy,
|
|
1881
|
-
proposeRule: proposePolicyRule,
|
|
1882
|
-
addRule: (rule) => addLocalPolicyRule(rootDir, rule, options.mode || "enforce")
|
|
1883
|
-
},
|
|
1884
|
-
audit: { verify: () => verifyAuditLogIntegrity(path14.join(rootDir, ".lbe/audit.jsonl")) },
|
|
1885
|
-
evaluateSync,
|
|
1886
|
-
auditSync
|
|
1887
|
-
};
|
|
1888
|
-
}
|
|
1889
|
-
var INTENTS, MUTATIONS, FORBIDDEN_CONTENT;
|
|
1890
|
-
var init_localExecutor = __esm({
|
|
1891
|
-
"src/exec/localExecutor.js"() {
|
|
1892
|
-
init_signature();
|
|
1893
|
-
init_validator();
|
|
1894
|
-
init_adapters();
|
|
1895
|
-
init_auditLog();
|
|
1896
|
-
init_localPolicy();
|
|
1897
|
-
INTENTS = {
|
|
1898
|
-
read_file: { id: "READ_FILE", adapter: "file", action: "read" },
|
|
1899
|
-
write_file: { id: "WRITE_FILE", adapter: "file", action: "write" },
|
|
1900
|
-
patch_file: { id: "PATCH_FILE", adapter: "file", action: "patch" },
|
|
1901
|
-
delete_file: { id: "DELETE_FILE", adapter: "file", action: "delete" },
|
|
1902
|
-
run_shell: { id: "RUN_SHELL", adapter: "shell", action: "run" }
|
|
1903
|
-
};
|
|
1904
|
-
MUTATIONS = /* @__PURE__ */ new Set(["write_file", "patch_file", "delete_file"]);
|
|
1905
|
-
FORBIDDEN_CONTENT = [
|
|
1906
|
-
/\beval\s*\(/i,
|
|
1907
|
-
/\bFunction\s*\(/i,
|
|
1908
|
-
/\bexec\s*\(/i,
|
|
1909
|
-
/\brequire\s*\(/,
|
|
1910
|
-
/\bimport\s*\(/,
|
|
1911
|
-
/\bchild_process\b/,
|
|
1912
|
-
/\b__proto__\b/,
|
|
1913
|
-
/\bconstructor\s*\[/,
|
|
1914
|
-
/evalScript/i
|
|
1915
|
-
];
|
|
1916
|
-
}
|
|
1917
|
-
});
|
|
1918
|
-
|
|
1919
|
-
// exec/cli.js
|
|
1920
|
-
import fs14 from "fs";
|
|
1921
|
-
import path15 from "path";
|
|
1922
|
-
import { spawnSync as spawnSync2, spawn } from "child_process";
|
|
1923
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1924
|
-
|
|
1925
|
-
// src/cli/commands/init.js
|
|
1926
|
-
init_signature();
|
|
1927
|
-
import fs4 from "fs";
|
|
1928
|
-
import path4 from "path";
|
|
1929
|
-
import readline from "readline";
|
|
1930
|
-
|
|
1931
|
-
// src/core/policySignature.js
|
|
1932
|
-
init_signature();
|
|
1933
|
-
import fs2 from "fs";
|
|
1934
|
-
import path2 from "path";
|
|
1935
|
-
|
|
1936
|
-
// src/core/trustedKeys.js
|
|
1937
|
-
import fs from "fs";
|
|
1938
|
-
import path from "path";
|
|
1939
|
-
|
|
1940
|
-
// src/core/policySignature.js
|
|
1941
|
-
function createPolicySignatureEnvelope({ policyObj, secretKeyB64, keyId }) {
|
|
1942
|
-
const signResult = signEd25519({
|
|
1943
|
-
payloadObj: policyObj,
|
|
1944
|
-
secretKeyB64
|
|
1945
|
-
});
|
|
1946
|
-
if (signResult.error) {
|
|
1947
|
-
return {
|
|
1948
|
-
ok: false,
|
|
1949
|
-
reason: "POLICY_SIGNATURE_CREATE_FAILED",
|
|
1950
|
-
message: signResult.error,
|
|
1951
|
-
envelope: null
|
|
1952
|
-
};
|
|
1953
|
-
}
|
|
1954
|
-
return {
|
|
1955
|
-
ok: true,
|
|
1956
|
-
reason: null,
|
|
1957
|
-
message: "Policy signature created",
|
|
1958
|
-
envelope: {
|
|
1959
|
-
alg: "ed25519",
|
|
1960
|
-
keyId,
|
|
1961
|
-
sig: signResult.signature,
|
|
1962
|
-
createdAt: Math.floor(Date.now() / 1e3)
|
|
1963
|
-
}
|
|
1964
|
-
};
|
|
1965
|
-
}
|
|
1966
|
-
|
|
1967
|
-
// src/core/workspaceScanner.js
|
|
1968
|
-
import fs3 from "fs";
|
|
1969
|
-
import path3 from "path";
|
|
1970
|
-
var PROJECT_SIGNALS = [
|
|
1971
|
-
// Code types — ordered by priority for primaryType resolution
|
|
1972
|
-
{ file: "package.json", type: "node" },
|
|
1973
|
-
{ file: "pyproject.toml", type: "python" },
|
|
1974
|
-
{ file: "requirements.txt", type: "python" },
|
|
1975
|
-
{ file: "go.mod", type: "go" },
|
|
1976
|
-
{ file: "Cargo.toml", type: "rust" },
|
|
1977
|
-
{ file: "pom.xml", type: "java" },
|
|
1978
|
-
{ file: "build.gradle", type: "java" },
|
|
1979
|
-
{ file: "build.gradle.kts", type: "java" },
|
|
1980
|
-
// Infrastructure types — supplementary, not primary
|
|
1981
|
-
{ file: "Dockerfile", type: "docker" },
|
|
1982
|
-
{ file: "docker-compose.yml", type: "docker" },
|
|
1983
|
-
{ dir: ".github/workflows", type: "ci" },
|
|
1984
|
-
{ file: ".gitlab-ci.yml", type: "ci" },
|
|
1985
|
-
{ dir: ".circleci", type: "ci" },
|
|
1986
|
-
{ file: "Jenkinsfile", type: "ci" },
|
|
1987
|
-
{ file: ".travis.yml", type: "ci" }
|
|
1988
|
-
];
|
|
1989
|
-
var CODE_TYPES = ["node", "python", "go", "rust", "java"];
|
|
1990
|
-
var SURFACE_MAP = {
|
|
1991
|
-
source: ["src", "lib", "app", "pages", "components", "core", "api", "server", "client", "pkg", "cmd"],
|
|
1992
|
-
generated: ["dist", "build", ".next", "out", "coverage", "target", ".cache", "__pycache__", ".turbo"],
|
|
1993
|
-
tests: ["test", "tests", "__tests__", "spec", "e2e"],
|
|
1994
|
-
docs: ["docs", "doc", "documentation"]
|
|
1995
|
-
};
|
|
1996
|
-
var SECRET_GLOBS = [".env", ".env.*", "keys/**", "secrets/**", "*.key", "*.pem", "*.p12", "*.pfx", "*.crt"];
|
|
1997
|
-
var ALWAYS_DENY = ["node_modules/**", ".git/**"];
|
|
1998
|
-
var LOCKFILES_BY_TYPE = {
|
|
1999
|
-
node: ["package-lock.json", "yarn.lock", "pnpm-lock.yaml"],
|
|
2000
|
-
python: ["Pipfile.lock", "poetry.lock"],
|
|
2001
|
-
go: ["go.sum"],
|
|
2002
|
-
rust: ["Cargo.lock"],
|
|
2003
|
-
java: ["gradle/wrapper/**"],
|
|
2004
|
-
docker: [],
|
|
2005
|
-
ci: [],
|
|
2006
|
-
generic: []
|
|
2007
|
-
};
|
|
2008
|
-
var CONFIG_FILES_BY_TYPE = {
|
|
2009
|
-
node: [
|
|
2010
|
-
"package.json",
|
|
2011
|
-
"tsconfig*.json",
|
|
2012
|
-
"jest.config.*",
|
|
2013
|
-
"vite.config.*",
|
|
2014
|
-
"next.config.*",
|
|
2015
|
-
"webpack.config.*",
|
|
2016
|
-
".eslintrc*",
|
|
2017
|
-
".eslint.config.*",
|
|
2018
|
-
".prettierrc*",
|
|
2019
|
-
"babel.config.*"
|
|
2020
|
-
],
|
|
2021
|
-
python: [
|
|
2022
|
-
"pyproject.toml",
|
|
2023
|
-
"setup.py",
|
|
2024
|
-
"setup.cfg",
|
|
2025
|
-
"tox.ini",
|
|
2026
|
-
"pytest.ini",
|
|
2027
|
-
"mypy.ini",
|
|
2028
|
-
".flake8",
|
|
2029
|
-
".pylintrc",
|
|
2030
|
-
"Pipfile"
|
|
2031
|
-
],
|
|
2032
|
-
go: ["go.mod", ".golangci.yml", ".golangci.yaml"],
|
|
2033
|
-
rust: ["Cargo.toml", "rust-toolchain.toml", "clippy.toml", ".rustfmt.toml"],
|
|
2034
|
-
java: [
|
|
2035
|
-
"pom.xml",
|
|
2036
|
-
"build.gradle",
|
|
2037
|
-
"build.gradle.kts",
|
|
2038
|
-
"gradle.properties",
|
|
2039
|
-
"settings.gradle",
|
|
2040
|
-
"settings.gradle.kts"
|
|
2041
|
-
],
|
|
2042
|
-
docker: ["Dockerfile", "docker-compose.yml", ".dockerignore"],
|
|
2043
|
-
ci: [".gitlab-ci.yml", "Jenkinsfile", ".travis.yml"],
|
|
2044
|
-
generic: ["Makefile", "CMakeLists.txt", "meson.build"]
|
|
2045
|
-
};
|
|
2046
|
-
var CONFIG_FILES_UNIVERSAL = [".editorconfig", ".nvmrc", ".node-version", ".python-version"];
|
|
2047
|
-
var CONFIG_DIRS_UNIVERSAL = ["config", ".github", ".gitlab", ".circleci", ".vscode"];
|
|
2048
|
-
var CONFIG_LABEL = {
|
|
2049
|
-
node: "dependency and build config",
|
|
2050
|
-
python: "package and environment config",
|
|
2051
|
-
go: "module definition",
|
|
2052
|
-
rust: "crate manifest",
|
|
2053
|
-
java: "build definition",
|
|
2054
|
-
docker: "container config",
|
|
2055
|
-
ci: "pipeline definition",
|
|
2056
|
-
generic: "project config"
|
|
2057
|
-
};
|
|
2058
|
-
var LOCKFILE_LABEL = {
|
|
2059
|
-
node: "package manager",
|
|
2060
|
-
python: "dependency resolver",
|
|
2061
|
-
go: "module checksums",
|
|
2062
|
-
rust: "dependency resolver",
|
|
2063
|
-
java: "Gradle wrapper"
|
|
2064
|
-
};
|
|
2065
|
-
var FALLBACK_MANIFESTS = [
|
|
2066
|
-
"composer.json",
|
|
2067
|
-
// PHP
|
|
2068
|
-
"Gemfile",
|
|
2069
|
-
// Ruby
|
|
2070
|
-
"mix.exs",
|
|
2071
|
-
// Elixir
|
|
2072
|
-
"pubspec.yaml",
|
|
2073
|
-
// Dart / Flutter
|
|
2074
|
-
"Package.swift",
|
|
2075
|
-
// Swift
|
|
2076
|
-
"project.clj",
|
|
2077
|
-
// Clojure
|
|
2078
|
-
"build.sbt",
|
|
2079
|
-
// Scala
|
|
2080
|
-
"stack.yaml",
|
|
2081
|
-
// Haskell
|
|
2082
|
-
"deno.json",
|
|
2083
|
-
"deno.jsonc",
|
|
2084
|
-
// Deno
|
|
2085
|
-
"Podfile"
|
|
2086
|
-
// CocoaPods (iOS/macOS)
|
|
2087
|
-
];
|
|
2088
|
-
var FALLBACK_LOCKFILES = [
|
|
2089
|
-
"composer.lock",
|
|
2090
|
-
// PHP
|
|
2091
|
-
"Gemfile.lock",
|
|
2092
|
-
// Ruby
|
|
2093
|
-
"mix.lock",
|
|
2094
|
-
// Elixir
|
|
2095
|
-
"pubspec.lock",
|
|
2096
|
-
// Dart / Flutter
|
|
2097
|
-
"Package.resolved"
|
|
2098
|
-
// Swift
|
|
2099
|
-
];
|
|
2100
|
-
var FALLBACK_EXTENSIONS = [".csproj", ".fsproj", ".sln", ".cabal"];
|
|
2101
|
-
function exists(p) {
|
|
2102
|
-
return fs3.existsSync(p);
|
|
2103
|
-
}
|
|
2104
|
-
function detectDirs(root, names) {
|
|
2105
|
-
return names.filter((n) => exists(path3.join(root, n))).map((n) => `${n}/**`);
|
|
2106
|
-
}
|
|
2107
|
-
function readGitignore(root) {
|
|
2108
|
-
const p = path3.join(root, ".gitignore");
|
|
2109
|
-
if (!exists(p)) return [];
|
|
2110
|
-
return fs3.readFileSync(p, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#") && !l.startsWith("!")).map((l) => l.endsWith("/") ? l + "**" : l);
|
|
2111
|
-
}
|
|
2112
|
-
function dedup(arr) {
|
|
2113
|
-
return arr.filter((v, i, a) => v && a.indexOf(v) === i);
|
|
2114
|
-
}
|
|
2115
|
-
function detectProjectTypes(root) {
|
|
2116
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2117
|
-
const types = [];
|
|
2118
|
-
for (const sig of PROJECT_SIGNALS) {
|
|
2119
|
-
if (seen.has(sig.type)) continue;
|
|
2120
|
-
const p = path3.join(root, sig.file || sig.dir);
|
|
2121
|
-
if (exists(p)) {
|
|
2122
|
-
seen.add(sig.type);
|
|
2123
|
-
types.push(sig.type);
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
return types.length > 0 ? types : ["generic"];
|
|
2127
|
-
}
|
|
2128
|
-
function primaryType(projectTypes) {
|
|
2129
|
-
return CODE_TYPES.find((t) => projectTypes.includes(t)) ?? "generic";
|
|
2130
|
-
}
|
|
2131
|
-
function scanFallbackManifests(root) {
|
|
2132
|
-
const manifests = FALLBACK_MANIFESTS.filter((f) => exists(path3.join(root, f)));
|
|
2133
|
-
const lockfiles = FALLBACK_LOCKFILES.filter((f) => exists(path3.join(root, f)));
|
|
2134
|
-
try {
|
|
2135
|
-
const entries = fs3.readdirSync(root);
|
|
2136
|
-
for (const e of entries) {
|
|
2137
|
-
if (FALLBACK_EXTENSIONS.some((ext) => e.endsWith(ext))) manifests.push(e);
|
|
2138
|
-
}
|
|
2139
|
-
} catch {
|
|
2140
|
-
}
|
|
2141
|
-
return { manifests, lockfiles };
|
|
2142
|
-
}
|
|
2143
|
-
function detectSurfaces(root, projectTypes) {
|
|
2144
|
-
const s = {};
|
|
2145
|
-
for (const [key, names] of Object.entries(SURFACE_MAP)) {
|
|
2146
|
-
s[key] = detectDirs(root, names);
|
|
2147
|
-
}
|
|
2148
|
-
s.secrets = SECRET_GLOBS.filter((g) => {
|
|
2149
|
-
const base = g.split("/")[0].replace(/\*.*/, "");
|
|
2150
|
-
return base.includes("*") || exists(path3.join(root, base));
|
|
2151
|
-
});
|
|
2152
|
-
const typeConfigFiles = dedup(
|
|
2153
|
-
projectTypes.flatMap((t) => CONFIG_FILES_BY_TYPE[t] || CONFIG_FILES_BY_TYPE.generic).concat(CONFIG_FILES_UNIVERSAL)
|
|
2154
|
-
);
|
|
2155
|
-
s.config = dedup([
|
|
2156
|
-
...typeConfigFiles.filter((f) => !f.includes("*") && !f.endsWith("/**") && exists(path3.join(root, f))),
|
|
2157
|
-
...typeConfigFiles.filter((f) => f.endsWith("/**") && exists(path3.join(root, f.replace("/**", "")))),
|
|
2158
|
-
...detectDirs(root, CONFIG_DIRS_UNIVERSAL)
|
|
2159
|
-
]);
|
|
2160
|
-
s.lockfiles = dedup(
|
|
2161
|
-
projectTypes.flatMap((t) => LOCKFILES_BY_TYPE[t] || []).filter((f) => {
|
|
2162
|
-
const base = f.replace(/\*.*/, "").split("/")[0];
|
|
2163
|
-
return base.includes("*") || exists(path3.join(root, base));
|
|
2164
|
-
})
|
|
2165
|
-
);
|
|
2166
|
-
if (!projectTypes.some((t) => CODE_TYPES.includes(t))) {
|
|
2167
|
-
const fb = scanFallbackManifests(root);
|
|
2168
|
-
s.config = dedup([...s.config, ...fb.manifests]);
|
|
2169
|
-
s.lockfiles = dedup([...s.lockfiles, ...fb.lockfiles]);
|
|
2170
|
-
}
|
|
2171
|
-
return s;
|
|
2172
|
-
}
|
|
2173
|
-
function buildSemantics(projectTypes, primary, surfaces) {
|
|
2174
|
-
const sem = {};
|
|
2175
|
-
sem.structure = "Preserve the existing folder structure. Add new files within established directories. Do not create top-level directories, reorganize, or rename existing folders.";
|
|
2176
|
-
if (surfaces.source.length > 0) {
|
|
2177
|
-
sem.source = `Source code lives in ${surfaces.source.join(", ")}. Make feature changes and bug fixes here only.`;
|
|
2178
|
-
}
|
|
2179
|
-
sem.secrets = `Never propose changes to credential or key files (${SECRET_GLOBS.slice(0, 4).join(", ")} \u2026). These are never task targets regardless of the instruction.`;
|
|
2180
|
-
if (surfaces.generated.length > 0) {
|
|
2181
|
-
sem.generated = `${surfaces.generated.join(", ")} contain generated output. Modify the source files that produce them; never write to generated directories directly.`;
|
|
2182
|
-
}
|
|
2183
|
-
if (surfaces.config.length > 0) {
|
|
2184
|
-
const codeTypes = projectTypes.filter((t) => CODE_TYPES.includes(t));
|
|
2185
|
-
const label = codeTypes.length === 1 ? CONFIG_LABEL[codeTypes[0]] : "project configuration";
|
|
2186
|
-
const listed = surfaces.config.slice(0, 5).join(", ");
|
|
2187
|
-
const trailer = surfaces.config.length > 5 ? " and related files" : "";
|
|
2188
|
-
sem.config = `Treat ${listed}${trailer} as ${label} files. Do not modify them unless the task explicitly requires a configuration or dependency change.`;
|
|
2189
|
-
}
|
|
2190
|
-
if (surfaces.tests.length > 0) {
|
|
2191
|
-
sem.tests = `Test files in ${surfaces.tests.join(", ")} validate behavior. Update them only when the behavior they cover changes.`;
|
|
2192
|
-
}
|
|
2193
|
-
if (surfaces.lockfiles?.length > 0) {
|
|
2194
|
-
const label = LOCKFILE_LABEL[primary] || "tooling";
|
|
2195
|
-
const listed = surfaces.lockfiles.slice(0, 3).join(", ");
|
|
2196
|
-
sem.lockfiles = `${listed} are generated by the ${label}. Never edit them directly.`;
|
|
2197
|
-
}
|
|
2198
|
-
if (primary === "generic") {
|
|
2199
|
-
const foundManifests = surfaces.config.filter((f) => !f.endsWith("/**"));
|
|
2200
|
-
if (foundManifests.length > 0) {
|
|
2201
|
-
sem.unknown = `This project uses an unrecognized toolchain. Treat ${foundManifests.slice(0, 3).join(", ")} as dependency/manifest files. Do not modify them unless the task explicitly requires a dependency change.`;
|
|
2202
|
-
} else {
|
|
2203
|
-
sem.unknown = "This project uses an unrecognized toolchain. Do not assume standard source layouts, dependency files, or build conventions apply. Confirm any structural assumption before acting.";
|
|
2204
|
-
}
|
|
2205
|
-
}
|
|
2206
|
-
if (projectTypes.includes("docker")) {
|
|
2207
|
-
sem.docker = "Dockerfile and docker-compose.yml define the container environment. Treat them as infrastructure config \u2014 only modify when the task explicitly involves container or environment changes.";
|
|
2208
|
-
}
|
|
2209
|
-
if (projectTypes.includes("ci")) {
|
|
2210
|
-
sem.ci = "CI config files (.github/**, .gitlab-ci.yml, etc.) define the build and deployment pipeline. Do not modify them unless the task explicitly involves CI/CD changes.";
|
|
2211
|
-
}
|
|
2212
|
-
return sem;
|
|
2213
|
-
}
|
|
2214
|
-
function buildEnforcement(surfaces, gitignorePatterns) {
|
|
2215
|
-
const allow = dedup([...surfaces.source, ...surfaces.docs, ...surfaces.tests]);
|
|
2216
|
-
const approval = [...surfaces.config];
|
|
2217
|
-
const deny = dedup([
|
|
2218
|
-
...surfaces.secrets,
|
|
2219
|
-
...surfaces.generated,
|
|
2220
|
-
...surfaces.lockfiles || [],
|
|
2221
|
-
...ALWAYS_DENY,
|
|
2222
|
-
...gitignorePatterns.filter((p) => p.endsWith("/**")).slice(0, 8)
|
|
2223
|
-
]);
|
|
2224
|
-
return {
|
|
2225
|
-
allow: allow.length > 0 ? allow : ["src/**"],
|
|
2226
|
-
approval: approval.length > 0 ? approval : [],
|
|
2227
|
-
deny
|
|
2228
|
-
};
|
|
2229
|
-
}
|
|
2230
|
-
function scanWorkspace(rootDir) {
|
|
2231
|
-
const root = path3.resolve(rootDir || process.cwd());
|
|
2232
|
-
const projectTypes = detectProjectTypes(root);
|
|
2233
|
-
const primary = primaryType(projectTypes);
|
|
2234
|
-
const surfaces = detectSurfaces(root, projectTypes);
|
|
2235
|
-
const gitignore = readGitignore(root);
|
|
2236
|
-
const semantics = buildSemantics(projectTypes, primary, surfaces);
|
|
2237
|
-
const enforcement = buildEnforcement(surfaces, gitignore);
|
|
2238
|
-
return { projectTypes, primaryType: primary, surfaces, semantics, enforcement };
|
|
2239
|
-
}
|
|
2240
|
-
function formatSummary(projectTypes, semantics, enforcement) {
|
|
2241
|
-
const lines = [];
|
|
2242
|
-
const label = Array.isArray(projectTypes) ? projectTypes.join(" + ") : projectTypes;
|
|
2243
|
-
lines.push(`Detected: ${label}`);
|
|
2244
|
-
lines.push("");
|
|
2245
|
-
lines.push("Agent semantics:");
|
|
2246
|
-
for (const [, v] of Object.entries(semantics)) {
|
|
2247
|
-
lines.push(` - ${v}`);
|
|
2248
|
-
}
|
|
2249
|
-
lines.push("");
|
|
2250
|
-
lines.push("Enforcement:");
|
|
2251
|
-
if (enforcement.allow.length) lines.push(` allow: ${enforcement.allow.join(", ")}`);
|
|
2252
|
-
if (enforcement.approval.length) lines.push(` approval: ${enforcement.approval.join(", ")}`);
|
|
2253
|
-
if (enforcement.deny.length) lines.push(` deny: ${enforcement.deny.slice(0, 6).join(", ")}${enforcement.deny.length > 6 ? " \u2026" : ""}`);
|
|
2254
|
-
return lines.join("\n");
|
|
2255
|
-
}
|
|
2256
|
-
|
|
2257
|
-
// src/cli/commands/init.js
|
|
2258
|
-
function ask(question) {
|
|
2259
|
-
if (!process.stdin.isTTY) return Promise.resolve("y");
|
|
2260
|
-
return new Promise((resolve) => {
|
|
2261
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
2262
|
-
rl.question(question, (ans) => {
|
|
2263
|
-
rl.close();
|
|
2264
|
-
resolve(ans.trim().toLowerCase());
|
|
2265
|
-
});
|
|
2266
|
-
});
|
|
2267
|
-
}
|
|
2268
|
-
function applyStrict(enforcement) {
|
|
2269
|
-
return {
|
|
2270
|
-
...enforcement,
|
|
2271
|
-
deny: [.../* @__PURE__ */ new Set([...enforcement.deny, ...enforcement.approval, "*.json", "config/**"])],
|
|
2272
|
-
approval: []
|
|
2273
|
-
};
|
|
2274
|
-
}
|
|
2275
|
-
function applyRelaxed(enforcement) {
|
|
2276
|
-
return { ...enforcement, approval: [] };
|
|
2277
|
-
}
|
|
2278
|
-
function setupCrypto(cwd) {
|
|
2279
|
-
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
2280
|
-
const expiresAt = new Date(Date.now() + 180 * 24 * 60 * 60 * 1e3).toISOString();
|
|
2281
|
-
const defaultKeyId = "agent:gpt-v1-2026Q1";
|
|
2282
|
-
const signerKeyId = "policy-signer-v1-2026Q1";
|
|
2283
|
-
const lbeDir = path4.join(cwd, ".lbe");
|
|
2284
|
-
for (const d of ["config", "keys", "data"]) {
|
|
2285
|
-
fs4.mkdirSync(path4.join(lbeDir, d), { recursive: true });
|
|
2286
|
-
}
|
|
2287
|
-
const dataFiles = {
|
|
2288
|
-
".lbe/data/nonce.db.json": JSON.stringify({ entries: [] }, null, 2),
|
|
2289
|
-
".lbe/data/rate-limit.db.json": JSON.stringify({ entries: [] }, null, 2),
|
|
2290
|
-
".lbe/data/policy.state.json": JSON.stringify({ schemaVersion: "1", lastAccepted: null, updatedAt: null }, null, 2),
|
|
2291
|
-
".lbe/data/audit.log.jsonl": ""
|
|
2292
|
-
};
|
|
2293
|
-
for (const [rel, content] of Object.entries(dataFiles)) {
|
|
2294
|
-
const p = path4.join(cwd, rel);
|
|
2295
|
-
if (!fs4.existsSync(p)) fs4.writeFileSync(p, content);
|
|
2296
|
-
}
|
|
2297
|
-
const keyDir = path4.join(lbeDir, "keys");
|
|
2298
|
-
const pubPath = path4.join(keyDir, "public.key");
|
|
2299
|
-
const secPath = path4.join(keyDir, "secret.key");
|
|
2300
|
-
let publicKeyB64, secretKeyB64;
|
|
2301
|
-
if (fs4.existsSync(pubPath) && fs4.existsSync(secPath)) {
|
|
2302
|
-
publicKeyB64 = fs4.readFileSync(pubPath, "utf8").trim();
|
|
2303
|
-
secretKeyB64 = fs4.readFileSync(secPath, "utf8").trim();
|
|
2304
|
-
} else {
|
|
2305
|
-
const kp = generateKeyPair();
|
|
2306
|
-
publicKeyB64 = kp.publicKey;
|
|
2307
|
-
secretKeyB64 = kp.secretKey;
|
|
2308
|
-
fs4.writeFileSync(pubPath, publicKeyB64);
|
|
2309
|
-
fs4.writeFileSync(secPath, secretKeyB64, { mode: 384 });
|
|
2310
|
-
}
|
|
2311
|
-
const keysPath = path4.join(lbeDir, "config/keys.json");
|
|
2312
|
-
const keysStore = fs4.existsSync(keysPath) ? JSON.parse(fs4.readFileSync(keysPath, "utf8")) : { schemaVersion: "1", defaultKeyId, trustedKeys: {} };
|
|
2313
|
-
for (const keyId of [defaultKeyId, signerKeyId]) {
|
|
2314
|
-
if (!keysStore.trustedKeys[keyId]) {
|
|
2315
|
-
keysStore.trustedKeys[keyId] = {
|
|
2316
|
-
publicKey: publicKeyB64,
|
|
2317
|
-
notBefore: nowIso,
|
|
2318
|
-
expiresAt,
|
|
2319
|
-
validFrom: nowIso,
|
|
2320
|
-
validUntil: expiresAt,
|
|
2321
|
-
deprecated: false
|
|
2322
|
-
};
|
|
2323
|
-
}
|
|
2324
|
-
}
|
|
2325
|
-
keysStore.defaultKeyId = defaultKeyId;
|
|
2326
|
-
fs4.writeFileSync(keysPath, JSON.stringify(keysStore, null, 2));
|
|
2327
|
-
const policyPath = path4.join(lbeDir, "config/policy.default.json");
|
|
2328
|
-
let policyObj;
|
|
2329
|
-
if (fs4.existsSync(policyPath)) {
|
|
2330
|
-
policyObj = JSON.parse(fs4.readFileSync(policyPath, "utf8"));
|
|
2331
|
-
} else {
|
|
2332
|
-
policyObj = {
|
|
2333
|
-
default: "DENY",
|
|
2334
|
-
version: "1.0.0",
|
|
2335
|
-
createdAt: nowIso,
|
|
2336
|
-
security: {
|
|
2337
|
-
maxClockSkewSec: 600,
|
|
2338
|
-
maxPolicyCreatedAtSkewSec: 31536e3,
|
|
2339
|
-
defaultRateLimit: { windowSec: 60, maxRequests: 30 }
|
|
2340
|
-
},
|
|
2341
|
-
requesters: {
|
|
2342
|
-
"agent:gpt": {
|
|
2343
|
-
allowAdapters: ["noop", "shell"],
|
|
2344
|
-
allowCommands: ["RUN_SHELL"],
|
|
2345
|
-
rateLimit: { windowSec: 60, maxRequests: 30 },
|
|
2346
|
-
filesystem: { roots: [cwd], denyPatterns: ["**/.git/**", "**/secrets/**", "**/*.key"] },
|
|
2347
|
-
exec: { allowCmds: ["ls", "node", "python", "echo"], denyCmds: ["rm", "chmod", "chown", "curl", "wget", "su", "sudo"] }
|
|
2348
|
-
}
|
|
2349
|
-
}
|
|
2350
|
-
};
|
|
2351
|
-
fs4.writeFileSync(policyPath, JSON.stringify(policyObj, null, 2));
|
|
2352
|
-
}
|
|
2353
|
-
const sigResult = createPolicySignatureEnvelope({ policyObj, secretKeyB64, keyId: signerKeyId });
|
|
2354
|
-
if (sigResult.ok) {
|
|
2355
|
-
fs4.writeFileSync(path4.join(lbeDir, "config/policy.sig.json"), JSON.stringify(sigResult.envelope, null, 2));
|
|
2356
|
-
}
|
|
2357
|
-
return { defaultKeyId, secretKeyB64, publicKeyB64 };
|
|
2358
|
-
}
|
|
2359
|
-
function agentContractContent() {
|
|
2360
|
-
return `# LBE Governance Contract
|
|
2
|
+
var jt=Object.defineProperty;var L=(e,t)=>()=>(e&&(t=e(e=0)),t);var $t=(e,t)=>{for(var o in t)jt(e,o,{get:t[o],enumerable:!0})};import pe from"tweetnacl";import{canonicalize as Le}from"json-canonicalize";function ue(e){return Buffer.from(e,"base64")}function fe(e){return Buffer.from(e).toString("base64")}function me({payloadObj:e,sigB64:t,pubKeyB64:o}){try{let n=Buffer.from(Le(e),"utf8"),r=ue(t),s=ue(o),i=pe.sign.detached.verify(new Uint8Array(n),new Uint8Array(r),new Uint8Array(s));return{valid:i,message:i?"Signature verified":"Signature verification failed"}}catch(n){return{valid:!1,message:`Signature verification error: ${n.message}`}}}function ne(){let e=pe.sign.keyPair();return{publicKey:fe(e.publicKey),secretKey:fe(e.secretKey)}}function re({payloadObj:e,secretKeyB64:t}){try{let o=Buffer.from(Le(e),"utf8"),n=ue(t),r=pe.sign.detached(new Uint8Array(o),new Uint8Array(n));return{signature:fe(r),error:null}}catch(o){return{signature:null,error:`Signing failed: ${o.message}`}}}var Z=L(()=>{});import R from"fs";import he from"path";import fo from"crypto";function mo(e){return e+".lock"}function Ke(e){try{let t=R.openSync(e,"wx");return R.writeSync(t,`pid:${process.pid}:${Date.now()}`),R.closeSync(t),!0}catch(t){if(t.code==="EEXIST"||t.code==="EPERM"||t.code==="EBUSY"||t.code==="EACCES")return!1;throw t}}function qe(e,t){try{let o=R.statSync(e);if(Date.now()-o.mtimeMs>t)try{R.unlinkSync(e)}catch{}}catch{}}function yo(e){let t=Date.now()+e;for(;Date.now()<t;)try{Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,Math.max(1,t-Date.now()))}catch{}}function Me(e,t,o){let n=typeof t=="function"?t:o,r=typeof t=="function"?{}:t||{},{timeoutMs:s,pollMs:i,staleMs:a}={...po,...r},c=he.dirname(e);R.existsSync(c)||R.mkdirSync(c,{recursive:!0});let l=mo(e),p=Date.now()+s,f=!1;for(;!f&&(f=Ke(l),!f);){if(Date.now()>=p){if(qe(l,a),f=Ke(l),f)break;let y=new Error(`withFileLock: timeout acquiring ${l} after ${s}ms`);throw y.code="ELOCKTIMEOUT",y}qe(l,a);let m=Math.floor(Math.random()*i);yo(i+m)}try{return n()}finally{try{R.unlinkSync(l)}catch{}}}function D(e,t,o={}){let n=he.dirname(e);R.existsSync(n)||R.mkdirSync(n,{recursive:!0});let r=he.join(n,`.tmp-${Date.now()}-${fo.randomBytes(4).toString("hex")}`);try{R.writeFileSync(r,t,o),R.renameSync(r,e)}catch(s){try{R.existsSync(r)&&R.unlinkSync(r)}catch{}throw s}}var po,z=L(()=>{po={timeoutMs:5e3,pollMs:15,staleMs:3e4}});import q from"fs";import Se from"path";import go from"crypto";function Ve(e){return go.createHash("sha256").update(e).digest("hex")}function ho(e){try{if(!q.existsSync(e))return"GENESIS";let t=q.readFileSync(e,"utf8").trim();if(!t)return"GENESIS";let o=t.split(`
|
|
3
|
+
`),n=o[o.length-1];try{return JSON.parse(n).hash||"GENESIS"}catch{return"GENESIS"}}catch{return"GENESIS"}}function ee(e,t){let o=Se.dirname(e);q.existsSync(o)||q.mkdirSync(o,{recursive:!0});let n;return Me(e,()=>{let r=ho(e),s={...t,prevHash:r,timestamp:new Date().toISOString()};delete s.hash;let i=JSON.stringify(s),a=Ve(i),c=JSON.stringify({...s,hash:a}),l="";q.existsSync(e)&&(l=q.readFileSync(e,"utf8"));try{D(e,l+c+`
|
|
4
|
+
`,{encoding:"utf8"})}catch(p){throw new Error(`Audit log write failed: ${p.message}`)}n={success:!0,hash:a,prevHash:r,message:"Audit entry appended"}}),n}function Be(e,t={}){let o=t.failFast!==!1,n=Number.isFinite(t.maxEntries)&&t.maxEntries>0?Math.floor(t.maxEntries):null,r={ok:!0,file:Se.resolve(e),entries:0,valid:!0,firstInvalidIndex:null,reason:null,errors:[],message:"Audit log verified"};try{if(!q.existsSync(e))return r.message="Audit log file not found (treated as empty)",r;let s=q.readFileSync(e,"utf8").trim();if(!s)return r.message="Empty audit log",r;let i=s.split(`
|
|
5
|
+
`),a=n?i.slice(0,n):i;r.entries=a.length;let c="GENESIS";for(let l=0;l<a.length;l++){let p;try{p=JSON.parse(a[l])}catch{let h={index:l,reason:"INVALID_JSON_LINE",message:`Line ${l} is not valid JSON`};if(r.valid=!1,r.ok=!1,r.firstInvalidIndex??=l,r.reason??=h.reason,r.errors.push(h),o)break;continue}if(p.prevHash!==c){let h={index:l,reason:"PREV_HASH_MISMATCH",message:`Expected prevHash '${c}', got '${p.prevHash}'`};if(r.valid=!1,r.ok=!1,r.firstInvalidIndex??=l,r.reason??=h.reason,r.errors.push(h),o)break}let f={...p},m=f.hash;delete f.hash;let y=Ve(JSON.stringify(f));if(m!==y){let h={index:l,reason:"HASH_MISMATCH",message:`Expected hash '${y}', got '${m}'`};if(r.valid=!1,r.ok=!1,r.firstInvalidIndex??=l,r.reason??=h.reason,r.errors.push(h),o)break}c=m}return r.message=r.valid?`Audit log verified: ${r.entries} entries`:`Audit log integrity failed at index ${r.firstInvalidIndex}`,r}catch(s){return{ok:!1,file:Se.resolve(e),entries:0,valid:!1,firstInvalidIndex:null,reason:"AUDIT_VERIFY_ERROR",errors:[{index:null,reason:"AUDIT_VERIFY_ERROR",message:s.message}],message:`Integrity check failed: ${s.message}`}}}var ke=L(()=>{z()});import Ye from"fs";import W from"path";import So from"crypto";function Eo(e){let t=e.replace(/[.+^${}()|[\]\\]/g,"\\$&");return new RegExp("^"+t.replace(/\*\*\//g,"(?:.*/)?").replace(/\*\*/g,".*").replace(/\*/g,"[^/]*")+"$")}function Io(e,t){return W.relative(e,W.resolve(t)).split(W.sep).join("/")}function Ee(e){let t=W.resolve(e||process.cwd());return{root:t,policyPath:W.join(t,Ue),auditPath:W.join(t,ko)}}function G(e,t="observe"){let o=Ee(e);if(!Ye.existsSync(o.policyPath))return{...o,policy:{version:1,mode:t,workspace:o.root,rules:[]}};let n=JSON.parse(Ye.readFileSync(o.policyPath,"utf8"));if(n?.version!==1||!["observe","enforce"].includes(n.mode)||!Array.isArray(n.rules))throw new Error(`Invalid ${Ue}`);return{...o,policy:n}}function Ie(e,t){let{policyPath:o,root:n}=Ee(e),r={...t,version:1,workspace:n,rules:Array.isArray(t.rules)?t.rules:[]};return D(o,JSON.stringify(r,null,2)+`
|
|
6
|
+
`,{encoding:"utf8"}),r}function We(e,t,o){if(!t||!["allow","deny"].includes(t.effect)||!["path","command"].includes(t.type)||typeof t.pattern!="string"||!t.pattern||typeof t.from!="string"||!t.from)throw new Error("Rule requires effect, type, pattern, and from");let n=G(e,o),r={id:t.id||So.randomUUID(),effect:t.effect,type:t.type,pattern:t.pattern,from:t.from,at:t.at||new Date().toISOString()};return Ie(n.root,{...n.policy,mode:o||n.policy.mode,rules:[...n.policy.rules,r]}),{id:r.id,added:!0,rule:r}}function Ge(e){return{...e,proposed:!0,at:new Date().toISOString()}}function be(e,t,{target:o,command:n}={}){let r=W.resolve(t),s=[];o&&s.push({type:"path",value:Io(r,o)}),n&&s.push({type:"command",value:n});let i=e.rules.filter(c=>s.some(l=>l.type===c.type&&Eo(c.pattern).test(l.value))),a=i.filter(c=>c.effect==="deny");return{allowed:a.length===0,matched:i,winningRules:a.length?a:i.filter(c=>c.effect==="allow"),reason:a.length?"LOCAL_POLICY_DENY":null}}function He(e,t){let{auditPath:o}=Ee(e);ee(o,{kind:"local_policy",timestamp:new Date().toISOString(),...t})}var Ue,ko,ve=L(()=>{ke();z();Ue=".lbe/policy.json",ko=".lbe/audit.jsonl"});import ze from"fs";import bo from"path";function Xe(e){if(typeof e=="number"&&Number.isFinite(e))return{ok:!0,kind:"int",parts:[Math.floor(e)],raw:String(e)};if(typeof e!="string"||!e.trim())return{ok:!1,reason:"POLICY_VERSION_INVALID",message:"Policy version is required"};let t=e.trim();if(/^\d+$/.test(t))return{ok:!0,kind:"int",parts:[Number(t)],raw:t};let o=t.replace(/^v/i,"");if(/^\d+(\.\d+){0,2}$/.test(o)){let n=o.split(".").map(r=>Number(r));for(;n.length<3;)n.push(0);return{ok:!0,kind:"semver",parts:n,raw:t}}return{ok:!1,reason:"POLICY_VERSION_INVALID",message:`Unsupported policy version format '${e}' (use integer or semver)`}}function vo(e,t){let o=Math.max(e.parts.length,t.parts.length);for(let n=0;n<o;n++){let r=e.parts[n]??0,s=t.parts[n]??0;if(r>s)return 1;if(r<s)return-1}return 0}function Qe(e){if(typeof e=="number"&&Number.isFinite(e))return{ok:!0,epochSec:e>1e12?Math.floor(e/1e3):Math.floor(e)};if(typeof e!="string"||!e.trim())return{ok:!1,reason:"POLICY_CREATED_AT_INVALID",message:"Policy createdAt is required"};let t=Date.parse(e);return Number.isNaN(t)?{ok:!1,reason:"POLICY_CREATED_AT_INVALID",message:`Invalid policy createdAt '${e}'`}:{ok:!0,epochSec:Math.floor(t/1e3)}}function xo(e){if(!ze.existsSync(e))return{schemaVersion:"1",lastAccepted:null,updatedAt:null};try{let t=JSON.parse(ze.readFileSync(e,"utf8"));if(!t||typeof t!="object")throw new Error("Policy state file has invalid structure");return{schemaVersion:String(t.schemaVersion||"1"),lastAccepted:t.lastAccepted&&typeof t.lastAccepted=="object"?t.lastAccepted:null,updatedAt:t.updatedAt||null}}catch(t){throw new Error(`Policy state at ${e} is corrupt or unreadable: ${t.message}`)}}function wo(e,t){let o=JSON.stringify(t,null,2);D(e,o,{encoding:"utf8"})}function Ze({policyObj:e,statePath:t=bo.resolve(".lbe/data/policy.state.json"),maxCreatedAtSkewSec:o=31536e3,nowSec:n=Math.floor(Date.now()/1e3),persist:r=!0}){let s=Xe(e?.version);if(!s.ok)return{ok:!1,reason:s.reason,message:s.message,updated:!1};let i=Qe(e?.createdAt);if(!i.ok)return{ok:!1,reason:i.reason,message:i.message,updated:!1};let a=Math.abs(n-i.epochSec),c=Number.isFinite(o)&&o>0?Math.floor(o):31536e3;if(a>c)return{ok:!1,reason:"POLICY_CREATED_AT_SKEW_EXCEEDED",message:`Policy createdAt skew ${a}s exceeds allowed ${c}s`,updated:!1};let l;try{l=xo(t)}catch(u){return{ok:!1,reason:"POLICY_STATE_CORRUPT",message:u.message,updated:!1}}let p=l.lastAccepted,f=null,m=null,y=0;if(p&&(f=Xe(p.version),m=Qe(p.createdAt),f.ok&&m.ok)){if(y=vo(s,f),y<0)return{ok:!1,reason:"POLICY_VERSION_REGRESSION",message:`Policy version regression: current '${s.raw}' < last '${f.raw}'`,updated:!1};if(y===0&&i.epochSec<m.epochSec)return{ok:!1,reason:"POLICY_CREATED_AT_REGRESSION",message:`Policy createdAt regression: current '${e.createdAt}' < last '${p.createdAt}'`,updated:!1};if(y>0&&i.epochSec<m.epochSec)return{ok:!1,reason:"POLICY_CREATED_AT_REGRESSION",message:"Policy createdAt must be monotonic when version increases",updated:!1}}let h=!p||!f?.ok||!m?.ok||y>0||y===0&&i.epochSec>m.epochSec;if(r&&h){let u={schemaVersion:"1",lastAccepted:{version:e.version,createdAt:e.createdAt,environment:e.environment||null},updatedAt:new Date().toISOString()};wo(t,u)}return{ok:!0,reason:null,message:"Policy version guard passed",updated:h}}var et=L(()=>{z()});import tt from"fs";import nt from"path";import{fileURLToPath as _o}from"url";function M(){if(se)return se;if(!tt.existsSync(xe))throw new Error(`LBE engine missing: ${xe}`);let e=tt.readFileSync(xe);return se=new WebAssembly.Instance(new WebAssembly.Module(e),{}),se}function ie(){return new Uint8Array(M().exports.memory.buffer)}function rt(){return M().exports.lbe_in_ptr()}function st(){return M().exports.lbe_out_ptr()}function To(){return M().exports.lbe_buf_size()}function it(e){let t=new TextEncoder().encode(e),o=ie(),n=rt();o.set(t,n),o[n+t.length]=0}function at(){let e=ie(),t=st(),o=t;for(;e[o]!==0&&o-t<To();)o++;return new TextDecoder().decode(e.slice(t,o))}function Co(e){let t=ie(),o=rt(),n=new DataView(t.buffer,o);e.forEach((r,s)=>n.setUint32(s*4,r>>>0,!0))}function Po(){let e=ie(),t=st(),o=new DataView(e.buffer,t);return{stage:o.getUint32(0,!0),code:o.getUint32(4,!0)}}function ct(e){Co([e.hasId?1:0,e.idValid?1:0,e.hasCommandId?1:0,e.commandIdValid?1:0,e.hasRequesterId?1:0,e.requesterIdValid?1:0,e.hasSessionId?1:0,e.sessionIdValid?1:0,e.hasTimestamp?1:0,e.timestampValid?1:0,e.hasNonce?1:0,e.nonceValid?1:0,e.hasRequires?1:0,e.requiresValid?1:0,e.hasPayload?1:0,e.hasPayloadAdapter?1:0,e.payloadAdapterValid?1:0,e.hasSignature?1:0,e.hasSignatureAlg?1:0,e.signatureAlgValid?1:0,e.hasSignatureKeyId?1:0,e.hasSignatureSig?1:0,e.signatureSigValid?1:0,e.hasRisk?1:0,e.riskValid?1:0,e.cmdTimestamp>>>0,e.nowSec>>>0,e.maxClockSkewSec>>>0,e.keyIdFormatValid?1:0,e.keyFound?1:0,e.keyNotDeprecated?1:0,e.keyRequesterMatches?1:0,e.keyNotBeforeOk?1:0,e.keyNotExpired?1:0,e.keyLifecycleFieldsPresent?1:0,e.signatureValid?1:0,e.rateLimitOk?1:0,e.rateLimitRetryAfterSec>>>0,e.nonceOk?1:0,e.policyConfigured?1:0,e.requesterConfigured?1:0,e.commandAllowed?1:0,e.adapterAllowed?1:0,e.filesystemRequired?1:0,e.filesystemRootsDefined?1:0,e.filesystemOk?1:0,e.pathDenied?1:0,e.shellRequired?1:0,e.shellCommandOk?1:0]),M().exports.lbe_validate_pipeline();let{stage:t,code:o}=Po();return{ok:t===255,stage:t,stageLabel:Ro[t]||"unknown",code:o,schemaError:t===0?Oo[o]?.error||"Schema invalid":null,keyReason:t===2?No[o]||"KEY_ERROR":null,policyResult:t===6?{...ot[o]||ot[1],code:o}:null,retryAfterSec:t===4?o:0,skewSec:t===1?o:0}}function we({ttlSec:e,nowSec:t,newKey:o,existingEntries:n}){let r=[`${e}:${t}`,o,...n].join(`
|
|
7
|
+
`)+`
|
|
8
|
+
`;if(it(r),M().exports.lbe_nonce_check()!==0)return{ok:!1,updatedEntriesText:null};let i=at();return{ok:!0,updatedEntriesText:i.startsWith(`OK
|
|
9
|
+
`)?i.slice(3):i}}function lt({windowSec:e,maxRequests:t,nowSec:o,requesterId:n,existingEntries:r}){let s=[`${e}:${t}:${o}`,n,...r].join(`
|
|
10
|
+
`)+`
|
|
11
|
+
`;it(s);let i=M().exports.lbe_rate_check()!==0,a=at();if(i){let c=parseInt(a.match(/^EXCEEDED:(\d+)/)?.[1]??"1",10),l=a.replace(/^EXCEEDED:\d+\n/,"");return{ok:!1,retryAfterSec:c,updatedEntriesText:l}}return{ok:!0,retryAfterSec:0,updatedEntriesText:a.startsWith(`OK
|
|
12
|
+
`)?a.slice(3):a}}function dt(e,t=!1){let o=Do[e]??0,n=M().exports.lbe_classify_risk(o,t?1:0);return Lo[n]??"LOW"}var Ao,xe,ot,Oo,No,Ro,Lo,Do,se,ut=L(()=>{Ao=nt.dirname(_o(import.meta.url)),xe=nt.join(Ao,"lbe_engine.wasm"),ot={0:{allowed:!0,reason:null,message:"Policy check passed"},1:{allowed:!1,reason:"POLICY_NOT_CONFIGURED",message:"No policy configured"},2:{allowed:!1,reason:"REQUESTER_NOT_ALLOWED",message:"Requester not in policy"},3:{allowed:!1,reason:"COMMAND_NOT_ALLOWED",message:"Command not allowed for requester"},4:{allowed:!1,reason:"ADAPTER_NOT_ALLOWED",message:"Adapter not allowed"},5:{allowed:!1,reason:"NO_FILESYSTEM_ROOTS_DEFINED",message:"No filesystem roots defined for requester"},6:{allowed:!1,reason:"CWD_OUTSIDE_ALLOWED_ROOT",message:"Path not under allowed roots"},7:{allowed:!1,reason:"PATH_DENIED_BY_PATTERN",message:"Path matches deny pattern"},8:{allowed:!1,reason:"SHELL_CMD_DENIED",message:"Shell command not allowed"}},Oo={0:{valid:!0,error:null},1:{valid:!1,error:"Missing required field: id"},2:{valid:!1,error:"Missing required field: commandId"},3:{valid:!1,error:"Missing required field: requesterId"},4:{valid:!1,error:"Missing required field: sessionId"},5:{valid:!1,error:"Missing required field: timestamp"},6:{valid:!1,error:"Missing required field: nonce"},7:{valid:!1,error:"Missing required field: requires"},8:{valid:!1,error:"Missing required field: payload"},9:{valid:!1,error:"Missing required field: signature"},10:{valid:!1,error:"Field 'id' is invalid"},11:{valid:!1,error:"Field 'commandId' is invalid"},12:{valid:!1,error:"Field 'requesterId' is invalid"},13:{valid:!1,error:"Field 'sessionId' is invalid"},14:{valid:!1,error:"Field 'timestamp' is invalid"},15:{valid:!1,error:"Field 'nonce' is invalid"},16:{valid:!1,error:"Field 'requires' is invalid"},17:{valid:!1,error:"payload: missing required field: adapter"},18:{valid:!1,error:"payload: field 'adapter' is invalid"},19:{valid:!1,error:"signature: missing required field: alg"},20:{valid:!1,error:"signature: missing required field: keyId"},21:{valid:!1,error:"signature: missing required field: sig"},22:{valid:!1,error:"signature: field 'alg' must be ed25519"},23:{valid:!1,error:"signature: field 'sig' is invalid"},24:{valid:!1,error:"Field 'risk' is invalid"}},No={1:"KEY_ID_INVALID",2:"KEY_NOT_TRUSTED",3:"KEY_DEPRECATED",4:"KEY_REQUESTER_MISMATCH",5:"KEY_LIFECYCLE_INVALID",6:"KEY_NOT_YET_VALID",7:"KEY_EXPIRED"},Ro={0:"schema",1:"timestamp",2:"key",3:"signature",4:"rate_limit",5:"nonce",6:"policy",255:"ok"},Lo=["LOW","MEDIUM","HIGH","CRITICAL"],Do={ECHO:0,READ_FILE:1,WRITE_FILE:2,PATCH_FILE:3,DELETE_FILE:4,RUN_SHELL:5},se=null});import _e from"path";function Fo(e){let t=s=>e!=null&&Object.prototype.hasOwnProperty.call(e,s),o=s=>typeof s=="string",n=e?.payload,r=e?.signature;return{hasId:t("id"),idValid:o(e?.id)&&/^[A-Z_]+$/.test(e.id)&&e.id.length>=1&&e.id.length<=50,hasCommandId:t("commandId"),commandIdValid:o(e?.commandId)&&/^[a-f0-9-]+$/.test(e.commandId)&&e.commandId.length===36,hasRequesterId:t("requesterId"),requesterIdValid:o(e?.requesterId)&&e.requesterId.length>=3&&e.requesterId.length<=100,hasSessionId:t("sessionId"),sessionIdValid:o(e?.sessionId)&&e.sessionId.length>=3,hasTimestamp:t("timestamp"),timestampValid:typeof e?.timestamp=="number"&&e.timestamp>=1e9,hasNonce:t("nonce"),nonceValid:o(e?.nonce)&&e.nonce.length>=32&&e.nonce.length<=128,hasRequires:t("requires"),requiresValid:Array.isArray(e?.requires)&&e.requires.length>=1&&e.requires.every(o),hasPayload:t("payload")&&typeof n=="object"&&n!==null&&!Array.isArray(n),hasPayloadAdapter:n!=null&&Object.prototype.hasOwnProperty.call(n,"adapter"),payloadAdapterValid:o(n?.adapter),hasSignature:t("signature")&&typeof r=="object"&&r!==null&&!Array.isArray(r),hasSignatureAlg:r!=null&&Object.prototype.hasOwnProperty.call(r,"alg"),signatureAlgValid:r?.alg==="ed25519",hasSignatureKeyId:r!=null&&Object.prototype.hasOwnProperty.call(r,"keyId"),hasSignatureSig:r!=null&&Object.prototype.hasOwnProperty.call(r,"sig"),signatureSigValid:o(r?.sig)&&r.sig.length>=10,hasRisk:t("risk"),riskValid:["LOW","MEDIUM","HIGH","CRITICAL"].includes(e?.risk)}}function jo(e,t){let o=!!(e&&e.default==="DENY"&&e.requesters&&typeof e.requesters=="object"),n=e?.requesters?.[t.requesterId],r=t.id?.toLowerCase()??"",s=!!n?.allowCommands?.some(y=>y.toLowerCase()===r),i=!!n?.allowAdapters?.includes(t.payload?.adapter),a=!!t.payload?.cwd,c=!1,l=!1,p=!1;if(a){let y=n?.filesystem?.roots??[];if(c=y.length>0,c){let h=_e.resolve(t.payload.cwd);l=y.some(d=>{let g=_e.resolve(d);return h===g||h.startsWith(g+_e.sep)}),p=(n?.filesystem?.denyPatterns??[]).some(d=>new RegExp("^"+d.replace(/\./g,"\\.").replace(/\*\*/g,".*").replace(/\*/g,"[^/]*")+"$").test(h))}}let f=!1,m=!0;if(t.id==="RUN_SHELL"){f=!0;let y=n?.exec?.allowCmds??[],h=n?.exec?.denyCmds??[],u=t.payload?.cmd;h.includes(u)?m=!1:m=y.length===0||y.includes(u)}return{policyConfigured:o,requesterConfigured:!!n,commandAllowed:s,adapterAllowed:i,filesystemRequired:a,filesystemRootsDefined:c,filesystemOk:l,pathDenied:p,shellRequired:f,shellCommandOk:m}}function $o(e,t,o,n=new Date){if(!e||!t)return{keyIdFormatValid:!1,keyFound:!1,keyNotDeprecated:!1,keyRequesterMatches:!1,keyNotBeforeOk:!1,keyNotExpired:!1,keyLifecycleFieldsPresent:!1,publicKey:null};let s=/^[A-Za-z0-9:_-]{3,128}$/.test(t)&&t!=="default";if(!s)return{keyIdFormatValid:s,keyFound:!1,keyNotDeprecated:!1,keyRequesterMatches:!1,keyNotBeforeOk:!1,keyNotExpired:!1,keyLifecycleFieldsPresent:!1,publicKey:null};let i=e.trustedKeys?.[t],a=!!i;if(!a)return{keyIdFormatValid:s,keyFound:a,keyNotDeprecated:!1,keyRequesterMatches:!1,keyNotBeforeOk:!1,keyNotExpired:!1,keyLifecycleFieldsPresent:!1,publicKey:null};let c=!i.deprecated,l=!i.requesterId||i.requesterId===o,p=i.notBefore||i.validFrom,f=i.expiresAt||i.validUntil,m=typeof p=="string"&&typeof f=="string",y=!1,h=!1;if(m){let u=new Date(p),d=new Date(f);!isNaN(u.getTime())&&!isNaN(d.getTime())&&u<d&&(y=n>=u,h=n<d)}return{keyIdFormatValid:s,keyFound:a,keyNotDeprecated:c,keyRequesterMatches:l,keyNotBeforeOk:y,keyNotExpired:h,keyLifecycleFieldsPresent:m,publicKey:i.publicKey??null}}function ft(e){return(e?.entries??[]).map(t=>`${t.key}:${t.timestamp}`)}function pt(e){return e.split(`
|
|
13
|
+
`).filter(Boolean).map(t=>{let o=t.lastIndexOf(":");return{key:t.slice(0,o),timestamp:parseInt(t.slice(o+1),10)||0}})}function Ko(e){return(e?.entries??[]).map(t=>`${t.requesterId}:${t.timestamp}`)}function qo(e){return e.split(`
|
|
14
|
+
`).filter(Boolean).map(t=>{let o=t.lastIndexOf(":");return{requesterId:t.slice(0,o),timestamp:parseInt(t.slice(o+1),10)||0}})}function mt({commandObj:e,pubKeyB64:t,keyStore:o,nonceDb:n,policy:r,rateLimiter:s,policyStatePath:i}){let a={valid:!1,commandId:e?.commandId,checks:{},errors:[]},c=Math.floor(Date.now()/1e3),l=new Date,p=Number.isFinite(r?.security?.maxClockSkewSec)?r.security.maxClockSkewSec:600;if(i&&r?.version!==void 0)try{let k=Ze({policyObj:r,statePath:i});if(a.checks.policyVersion=k.ok,!k.ok)return a.errors.push({type:"POLICY_VERSION_INVALID",message:k.message}),a}catch{a.checks.policyVersion=!0}else a.checks.policyVersion=!0;let f=Fo(e),m=e?.signature?.keyId,y=$o(o,m,e?.requesterId,l),h=!1,u=y.publicKey;if(!u&&t&&(u=t),u){let k={...e};delete k.signature,h=me({payloadObj:k,sigB64:e?.signature?.sig,pubKeyB64:u}).valid}let d=!0,g=0;if(h&&s&&typeof s.db<"u"){let k=r?.requesters?.[e.requesterId]?.rateLimit||{},_=r?.security?.defaultRateLimit||{},K=k.windowSec??_.windowSec??60,Ft=k.maxRequests??_.maxRequests??30,oe=lt({windowSec:K,maxRequests:Ft,nowSec:c,requesterId:e.requesterId,existingEntries:Ko(s.db)});d=oe.ok,g=oe.retryAfterSec,oe.ok&&(s.db.entries=qo(oe.updatedEntriesText))}else if(h&&s&&typeof s.checkAndRecord=="function"){let k=r?.requesters?.[e.requesterId]?.rateLimit||{},_=r?.security?.defaultRateLimit||{},K=s.checkAndRecord({requesterId:e.requesterId,nowSec:c,windowSec:k.windowSec??_.windowSec??60,maxRequests:k.maxRequests??_.maxRequests??30});d=K.ok,g=K.retryAfterSec??0}let I=!0,b=`${e?.requesterId}|${e?.sessionId}|${e?.nonce}`,$=3600;if(h&&d&&n)if(typeof n.checkAndRecord=="function")if(n.db){let k=we({ttlSec:$,nowSec:c,newKey:b,existingEntries:ft(n.db)});I=k.ok,k.ok&&(n.db.entries=pt(k.updatedEntriesText))}else I=n.checkAndRecord({requesterId:e.requesterId,sessionId:e.sessionId,nonce:e.nonce}).ok;else{let k=we({ttlSec:$,nowSec:c,newKey:b,existingEntries:ft(n)});I=k.ok,k.ok&&(n.entries=pt(k.updatedEntriesText))}let O=jo(r,e??{}),w=ct({...f,cmdTimestamp:e?.timestamp??0,nowSec:c,maxClockSkewSec:p,...y,signatureValid:h,rateLimitOk:d,rateLimitRetryAfterSec:g,nonceOk:I,...O}),x=w.stage;if(a.checks.schema=x!==0,x>=1&&(a.checks.timestamp=x!==1),x>=2&&(a.checks.keyId=x!==2),x>=2&&(a.checks.signature=x!==2&&x!==3),x>=4&&(a.checks.rateLimit=x!==4),x>=5&&(a.checks.nonce=x!==5),(x>=6||w.ok)&&(a.checks.policy=x!==6),!w.ok){let k=w.stageLabel;if(k==="schema")a.errors.push({type:"SCHEMA_ERROR",message:w.schemaError||"Schema invalid"});else if(k==="timestamp")a.errors.push({type:"TIMESTAMP_SKEW_EXCEEDED",message:`Command timestamp skew ${w.skewSec}s exceeds allowed ${p}s`});else if(k==="key"){let _=w.keyReason||"KEY_ERROR",K={KEY_ID_INVALID:`Invalid keyId '${m}'`,KEY_NOT_TRUSTED:`Key '${m}' is not in trusted key store`,KEY_DEPRECATED:`Key '${m}' is deprecated`,KEY_REQUESTER_MISMATCH:`Key '${m}' is not authorized for requester '${e?.requesterId}'`,KEY_LIFECYCLE_INVALID:`Key '${m}' must define notBefore and expiresAt`,KEY_NOT_YET_VALID:`Key '${m}' is not yet valid`,KEY_EXPIRED:`Key '${m}' has expired`};a.errors.push({type:_,message:K[_]||_})}else k==="signature"?a.errors.push({type:"SIGNATURE_INVALID",message:u?"Signature verification failed":"No public key available"}):k==="rate_limit"?a.errors.push({type:"RATE_LIMIT_EXCEEDED",message:`Rate limit exceeded. Retry after ${w.retryAfterSec}s`}):k==="nonce"?a.errors.push({type:"REPLAY_NONCE",message:"Nonce has already been used"}):k==="policy"&&w.policyResult?a.errors.push({type:w.policyResult.reason,message:w.policyResult.message}):a.errors.push({type:"VALIDATION_FAILED",message:`Failed at stage: ${k}`});return a}return a.valid=!0,a.risk=dt(e.id,e.payload?.cmd==="rm"),a.message="Command validation successful",a}var yt=L(()=>{Z();et();ut()});async function gt(e){return{adapter:"noop",commandId:e.commandId||"unknown",command:e.id||"unknown",status:"completed",output:`[NOOP] Would execute: ${e.id||"unknown"} on adapter: ${e.payload?.adapter||"unknown"}`,exitCode:0,timestamp:new Date().toISOString()}}var ht=L(()=>{});import{spawnSync as Mo}from"child_process";import Ae from"path";import Vo from"fs";function St(e){try{return Vo.realpathSync(Ae.resolve(e))}catch{return Ae.resolve(e)}}function Bo(e){if(e===void 0)return{ok:!0,args:[]};if(!Array.isArray(e))return{ok:!1,error:"payload.args must be an array"};let t=[];for(let o of e){if(typeof o!="string"&&typeof o!="number"&&typeof o!="boolean")return{ok:!1,error:"payload.args may only contain string, number, or boolean values"};t.push(String(o))}return{ok:!0,args:t}}async function kt(e,t,o){let n=e.payload,r=Math.min(Math.max(Number(n.timeoutMs)||3e4,1),3e4),s=Math.min(Math.max(Number(n.maxOutputBytes)||1024*1024,1024),1024*1024);if(n.adapter!=="shell")return{adapter:"shell",commandId:e.commandId,status:"error",error:"Adapter mismatch",exitCode:1};let i=o?.exec?.allowCmds||[];if((o?.exec?.denyCmds||[]).includes(n.cmd))return{adapter:"shell",commandId:e.commandId,status:"blocked",error:`Command '${n.cmd}' is denied`,exitCode:2};if(i.length>0&&!i.includes(n.cmd))return{adapter:"shell",commandId:e.commandId,status:"blocked",error:`Command '${n.cmd}' not in allowlist`,exitCode:2};if(!(o?.filesystem?.roots||[]).some(f=>{let m=St(f),y=St(n.cwd);return y===m||y.startsWith(m+Ae.sep)}))return{adapter:"shell",commandId:e.commandId,status:"blocked",error:`CWD '${n.cwd}' not authorized`,exitCode:2};let p=Bo(n.args);if(!p.ok)return{adapter:"shell",commandId:e.commandId,status:"blocked",error:p.error,exitCode:2};try{let f=Mo(n.cmd,p.args,{cwd:n.cwd,timeout:r,encoding:"utf8",maxBuffer:s,stdio:["pipe","pipe","pipe"],shell:!1});if(f.error)throw f.error;let m=`${f.stdout||""}${f.stderr||""}`,y=f.status??1;return y!==0?{adapter:"shell",commandId:e.commandId,command:n.cmd,status:"error",error:m.substring(0,s)||`Command exited with code ${y}`,exitCode:y,timestamp:new Date().toISOString()}:{adapter:"shell",commandId:e.commandId,command:n.cmd,status:"completed",output:m.substring(0,s),exitCode:0,timestamp:new Date().toISOString()}}catch(f){return{adapter:"shell",commandId:e.commandId,command:n.cmd,status:"error",error:f.message,exitCode:f.status||1,timestamp:new Date().toISOString()}}}var Et=L(()=>{});import V from"fs";import ae from"path";import Yo from"crypto";function It(e,t){let o=t||ae.resolve(".lbe/data/backups");V.existsSync(o)||V.mkdirSync(o,{recursive:!0});let n=ae.resolve(e),r=V.existsSync(n),s=null,i=null;r&&(s=V.readFileSync(n),i=Yo.createHash("sha256").update(s).digest("hex"));let a=ae.basename(n).replace(/[^a-zA-Z0-9._-]/g,"_"),c=`${Date.now()}-${i?i.slice(0,8):"new"}-${a}`,l=r?ae.join(o,c):null;return r&&s!==null&&D(l,s),{originalPath:n,backupPath:l,existed:r,hash:i,createdAt:new Date().toISOString()}}function ce(e){if(!e)return{restored:!1,error:"No backup metadata"};let{originalPath:t,backupPath:o,existed:n}=e;if(!n)try{return V.existsSync(t)&&V.unlinkSync(t),{restored:!0,action:"deleted"}}catch(r){return{restored:!1,error:r.message}}if(!o||!V.existsSync(o))return{restored:!1,error:"Backup file not found at: "+o};try{let r=V.readFileSync(o);return D(t,r),{restored:!0,action:"restored"}}catch(r){return{restored:!1,error:r.message}}}var bt=L(()=>{z()});import H from"fs";import B from"path";function Wo(e,t){return e?B.isAbsolute(e)?B.resolve(e):B.resolve(t||process.cwd(),e):null}function Go(e,t){let o=vt(e);return t.some(n=>{let r=vt(n);return o===r||o.startsWith(r+B.sep)})}function vt(e){let t=B.resolve(e),o=[];for(;!H.existsSync(t);){let n=B.dirname(t);if(n===t)break;o.unshift(B.basename(t)),t=n}try{t=H.realpathSync(t)}catch{}return B.join(t,...o)}function Ho(e,t){for(let o of t||[])if(new RegExp("^"+o.replace(/\./g,"\\.").replace(/\*\*/g,".*").replace(/\*/g,"[^/\\\\]*")+"$").test(e))return o;return null}function X(e,t,o,n=2){return{adapter:"file",commandId:e.commandId,status:"blocked",errorCode:t,error:o,exitCode:n}}function j(e,t,o,n=null,r=1){return{adapter:"file",commandId:e.commandId,status:"error",errorCode:t,error:o,backup:n?le(n):null,exitCode:r}}function le(e){return e?{path:e.backupPath,existed:e.existed,hash:e.hash,createdAt:e.createdAt}:null}async function xt(e,t,o){let n=e.payload,r=n.action,s=n.cwd||process.cwd(),i=Wo(n.target,s);if(!r)return X(e,"FILE_NO_ACTION","payload.action is required");if(!i&&r!=="noop")return X(e,"FILE_NO_TARGET","payload.target is required");let a=o?.filesystem?.roots||[];if(a.length===0)return X(e,"FILE_NO_ROOTS","No filesystem roots defined for requester");if(!Go(i,a))return X(e,"FILE_OUTSIDE_ROOT",`'${i}' is outside allowed roots`);let c=Ho(i,o?.filesystem?.denyPatterns);if(c)return X(e,"FILE_PATH_DENIED",`'${i}' matches deny pattern: ${c}`);switch(r){case"read":return Jo(e,i);case"write":return zo(e,i,n);case"patch":return Xo(e,i,n);case"delete":return Qo(e,i);default:return X(e,"FILE_UNKNOWN_ACTION",`Unknown action: '${r}'`)}}function Jo(e,t){if(!H.existsSync(t))return j(e,"FILE_NOT_FOUND",`Not found: ${t}`);try{let o=H.statSync(t);if(o.size>Uo)return j(e,"FILE_TOO_LARGE","File exceeds 10 MB read limit");let n=H.readFileSync(t,"utf8");return{adapter:"file",action:"read",commandId:e.commandId,status:"completed",target:t,output:n,bytesRead:o.size,exitCode:0}}catch(o){return j(e,"FILE_READ_ERROR",o.message)}}function zo(e,t,o){let n=o.content;if(n==null)return j(e,"FILE_MISSING_CONTENT","payload.content is required for write");let r=Oe(t);try{return D(t,n,{encoding:"utf8"}),{adapter:"file",action:"write",commandId:e.commandId,status:"completed",target:t,backup:le(r),output:`Wrote ${Buffer.byteLength(n,"utf8")} bytes to ${t}`,exitCode:0}}catch(s){return ce(r),j(e,"FILE_WRITE_ERROR",s.message,r)}}function Xo(e,t,o){let n=o.content;if(n==null)return j(e,"FILE_MISSING_CONTENT","payload.content is required for patch");let r=Oe(t);try{return D(t,n,{encoding:"utf8"}),{adapter:"file",action:"patch",commandId:e.commandId,status:"completed",target:t,backup:le(r),output:`Patched ${t} (${Buffer.byteLength(n,"utf8")} bytes)`,exitCode:0}}catch(s){return ce(r),j(e,"FILE_PATCH_ERROR",s.message,r)}}function Qo(e,t){if(!H.existsSync(t))return j(e,"FILE_NOT_FOUND",`Not found: ${t}`);let o=Oe(t);try{return H.unlinkSync(t),{adapter:"file",action:"delete",commandId:e.commandId,status:"completed",target:t,backup:le(o),output:`Deleted ${t}`,exitCode:0}}catch(n){return ce(o),j(e,"FILE_DELETE_ERROR",n.message,o)}}function Oe(e){try{return It(e)}catch{return null}}var Uo,wt=L(()=>{z();bt();Uo=10*1024*1024});function Zo(e){return _t[e]}async function At(e,t,o,n){let r=Zo(e);if(!r)return{adapter:e,commandId:t.commandId,status:"error",error:`Adapter '${e}' not found`,exitCode:1};try{return await r(t,o,n)}catch(s){return{adapter:e,commandId:t.commandId,status:"error",error:`Adapter execution failed: ${s.message}`,exitCode:9}}}var _t,Dr,Ot=L(()=>{ht();Et();wt();_t={noop:gt,shell:kt,file:xt};Dr=Object.keys(_t)});var Dt={};$t(Dt,{createLocalExecutor:()=>sn});import Nt from"crypto";import J from"fs";import N from"path";function T(e,t,o=!1){return{ok:!1,decision:"deny",executed:!1,dryRun:!1,error:{code:e,message:t,recoverable:o}}}function en(e,t,o={}){let n=new Date,r=new Date(n.getTime()+365*24*60*60*1e3);return{version:1,default:"DENY",requesters:{[t]:{allowCommands:Object.values(Lt).map(s=>s.id),allowAdapters:["file","shell"],filesystem:{roots:[e],denyPatterns:[]},exec:{allowCmds:o.allowCommands||[],denyCmds:o.denyCommands||[]},rateLimit:{windowSec:60,maxRequests:o.maxRequests||60}}},security:{maxClockSkewSec:600,defaultRateLimit:{windowSec:60,maxRequests:60}},_keyWindow:{notBefore:n.toISOString(),expiresAt:r.toISOString()}}}function Rt(e){let t=N.resolve(e),o=[];for(;!J.existsSync(t);){let n=N.dirname(t);if(n===t)break;o.unshift(N.basename(t)),t=n}try{t=J.realpathSync(t)}catch{}return N.join(t,...o)}function Re(e,t){let o=Rt(e),n=Rt(t);return o===n||o.startsWith(n+N.sep)}function on(e,t){if(typeof e!="string")return null;for(let o of tn)if(o.test(e))return T("PAYLOAD_CONTENT_REJECTED",`Forbidden pattern in ${t}: ${o}`);return null}function nn(e,t,o={}){if(!t||typeof t!="object")return{error:T("REQUEST_INVALID","request must be an object")};let n=Lt[t.intent];if(!n)return{error:T("INTENT_UNSUPPORTED",`Unsupported intent '${t.intent}'`)};let r=typeof t.actor=="string"&&t.actor?t.actor:"agent:local",s=null;if(n.adapter==="file"){if(typeof t.target!="string"||!t.target)return{error:T("TARGET_REQUIRED","target is required for file intents")};if(s=N.resolve(e,t.target),!Re(s,e))return{error:T("PATH_OUTSIDE_ROOT","target is outside project root")};if(["write_file","patch_file"].includes(t.intent)&&typeof t.content!="string")return{error:T("CONTENT_REQUIRED","content is required for write and patch")};let a=on(t.content,"content");if(a)return{error:a}}let i=null;if(n.adapter==="shell"){if(i=t.command,!i||typeof i.cmd!="string"||!Array.isArray(i.args)||i.args.some(c=>typeof c!="string"))return{error:T("COMMAND_INVALID","command requires cmd and string args")};let a=N.resolve(e,i.cwd||".");if(!Re(a,e))return{error:T("CWD_OUTSIDE_ROOT","command cwd is outside project root")};if(!Array.isArray(o.allowCommands)||!o.allowCommands.includes(i.cmd))return{error:T("SHELL_NOT_ALLOWLISTED",`command '${i.cmd}' is not explicitly allowlisted`)};if(o.denyCommands?.includes(i.cmd))return{error:T("SHELL_DENIED",`command '${i.cmd}' is denied`)};i={...i,cwd:a,timeoutMs:Math.min(Math.max(i.timeoutMs||3e4,1),3e4),maxOutputBytes:Math.min(Math.max(i.maxOutputBytes||1024*1024,1024),1024*1024)}}return{actor:r,detail:n,target:s,command:i,request:t}}function rn(e,t,o){let{actor:n,detail:r,target:s,command:i,request:a}=e,c={id:r.id,risk:Ne.has(a.intent)?"MEDIUM":"LOW",commandId:Nt.randomUUID(),requesterId:n,sessionId:"local-host",timestamp:Math.floor(Date.now()/1e3),nonce:Nt.randomBytes(32).toString("hex"),requires:["policy","signature"],payload:{adapter:r.adapter,action:r.action,target:s,content:a.content,cmd:i?.cmd,args:i?.args,timeoutMs:i?.timeoutMs,maxOutputBytes:i?.maxOutputBytes,cwd:i?.cwd||(s?N.dirname(s):process.cwd())}},l=re({payloadObj:c,secretKeyB64:o});if(l.error)throw new Error(l.error);return{...c,signature:{alg:"ed25519",keyId:t,sig:l.signature}}}function sn(e={}){let t=N.resolve(e.rootDir||process.cwd()),o=e.keyId||"host:local-exec",n=e.keyPair||ne(),r=e.shell||{};function s(u,{recordNonce:d=!1}={}){let g=nn(t,u,r);if(g.error)return g;let I=G(t,e.mode||"enforce"),b=be(I.policy,t,{target:g.target,command:g.command?.cmd});if(I.policy.mode==="enforce"&&!b.allowed)return{error:T("LOCAL_POLICY_DENY",`Blocked by rule(s): ${b.winningRules.map(K=>K.id).join(", ")}`),local:I,localDecision:b,normalized:g};let O=en(t,g.actor,r),w={defaultKeyId:o,trustedKeys:{[o]:{publicKey:n.publicKey,notBefore:O._keyWindow.notBefore,expiresAt:O._keyWindow.expiresAt,deprecated:!1}}};delete O._keyWindow;let x=rn(g,o,n.secretKey),_=mt({commandObj:x,keyStore:w,nonceDb:d?{entries:[]}:{entries:[]},policy:O});return _.valid?{local:I,localDecision:b,normalized:g,proposal:x,policy:O,validation:_}:{error:T(_.errors[0]?.type||"VALIDATION_FAILED",_.errors[0]?.message||"Validation failed"),local:I,localDecision:b,normalized:g,proposal:x,policy:O,validation:_}}function i(u){let d=G(t,e.mode||"observe"),g=d.policy.mode,I=null,b=null;if(u.path)try{if(I=N.resolve(t,u.path),!Re(I,t))return{decision:"deny",deny:!0,matchedRules:["path:outside_root"],mode:g,enforced:g==="enforce",reason:"PATH_OUTSIDE_ROOT"}}catch{}u.cmd&&(b=u.cmd);let $=be(d.policy,t,{target:I,command:b}),O=!$.allowed;return{decision:O?"deny":"allow",deny:O,matchedRules:$.winningRules.map(w=>w.id),mode:g,enforced:g==="enforce"}}function a(u){let d=N.join(t,".lbe","events.jsonl"),g=N.dirname(d);J.existsSync(g)||J.mkdirSync(g,{recursive:!0});let I=JSON.stringify({ts:Math.floor(Date.now()/1e3),...u})+`
|
|
15
|
+
`,b=J.openSync(d,"a");try{J.writeSync(b,I)}finally{J.closeSync(b)}}async function c(u){let d=s(u);return d.error?{...d.error,dryRun:!0}:{ok:!0,decision:d.local.policy.mode==="observe"?"observe":"allow",executed:!1,dryRun:!0,matchedRules:d.localDecision.winningRules.map(g=>g.id),rollback:{available:Ne.has(d.normalized.request.intent),performed:!1}}}async function l(u){let d=s(u,{recordNonce:!0});if(d.error)return He(t,{action:u?.intent,actor:u?.actor||"agent:local",decision:"deny",error:d.error.error.code}),d.error;if(d.local.policy.mode==="observe")return ee(N.join(t,".lbe/audit.jsonl"),{kind:"local_execution",commandId:d.proposal.commandId,requesterId:d.normalized.actor,intent:d.normalized.request.intent,decision:"observe",status:"observed"}),{ok:!0,decision:"observe",executed:!1,dryRun:!1,matchedRules:d.localDecision.winningRules.map(O=>O.id),rollback:{available:!1,performed:!1}};let g=d.policy.requesters[d.normalized.actor],I=await At(d.normalized.detail.adapter,d.proposal,d.policy,g),b=I.status==="completed",$=ee(N.join(t,".lbe/audit.jsonl"),{kind:"local_execution",commandId:d.proposal.commandId,requesterId:d.normalized.actor,intent:d.normalized.request.intent,decision:b?"allow":"deny",status:I.status});return{ok:b,decision:b?"allow":"deny",executed:b,dryRun:!1,matchedRules:d.localDecision.winningRules.map(O=>O.id),auditId:$.hash,rollback:{available:Ne.has(d.normalized.request.intent),performed:!1,backupId:I.backup?.hash},...b?{}:{error:{code:I.errorCode||"EXECUTION_FAILED",message:I.error||"Execution failed",recoverable:!0}}}}return{rootDir:t,writeFile:(u,d)=>l({intent:"write_file",target:u,content:d}),readFile:u=>l({intent:"read_file",target:u}),patchFile:(u,d)=>l({intent:"patch_file",target:u,content:d}),deleteFile:u=>l({intent:"delete_file",target:u}),runShell:(u,d=[],g={})=>l({intent:"run_shell",command:{cmd:u,args:d,...g}}),validate:async u=>({...await c(u),dryRun:!1,executed:!1}),dryRun:c,execute:l,policy:{read:()=>G(t,e.mode||"enforce").policy,proposeRule:Ge,addRule:u=>We(t,u,e.mode||"enforce")},audit:{verify:()=>Be(N.join(t,".lbe/audit.jsonl"))},evaluateSync:i,auditSync:a}}var Lt,Ne,tn,Tt=L(()=>{Z();yt();Ot();ke();ve();Lt={read_file:{id:"READ_FILE",adapter:"file",action:"read"},write_file:{id:"WRITE_FILE",adapter:"file",action:"write"},patch_file:{id:"PATCH_FILE",adapter:"file",action:"patch"},delete_file:{id:"DELETE_FILE",adapter:"file",action:"delete"},run_shell:{id:"RUN_SHELL",adapter:"shell",action:"run"}},Ne=new Set(["write_file","patch_file","delete_file"]);tn=[/\beval\s*\(/i,/\bFunction\s*\(/i,/\bexec\s*\(/i,/\brequire\s*\(/,/\bimport\s*\(/,/\bchild_process\b/,/\b__proto__\b/,/\bconstructor\s*\[/,/evalScript/i]});import E from"fs";import A from"path";import{spawn as de}from"child_process";import{fileURLToPath as an}from"url";Z();import S from"fs";import v from"path";import no from"readline";Z();import bn from"fs";import xn from"path";import gn from"fs";import Sn from"path";function De({policyObj:e,secretKeyB64:t,keyId:o}){let n=re({payloadObj:e,secretKeyB64:t});return n.error?{ok:!1,reason:"POLICY_SIGNATURE_CREATE_FAILED",message:n.error,envelope:null}:{ok:!0,reason:null,message:"Policy signature created",envelope:{alg:"ed25519",keyId:o,sig:n.signature,createdAt:Math.floor(Date.now()/1e3)}}}import ye from"fs";import C from"path";var Kt=[{file:"package.json",type:"node"},{file:"pyproject.toml",type:"python"},{file:"requirements.txt",type:"python"},{file:"go.mod",type:"go"},{file:"Cargo.toml",type:"rust"},{file:"pom.xml",type:"java"},{file:"build.gradle",type:"java"},{file:"build.gradle.kts",type:"java"},{file:"Dockerfile",type:"docker"},{file:"docker-compose.yml",type:"docker"},{dir:".github/workflows",type:"ci"},{file:".gitlab-ci.yml",type:"ci"},{dir:".circleci",type:"ci"},{file:"Jenkinsfile",type:"ci"},{file:".travis.yml",type:"ci"}],ge=["node","python","go","rust","java"],qt={source:["src","lib","app","pages","components","core","api","server","client","pkg","cmd"],generated:["dist","build",".next","out","coverage","target",".cache","__pycache__",".turbo"],tests:["test","tests","__tests__","spec","e2e"],docs:["docs","doc","documentation"]},Pe=[".env",".env.*","keys/**","secrets/**","*.key","*.pem","*.p12","*.pfx","*.crt"],Mt=["node_modules/**",".git/**"],Vt={node:["package-lock.json","yarn.lock","pnpm-lock.yaml"],python:["Pipfile.lock","poetry.lock"],go:["go.sum"],rust:["Cargo.lock"],java:["gradle/wrapper/**"],docker:[],ci:[],generic:[]},Te={node:["package.json","tsconfig*.json","jest.config.*","vite.config.*","next.config.*","webpack.config.*",".eslintrc*",".eslint.config.*",".prettierrc*","babel.config.*"],python:["pyproject.toml","setup.py","setup.cfg","tox.ini","pytest.ini","mypy.ini",".flake8",".pylintrc","Pipfile"],go:["go.mod",".golangci.yml",".golangci.yaml"],rust:["Cargo.toml","rust-toolchain.toml","clippy.toml",".rustfmt.toml"],java:["pom.xml","build.gradle","build.gradle.kts","gradle.properties","settings.gradle","settings.gradle.kts"],docker:["Dockerfile","docker-compose.yml",".dockerignore"],ci:[".gitlab-ci.yml","Jenkinsfile",".travis.yml"],generic:["Makefile","CMakeLists.txt","meson.build"]},Bt=[".editorconfig",".nvmrc",".node-version",".python-version"],Yt=["config",".github",".gitlab",".circleci",".vscode"],Ut={node:"dependency and build config",python:"package and environment config",go:"module definition",rust:"crate manifest",java:"build definition",docker:"container config",ci:"pipeline definition",generic:"project config"},Wt={node:"package manager",python:"dependency resolver",go:"module checksums",rust:"dependency resolver",java:"Gradle wrapper"},Gt=["composer.json","Gemfile","mix.exs","pubspec.yaml","Package.swift","project.clj","build.sbt","stack.yaml","deno.json","deno.jsonc","Podfile"],Ht=["composer.lock","Gemfile.lock","mix.lock","pubspec.lock","Package.resolved"],Jt=[".csproj",".fsproj",".sln",".cabal"];function F(e){return ye.existsSync(e)}function Ce(e,t){return t.filter(o=>F(C.join(e,o))).map(o=>`${o}/**`)}function zt(e){let t=C.join(e,".gitignore");return F(t)?ye.readFileSync(t,"utf8").split(`
|
|
16
|
+
`).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#")&&!o.startsWith("!")).map(o=>o.endsWith("/")?o+"**":o):[]}function U(e){return e.filter((t,o,n)=>t&&n.indexOf(t)===o)}function Xt(e){let t=new Set,o=[];for(let n of Kt){if(t.has(n.type))continue;let r=C.join(e,n.file||n.dir);F(r)&&(t.add(n.type),o.push(n.type))}return o.length>0?o:["generic"]}function Qt(e){return ge.find(t=>e.includes(t))??"generic"}function Zt(e){let t=Gt.filter(n=>F(C.join(e,n))),o=Ht.filter(n=>F(C.join(e,n)));try{let n=ye.readdirSync(e);for(let r of n)Jt.some(s=>r.endsWith(s))&&t.push(r)}catch{}return{manifests:t,lockfiles:o}}function eo(e,t){let o={};for(let[r,s]of Object.entries(qt))o[r]=Ce(e,s);o.secrets=Pe.filter(r=>{let s=r.split("/")[0].replace(/\*.*/,"");return s.includes("*")||F(C.join(e,s))});let n=U(t.flatMap(r=>Te[r]||Te.generic).concat(Bt));if(o.config=U([...n.filter(r=>!r.includes("*")&&!r.endsWith("/**")&&F(C.join(e,r))),...n.filter(r=>r.endsWith("/**")&&F(C.join(e,r.replace("/**","")))),...Ce(e,Yt)]),o.lockfiles=U(t.flatMap(r=>Vt[r]||[]).filter(r=>{let s=r.replace(/\*.*/,"").split("/")[0];return s.includes("*")||F(C.join(e,s))})),!t.some(r=>ge.includes(r))){let r=Zt(e);o.config=U([...o.config,...r.manifests]),o.lockfiles=U([...o.lockfiles,...r.lockfiles])}return o}function to(e,t,o){let n={};if(n.structure="Preserve the existing folder structure. Add new files within established directories. Do not create top-level directories, reorganize, or rename existing folders.",o.source.length>0&&(n.source=`Source code lives in ${o.source.join(", ")}. Make feature changes and bug fixes here only.`),n.secrets=`Never propose changes to credential or key files (${Pe.slice(0,4).join(", ")} \u2026). These are never task targets regardless of the instruction.`,o.generated.length>0&&(n.generated=`${o.generated.join(", ")} contain generated output. Modify the source files that produce them; never write to generated directories directly.`),o.config.length>0){let r=e.filter(c=>ge.includes(c)),s=r.length===1?Ut[r[0]]:"project configuration",i=o.config.slice(0,5).join(", "),a=o.config.length>5?" and related files":"";n.config=`Treat ${i}${a} as ${s} files. Do not modify them unless the task explicitly requires a configuration or dependency change.`}if(o.tests.length>0&&(n.tests=`Test files in ${o.tests.join(", ")} validate behavior. Update them only when the behavior they cover changes.`),o.lockfiles?.length>0){let r=Wt[t]||"tooling",s=o.lockfiles.slice(0,3).join(", ");n.lockfiles=`${s} are generated by the ${r}. Never edit them directly.`}if(t==="generic"){let r=o.config.filter(s=>!s.endsWith("/**"));r.length>0?n.unknown=`This project uses an unrecognized toolchain. Treat ${r.slice(0,3).join(", ")} as dependency/manifest files. Do not modify them unless the task explicitly requires a dependency change.`:n.unknown="This project uses an unrecognized toolchain. Do not assume standard source layouts, dependency files, or build conventions apply. Confirm any structural assumption before acting."}return e.includes("docker")&&(n.docker="Dockerfile and docker-compose.yml define the container environment. Treat them as infrastructure config \u2014 only modify when the task explicitly involves container or environment changes."),e.includes("ci")&&(n.ci="CI config files (.github/**, .gitlab-ci.yml, etc.) define the build and deployment pipeline. Do not modify them unless the task explicitly involves CI/CD changes."),n}function oo(e,t){let o=U([...e.source,...e.docs,...e.tests]),n=[...e.config],r=U([...e.secrets,...e.generated,...e.lockfiles||[],...Mt,...t.filter(s=>s.endsWith("/**")).slice(0,8)]);return{allow:o.length>0?o:["src/**"],approval:n.length>0?n:[],deny:r}}function Fe(e){let t=C.resolve(e||process.cwd()),o=Xt(t),n=Qt(o),r=eo(t,o),s=zt(t),i=to(o,n,r),a=oo(r,s);return{projectTypes:o,primaryType:n,surfaces:r,semantics:i,enforcement:a}}function je(e,t,o){let n=[],r=Array.isArray(e)?e.join(" + "):e;n.push(`Detected: ${r}`),n.push(""),n.push("Agent semantics:");for(let[,s]of Object.entries(t))n.push(` - ${s}`);return n.push(""),n.push("Enforcement:"),o.allow.length&&n.push(` allow: ${o.allow.join(", ")}`),o.approval.length&&n.push(` approval: ${o.approval.join(", ")}`),o.deny.length&&n.push(` deny: ${o.deny.slice(0,6).join(", ")}${o.deny.length>6?" \u2026":""}`),n.join(`
|
|
17
|
+
`)}function ro(e){return process.stdin.isTTY?new Promise(t=>{let o=no.createInterface({input:process.stdin,output:process.stdout});o.question(e,n=>{o.close(),t(n.trim().toLowerCase())})}):Promise.resolve("y")}function so(e){return{...e,deny:[...new Set([...e.deny,...e.approval,"*.json","config/**"])],approval:[]}}function io(e){return{...e,approval:[]}}function ao(e){let t=new Date().toISOString(),o=new Date(Date.now()+4320*60*60*1e3).toISOString(),n="agent:gpt-v1-2026Q1",r="policy-signer-v1-2026Q1",s=v.join(e,".lbe");for(let g of["config","keys","data"])S.mkdirSync(v.join(s,g),{recursive:!0});let i={".lbe/data/nonce.db.json":JSON.stringify({entries:[]},null,2),".lbe/data/rate-limit.db.json":JSON.stringify({entries:[]},null,2),".lbe/data/policy.state.json":JSON.stringify({schemaVersion:"1",lastAccepted:null,updatedAt:null},null,2),".lbe/data/audit.log.jsonl":""};for(let[g,I]of Object.entries(i)){let b=v.join(e,g);S.existsSync(b)||S.writeFileSync(b,I)}let a=v.join(s,"keys"),c=v.join(a,"public.key"),l=v.join(a,"secret.key"),p,f;if(S.existsSync(c)&&S.existsSync(l))p=S.readFileSync(c,"utf8").trim(),f=S.readFileSync(l,"utf8").trim();else{let g=ne();p=g.publicKey,f=g.secretKey,S.writeFileSync(c,p),S.writeFileSync(l,f,{mode:384})}let m=v.join(s,"config/keys.json"),y=S.existsSync(m)?JSON.parse(S.readFileSync(m,"utf8")):{schemaVersion:"1",defaultKeyId:n,trustedKeys:{}};for(let g of[n,r])y.trustedKeys[g]||(y.trustedKeys[g]={publicKey:p,notBefore:t,expiresAt:o,validFrom:t,validUntil:o,deprecated:!1});y.defaultKeyId=n,S.writeFileSync(m,JSON.stringify(y,null,2));let h=v.join(s,"config/policy.default.json"),u;S.existsSync(h)?u=JSON.parse(S.readFileSync(h,"utf8")):(u={default:"DENY",version:"1.0.0",createdAt:t,security:{maxClockSkewSec:600,maxPolicyCreatedAtSkewSec:31536e3,defaultRateLimit:{windowSec:60,maxRequests:30}},requesters:{"agent:gpt":{allowAdapters:["noop","shell"],allowCommands:["RUN_SHELL"],rateLimit:{windowSec:60,maxRequests:30},filesystem:{roots:[e],denyPatterns:["**/.git/**","**/secrets/**","**/*.key"]},exec:{allowCmds:["ls","node","python","echo"],denyCmds:["rm","chmod","chown","curl","wget","su","sudo"]}}}},S.writeFileSync(h,JSON.stringify(u,null,2)));let d=De({policyObj:u,secretKeyB64:f,keyId:r});return d.ok&&S.writeFileSync(v.join(s,"config/policy.sig.json"),JSON.stringify(d.envelope,null,2)),{defaultKeyId:n,secretKeyB64:f,publicKeyB64:p}}function co(){return`# LBE Governance Contract
|
|
2361
18
|
|
|
2362
19
|
This project has LetterBlack LBE (Local-first execution Governance) active.
|
|
2363
20
|
|
|
@@ -2404,489 +61,28 @@ const proposal = lbe.policy.proposeRule({
|
|
|
2404
61
|
- Policy: \`.lbe/policy.json\`
|
|
2405
62
|
- Audit: \`.lbe/audit.jsonl\`
|
|
2406
63
|
- Status: \`npx lbe-exec status\`
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
}
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
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
|
-
}
|
|
2442
|
-
}
|
|
2443
|
-
}
|
|
2444
|
-
return removed;
|
|
2445
|
-
}
|
|
2446
|
-
async function initCommand(opts2 = {}) {
|
|
2447
|
-
const cwd = process.cwd();
|
|
2448
|
-
const yes = opts2.yes || opts2.y || !process.stdin.isTTY;
|
|
2449
|
-
const lbeDir = path4.join(cwd, ".lbe");
|
|
2450
|
-
fs4.mkdirSync(lbeDir, { recursive: true });
|
|
2451
|
-
const outPath = path4.join(lbeDir, "workspace.json");
|
|
2452
|
-
console.log("\nScanning workspace...\n");
|
|
2453
|
-
const { projectTypes, primaryType: primaryType2, semantics, enforcement } = scanWorkspace(cwd);
|
|
2454
|
-
console.log(formatSummary(projectTypes, semantics, enforcement));
|
|
2455
|
-
console.log("");
|
|
2456
|
-
let finalEnforcement = enforcement;
|
|
2457
|
-
if (!yes) {
|
|
2458
|
-
const answer = await ask("Accept? [Y = accept / s = strict / r = relaxed / n = cancel] ");
|
|
2459
|
-
if (answer === "n") {
|
|
2460
|
-
console.log("Cancelled.");
|
|
2461
|
-
return { success: false };
|
|
2462
|
-
}
|
|
2463
|
-
if (answer === "s") finalEnforcement = applyStrict(enforcement);
|
|
2464
|
-
if (answer === "r") finalEnforcement = applyRelaxed(enforcement);
|
|
2465
|
-
}
|
|
2466
|
-
const contract = {
|
|
2467
|
-
lbe: true,
|
|
2468
|
-
version: "0.4.0",
|
|
2469
|
-
state: "local",
|
|
2470
|
-
projectTypes,
|
|
2471
|
-
primaryType: primaryType2,
|
|
2472
|
-
semantics,
|
|
2473
|
-
enforcement: finalEnforcement
|
|
2474
|
-
};
|
|
2475
|
-
fs4.writeFileSync(outPath, JSON.stringify(contract, null, 2));
|
|
2476
|
-
console.log("\u2713 Wrote .lbe/workspace.json");
|
|
2477
|
-
setupCrypto(cwd);
|
|
2478
|
-
const localPolicyPath = path4.join(lbeDir, "policy.json");
|
|
2479
|
-
if (!fs4.existsSync(localPolicyPath)) {
|
|
2480
|
-
fs4.writeFileSync(localPolicyPath, JSON.stringify({ version: 1, mode: "observe", workspace: cwd, rules: [] }, null, 2) + "\n");
|
|
2481
|
-
}
|
|
2482
|
-
const localAuditPath = path4.join(lbeDir, "audit.jsonl");
|
|
2483
|
-
if (!fs4.existsSync(localAuditPath)) fs4.writeFileSync(localAuditPath, "");
|
|
2484
|
-
console.log("\u2713 Keys and policy ready (.lbe/)");
|
|
2485
|
-
writeAgentContract(cwd);
|
|
2486
|
-
console.log("\u2713 Agent contract written \u2192 .lbe/AGENT_CONTRACT.md");
|
|
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");
|
|
2494
|
-
return { success: true, contract };
|
|
2495
|
-
}
|
|
2496
|
-
|
|
2497
|
-
// src/cli/commands/policyMode.js
|
|
2498
|
-
init_localPolicy();
|
|
2499
|
-
async function policyModeCommand(mode, opts2 = {}) {
|
|
2500
|
-
const loaded = loadLocalPolicy(opts2.root || process.cwd(), mode);
|
|
2501
|
-
writeLocalPolicy(loaded.root, { ...loaded.policy, mode });
|
|
2502
|
-
console.log(JSON.stringify({ mode, policy: loaded.policyPath }, null, 2));
|
|
2503
|
-
}
|
|
2504
|
-
|
|
2505
|
-
// exec/cli.js
|
|
2506
|
-
var [, , cmd, ...rest] = process.argv;
|
|
2507
|
-
var opts = Object.fromEntries(
|
|
2508
|
-
rest.flatMap((v, i, a) => v.startsWith("--") ? [[v.slice(2), a[i + 1] ?? true]] : [])
|
|
2509
|
-
);
|
|
2510
|
-
var positional = rest.filter((v) => !v.startsWith("--") && rest[rest.indexOf(v) - 1]?.startsWith("--") === false);
|
|
2511
|
-
var __dir = path15.dirname(fileURLToPath2(import.meta.url));
|
|
2512
|
-
function loadPolicy() {
|
|
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");
|
|
2515
|
-
return fs14.existsSync(p) ? JSON.parse(fs14.readFileSync(p, "utf8")) : null;
|
|
2516
|
-
}
|
|
2517
|
-
function findHookPath() {
|
|
2518
|
-
const candidates = [
|
|
2519
|
-
path15.resolve(__dir, "../hooks/register.cjs"),
|
|
2520
|
-
// npm: dist/ → ../hooks/
|
|
2521
|
-
path15.resolve(__dir, "../src/hooks/register.cjs")
|
|
2522
|
-
// dev: exec/ → ../src/hooks/
|
|
2523
|
-
];
|
|
2524
|
-
return candidates.find((p) => fs14.existsSync(p)) || candidates[0];
|
|
2525
|
-
}
|
|
2526
|
-
function detectNodeScripts(scripts) {
|
|
2527
|
-
const pattern = /(?:^|\s)node\s+(\S+)/;
|
|
2528
|
-
return Object.entries(scripts || {}).filter(([name, cmd2]) => {
|
|
2529
|
-
if (name.includes(":lbe") || name.startsWith("lbe")) return false;
|
|
2530
|
-
return pattern.test(cmd2);
|
|
2531
|
-
});
|
|
2532
|
-
}
|
|
2533
|
-
function extractNodeArgs(cmd2) {
|
|
2534
|
-
const match = cmd2.match(/(?:^|\s)node\s+(.+)/);
|
|
2535
|
-
return match ? match[1].trim() : null;
|
|
2536
|
-
}
|
|
2537
|
-
function injectScripts(wrapScript) {
|
|
2538
|
-
const pkgPath = path15.join(process.cwd(), "package.json");
|
|
2539
|
-
if (!fs14.existsSync(pkgPath)) return [];
|
|
2540
|
-
const pkg = JSON.parse(fs14.readFileSync(pkgPath, "utf8"));
|
|
2541
|
-
const scripts = pkg.scripts || {};
|
|
2542
|
-
const added = [];
|
|
2543
|
-
if (wrapScript) {
|
|
2544
|
-
const original = scripts[wrapScript];
|
|
2545
|
-
if (!original) {
|
|
2546
|
-
console.error(`No script named "${wrapScript}" found.`);
|
|
2547
|
-
return [];
|
|
2548
|
-
}
|
|
2549
|
-
const args = extractNodeArgs(original);
|
|
2550
|
-
if (!args) {
|
|
2551
|
-
console.error(`Script "${wrapScript}" does not look like a node script.`);
|
|
2552
|
-
return [];
|
|
2553
|
-
}
|
|
2554
|
-
scripts[wrapScript] = `lbe-exec run-node --mode observe ${args}`;
|
|
2555
|
-
added.push(wrapScript);
|
|
2556
|
-
} else {
|
|
2557
|
-
const candidates = detectNodeScripts(scripts);
|
|
2558
|
-
for (const [name, scriptCmd] of candidates) {
|
|
2559
|
-
const args = extractNodeArgs(scriptCmd);
|
|
2560
|
-
if (!args) continue;
|
|
2561
|
-
const lbeName = name + ":lbe";
|
|
2562
|
-
const lbeEnforceName = name + ":lbe:enforce";
|
|
2563
|
-
if (!scripts[lbeName]) {
|
|
2564
|
-
scripts[lbeName] = `lbe-exec run-node --mode observe ${args}`;
|
|
2565
|
-
added.push(lbeName);
|
|
2566
|
-
}
|
|
2567
|
-
if (!scripts[lbeEnforceName]) {
|
|
2568
|
-
scripts[lbeEnforceName] = `lbe-exec run-node --mode enforce ${args}`;
|
|
2569
|
-
added.push(lbeEnforceName);
|
|
2570
|
-
}
|
|
2571
|
-
}
|
|
2572
|
-
}
|
|
2573
|
-
if (!scripts["lbe:status"]) {
|
|
2574
|
-
scripts["lbe:status"] = "lbe-exec status";
|
|
2575
|
-
added.push("lbe:status");
|
|
2576
|
-
}
|
|
2577
|
-
if (!scripts["lbe:audit"]) {
|
|
2578
|
-
scripts["lbe:audit"] = "lbe-exec audit";
|
|
2579
|
-
added.push("lbe:audit");
|
|
2580
|
-
}
|
|
2581
|
-
if (added.length) {
|
|
2582
|
-
pkg.scripts = scripts;
|
|
2583
|
-
fs14.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
2584
|
-
for (const s of added) console.log(` added: ${s}`);
|
|
2585
|
-
}
|
|
2586
|
-
return added;
|
|
2587
|
-
}
|
|
2588
|
-
switch (cmd) {
|
|
2589
|
-
case "run-node": {
|
|
2590
|
-
const mode = opts.mode || "observe";
|
|
2591
|
-
if (!["observe", "enforce"].includes(mode)) {
|
|
2592
|
-
console.error("--mode must be observe or enforce");
|
|
2593
|
-
process.exit(1);
|
|
2594
|
-
}
|
|
2595
|
-
const scriptIdx = rest.findIndex((v, i) => !v.startsWith("--") && (i === 0 || !rest[i - 1].startsWith("--")));
|
|
2596
|
-
if (scriptIdx === -1) {
|
|
2597
|
-
console.error("Usage: lbe-exec run-node [--mode observe|enforce] <script> [...args]");
|
|
2598
|
-
process.exit(1);
|
|
2599
|
-
}
|
|
2600
|
-
const scriptAndArgs = rest.slice(scriptIdx);
|
|
2601
|
-
const hookPath = findHookPath();
|
|
2602
|
-
if (!fs14.existsSync(hookPath)) {
|
|
2603
|
-
console.error("Hook not found: " + hookPath + "\nRun: npm install @letterblack/lbe-exec");
|
|
2604
|
-
process.exit(1);
|
|
2605
|
-
}
|
|
2606
|
-
const child = spawn(process.execPath, ["--require", hookPath, ...scriptAndArgs], {
|
|
2607
|
-
stdio: "inherit",
|
|
2608
|
-
env: { ...process.env, LBE_MODE: mode, LBE_ROOT: process.cwd() }
|
|
2609
|
-
});
|
|
2610
|
-
child.on("close", (code) => process.exit(code ?? 0));
|
|
2611
|
-
break;
|
|
2612
|
-
}
|
|
2613
|
-
case "npm": {
|
|
2614
|
-
console.error('[lbe] Note: Use "lbe-exec run-node" for reliable hook preload.');
|
|
2615
|
-
console.error("[lbe] NODE_OPTIONS --require may not fire for all npm lifecycle hooks.\n");
|
|
2616
|
-
const hookPath = findHookPath();
|
|
2617
|
-
if (!fs14.existsSync(hookPath)) {
|
|
2618
|
-
console.error("Hook not found: " + hookPath);
|
|
2619
|
-
process.exit(1);
|
|
2620
|
-
}
|
|
2621
|
-
const existing = process.env.NODE_OPTIONS || "";
|
|
2622
|
-
const hookPathFwd = hookPath.replace(/\\/g, "/");
|
|
2623
|
-
const hookFlag = '--require "' + hookPathFwd + '"';
|
|
2624
|
-
const nodeOptions = existing.includes(hookPathFwd) ? existing : (existing + " " + hookFlag).trim();
|
|
2625
|
-
const npmArgs = rest.filter((v) => !v.startsWith("--mode") && v !== opts.mode);
|
|
2626
|
-
const child = spawn("npm", npmArgs, {
|
|
2627
|
-
stdio: "inherit",
|
|
2628
|
-
shell: true,
|
|
2629
|
-
env: { ...process.env, NODE_OPTIONS: nodeOptions, LBE_MODE: opts.mode || "observe", LBE_ROOT: process.cwd() }
|
|
2630
|
-
});
|
|
2631
|
-
child.on("close", (code) => process.exit(code ?? 0));
|
|
2632
|
-
break;
|
|
2633
|
-
}
|
|
2634
|
-
case "status": {
|
|
2635
|
-
const root = process.cwd();
|
|
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");
|
|
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)");
|
|
2661
|
-
}
|
|
2662
|
-
}
|
|
2663
|
-
const statusFile = path15.join(root, ".lbe", "runtime", "hook-status.json");
|
|
2664
|
-
if (fs14.existsSync(statusFile)) {
|
|
2665
|
-
let h;
|
|
2666
|
-
try {
|
|
2667
|
-
h = JSON.parse(fs14.readFileSync(statusFile, "utf8"));
|
|
2668
|
-
} catch (_) {
|
|
2669
|
-
}
|
|
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
|
-
}
|
|
2688
|
-
} else {
|
|
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");
|
|
2691
|
-
}
|
|
2692
|
-
break;
|
|
2693
|
-
}
|
|
2694
|
-
case "audit": {
|
|
2695
|
-
const eventsPath = path15.join(process.cwd(), ".lbe", "events.jsonl");
|
|
2696
|
-
if (!fs14.existsSync(eventsPath)) {
|
|
2697
|
-
console.log("No events log found. Run an agent with: npx lbe-exec run-node ./agent.js");
|
|
2698
|
-
break;
|
|
2699
|
-
}
|
|
2700
|
-
const lines = fs14.readFileSync(eventsPath, "utf8").split("\n").filter((l) => l.trim());
|
|
2701
|
-
if (!lines.length) {
|
|
2702
|
-
console.log("No events recorded yet.");
|
|
2703
|
-
break;
|
|
2704
|
-
}
|
|
2705
|
-
console.log("\u2500\u2500 LBE Event Log (" + lines.length + " entries) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
2706
|
-
for (const line of lines) {
|
|
2707
|
-
try {
|
|
2708
|
-
const e = JSON.parse(line);
|
|
2709
|
-
const ts = new Date(e.ts * 1e3).toISOString().replace("T", " ").slice(0, 19);
|
|
2710
|
-
const target = e.path || e.cmd || "?";
|
|
2711
|
-
const status = e.enforced && e.decision === "deny" ? "BLOCKED" : e.decision === "deny" ? "WOULD-BLOCK" : "allowed";
|
|
2712
|
-
console.log(`${ts} [${e.mode}] ${e.action} ${target} \u2192 ${status}`);
|
|
2713
|
-
} catch (_) {
|
|
2714
|
-
}
|
|
2715
|
-
}
|
|
2716
|
-
break;
|
|
2717
|
-
}
|
|
2718
|
-
case "init":
|
|
2719
|
-
initCommand(opts).then(() => {
|
|
2720
|
-
const added = injectScripts(opts.wrap || null);
|
|
2721
|
-
if (added.length) {
|
|
2722
|
-
console.log("\n\u2713 Added LBE script variants to package.json");
|
|
2723
|
-
console.log(" Run your agent through LBE: npm run <name>:lbe");
|
|
2724
|
-
} else {
|
|
2725
|
-
console.log("\nNo node agent scripts detected in package.json.");
|
|
2726
|
-
console.log("Use: npx lbe-exec run-node [--mode observe|enforce] ./your-agent.js");
|
|
2727
|
-
}
|
|
2728
|
-
}).catch((e) => {
|
|
2729
|
-
console.error(e.message);
|
|
2730
|
-
process.exit(1);
|
|
2731
|
-
});
|
|
2732
|
-
break;
|
|
2733
|
-
case "activate": {
|
|
2734
|
-
const hookPath = findHookPath();
|
|
2735
|
-
if (!fs14.existsSync(hookPath)) {
|
|
2736
|
-
console.error("Hook not found: " + hookPath);
|
|
2737
|
-
console.error("Run: npm install @letterblack/lbe-exec");
|
|
2738
|
-
process.exit(1);
|
|
2739
|
-
}
|
|
2740
|
-
const mode = opts.mode || "observe";
|
|
2741
|
-
const root = process.cwd();
|
|
2742
|
-
const lbeDir = path15.join(root, ".lbe");
|
|
2743
|
-
fs14.mkdirSync(lbeDir, { recursive: true });
|
|
2744
|
-
fs14.writeFileSync(path15.join(lbeDir, "activation.json"), JSON.stringify({
|
|
2745
|
-
activated: true,
|
|
2746
|
-
activatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2747
|
-
hookPath,
|
|
2748
|
-
mode,
|
|
2749
|
-
root
|
|
2750
|
-
}, null, 2) + "\n");
|
|
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 (_) {
|
|
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
|
-
);
|
|
2797
|
-
} else {
|
|
2798
|
-
const sh = process.env.SHELL || "/bin/bash";
|
|
2799
|
-
shellProc = spawn(sh, [], { stdio: "inherit", env: shellEnv });
|
|
2800
|
-
}
|
|
2801
|
-
shellProc.on("close", (code) => {
|
|
2802
|
-
console.log("\n[lbe] Governed shell closed.");
|
|
2803
|
-
process.exit(code ?? 0);
|
|
2804
|
-
});
|
|
2805
|
-
break;
|
|
2806
|
-
}
|
|
2807
|
-
case "deactivate": {
|
|
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++;
|
|
2818
|
-
}
|
|
2819
|
-
}
|
|
2820
|
-
if (removed) {
|
|
2821
|
-
console.log("\u2713 LBE deactivated \u2014 workspace activation files removed.");
|
|
2822
|
-
} else {
|
|
2823
|
-
console.log("Nothing to deactivate (workspace was not activated).");
|
|
2824
|
-
}
|
|
2825
|
-
console.log('Close any open "lbe-exec shell" sessions to fully disarm.');
|
|
2826
|
-
break;
|
|
2827
|
-
}
|
|
2828
|
-
case "observe":
|
|
2829
|
-
case "enforce":
|
|
2830
|
-
policyModeCommand(cmd, opts).catch((e) => {
|
|
2831
|
-
console.error(e.message);
|
|
2832
|
-
process.exit(1);
|
|
2833
|
-
});
|
|
2834
|
-
break;
|
|
2835
|
-
case "policy": {
|
|
2836
|
-
const policy = loadPolicy();
|
|
2837
|
-
if (!policy) {
|
|
2838
|
-
console.log("No policy found. Run: npx lbe-exec init");
|
|
2839
|
-
break;
|
|
2840
|
-
}
|
|
2841
|
-
if (!policy.rules?.length) {
|
|
2842
|
-
console.log("No rules defined.");
|
|
2843
|
-
break;
|
|
2844
|
-
}
|
|
2845
|
-
for (const r of policy.rules) {
|
|
2846
|
-
console.log(`[${r.effect.toUpperCase()}] ${r.type}:${r.pattern} \u2014 ${r.from || ""} (${r.id || "?"})`);
|
|
2847
|
-
}
|
|
2848
|
-
break;
|
|
2849
|
-
}
|
|
2850
|
-
case "execute": {
|
|
2851
|
-
Promise.resolve().then(() => (init_localExecutor(), localExecutor_exports)).then(async ({ createLocalExecutor: createLocalExecutor2 }) => {
|
|
2852
|
-
const lbe = createLocalExecutor2({ rootDir: process.cwd() });
|
|
2853
|
-
let raw = "";
|
|
2854
|
-
if (opts.input) {
|
|
2855
|
-
raw = fs14.readFileSync(path15.resolve(opts.input), "utf8");
|
|
2856
|
-
} else {
|
|
2857
|
-
for await (const chunk of process.stdin) raw += chunk;
|
|
2858
|
-
}
|
|
2859
|
-
const request = JSON.parse(raw);
|
|
2860
|
-
const result = await lbe.execute(request);
|
|
2861
|
-
console.log(JSON.stringify(result, null, 2));
|
|
2862
|
-
process.exit(result.ok ? 0 : result.decision === "deny" ? 1 : 2);
|
|
2863
|
-
}).catch((e) => {
|
|
2864
|
-
console.error(e.message);
|
|
2865
|
-
process.exit(2);
|
|
2866
|
-
});
|
|
2867
|
-
break;
|
|
2868
|
-
}
|
|
2869
|
-
default:
|
|
2870
|
-
console.log("Usage: lbe-exec <command>\n");
|
|
2871
|
-
console.log(" init Bootstrap governance \u2014 policy, keys, agent files");
|
|
2872
|
-
console.log(" run-node Run a Node.js agent under LBE governance");
|
|
2873
|
-
console.log(" [--mode observe|enforce] <script> [...args]");
|
|
2874
|
-
console.log(" npm Wrap npm command with LBE hook (via NODE_OPTIONS)");
|
|
2875
|
-
console.log(" [...npm-args]");
|
|
2876
|
-
console.log(" status Show workspace, mode, hook state, patched functions");
|
|
2877
|
-
console.log(" audit Show unified event log (.lbe/events.jsonl)");
|
|
2878
|
-
console.log(" policy List active policy rules");
|
|
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");
|
|
2884
|
-
console.log(" observe Switch to observer mode (log only, nothing blocked)");
|
|
2885
|
-
console.log(" enforce Switch to enforcement mode (violations blocked)");
|
|
2886
|
-
console.log(" execute Send a JSON request from stdin or --input file");
|
|
2887
|
-
console.log("\nCLI: npx lbe-exec <command>");
|
|
2888
|
-
if (cmd && cmd !== "--help" && cmd !== "help") {
|
|
2889
|
-
console.error("\nUnknown command: " + cmd);
|
|
2890
|
-
process.exit(1);
|
|
2891
|
-
}
|
|
2892
|
-
}
|
|
64
|
+
`}function lo(e){let t=v.join(e,".lbe");S.mkdirSync(t,{recursive:!0}),S.writeFileSync(v.join(t,"AGENT_CONTRACT.md"),co())}function uo(e){let t=v.join(e,".lbe");S.mkdirSync(t,{recursive:!0});let o=[["lbe.policy.json",".lbe/policy.json"],["lbe.workspace.json",".lbe/workspace.json"]],n=[];for(let[s,i]of o){let a=v.join(e,s),c=v.join(e,i);S.existsSync(a)&&!S.existsSync(c)?(S.renameSync(a,c),n.push(s+" \u2192 "+i)):S.existsSync(a)&&(S.unlinkSync(a),n.push(s+" (removed \u2014 .lbe/ version exists)"))}let r=["CLAUDE.md",v.join(".github","copilot-instructions.md")];for(let s of r){let i=v.join(e,s);if(S.existsSync(i)){let a=S.readFileSync(i,"utf8");(a.includes("lbe-governance")||a.includes("LetterBlack LBE"))&&(S.unlinkSync(i),n.push(s+" (removed \u2014 LBE-generated file)"))}}return n}async function $e(e={}){let t=process.cwd(),o=e.yes||e.y||!process.stdin.isTTY,n=v.join(t,".lbe");S.mkdirSync(n,{recursive:!0});let r=v.join(n,"workspace.json");console.log(`
|
|
65
|
+
Scanning workspace...
|
|
66
|
+
`);let{projectTypes:s,primaryType:i,semantics:a,enforcement:c}=Fe(t);console.log(je(s,a,c)),console.log("");let l=c;if(!o){let h=await ro("Accept? [Y = accept / s = strict / r = relaxed / n = cancel] ");if(h==="n")return console.log("Cancelled."),{success:!1};h==="s"&&(l=so(c)),h==="r"&&(l=io(c))}let p={lbe:!0,version:"0.4.0",state:"local",projectTypes:s,primaryType:i,semantics:a,enforcement:l};S.writeFileSync(r,JSON.stringify(p,null,2)),console.log("\u2713 Wrote .lbe/workspace.json"),ao(t);let f=v.join(n,"policy.json");S.existsSync(f)||S.writeFileSync(f,JSON.stringify({version:1,mode:"observe",workspace:t,rules:[]},null,2)+`
|
|
67
|
+
`);let m=v.join(n,"audit.jsonl");S.existsSync(m)||S.writeFileSync(m,""),console.log("\u2713 Keys and policy ready (.lbe/)"),lo(t),console.log("\u2713 Agent contract written \u2192 .lbe/AGENT_CONTRACT.md");let y=uo(t);if(y.length){console.log(`
|
|
68
|
+
\u2713 Migrated legacy files:`);for(let h of y)console.log(" "+h)}return console.log(`
|
|
69
|
+
Done. All LBE state is in .lbe/`),console.log(`Run npx lbe-exec status to verify.
|
|
70
|
+
`),{success:!0,contract:p}}ve();async function Je(e,t={}){let o=G(t.root||process.cwd(),e);Ie(o.root,{...o.policy,mode:e}),console.log(JSON.stringify({mode:e,policy:o.policyPath},null,2))}var[,,Q,...Y]=process.argv,P=Object.fromEntries(Y.flatMap((e,t,o)=>e.startsWith("--")?[[e.slice(2),o[t+1]??!0]]:[])),Jr=Y.filter(e=>!e.startsWith("--")&&Y[Y.indexOf(e)-1]?.startsWith("--")===!1),Ct=A.dirname(an(import.meta.url));function cn(){let e=process.cwd(),t=E.existsSync(A.join(e,".lbe","policy.json"))?A.join(e,".lbe","policy.json"):A.join(e,"lbe.policy.json");return E.existsSync(t)?JSON.parse(E.readFileSync(t,"utf8")):null}function te(){let e=[A.resolve(Ct,"../hooks/register.cjs"),A.resolve(Ct,"../src/hooks/register.cjs")];return e.find(t=>E.existsSync(t))||e[0]}function ln(e){let t=/(?:^|\s)node\s+(\S+)/;return Object.entries(e||{}).filter(([o,n])=>o.includes(":lbe")||o.startsWith("lbe")?!1:t.test(n))}function Pt(e){let t=e.match(/(?:^|\s)node\s+(.+)/);return t?t[1].trim():null}function dn(e){let t=A.join(process.cwd(),"package.json");if(!E.existsSync(t))return[];let o=JSON.parse(E.readFileSync(t,"utf8")),n=o.scripts||{},r=[];if(e){let s=n[e];if(!s)return console.error(`No script named "${e}" found.`),[];let i=Pt(s);if(!i)return console.error(`Script "${e}" does not look like a node script.`),[];n[e]=`lbe-exec run-node --mode observe ${i}`,r.push(e)}else{let s=ln(n);for(let[i,a]of s){let c=Pt(a);if(!c)continue;let l=i+":lbe",p=i+":lbe:enforce";n[l]||(n[l]=`lbe-exec run-node --mode observe ${c}`,r.push(l)),n[p]||(n[p]=`lbe-exec run-node --mode enforce ${c}`,r.push(p))}}if(n["lbe:status"]||(n["lbe:status"]="lbe-exec status",r.push("lbe:status")),n["lbe:audit"]||(n["lbe:audit"]="lbe-exec audit",r.push("lbe:audit")),r.length){o.scripts=n,E.writeFileSync(t,JSON.stringify(o,null,2)+`
|
|
71
|
+
`);for(let s of r)console.log(` added: ${s}`)}return r}switch(Q){case"run-node":{let e=P.mode||"observe";["observe","enforce"].includes(e)||(console.error("--mode must be observe or enforce"),process.exit(1));let t=Y.findIndex((s,i)=>!s.startsWith("--")&&(i===0||!Y[i-1].startsWith("--")));t===-1&&(console.error("Usage: lbe-exec run-node [--mode observe|enforce] <script> [...args]"),process.exit(1));let o=Y.slice(t),n=te();E.existsSync(n)||(console.error("Hook not found: "+n+`
|
|
72
|
+
Run: npm install @letterblack/lbe-exec`),process.exit(1)),de(process.execPath,["--require",n,...o],{stdio:"inherit",env:{...process.env,LBE_MODE:e,LBE_ROOT:process.cwd()}}).on("close",s=>process.exit(s??0));break}case"npm":{console.error('[lbe] Note: Use "lbe-exec run-node" for reliable hook preload.'),console.error(`[lbe] NODE_OPTIONS --require may not fire for all npm lifecycle hooks.
|
|
73
|
+
`);let e=te();E.existsSync(e)||(console.error("Hook not found: "+e),process.exit(1));let t=process.env.NODE_OPTIONS||"",o=e.replace(/\\/g,"/"),n='--require "'+o+'"',r=t.includes(o)?t:(t+" "+n).trim(),s=Y.filter(a=>!a.startsWith("--mode")&&a!==P.mode);de("npm",s,{stdio:"inherit",shell:!0,env:{...process.env,NODE_OPTIONS:r,LBE_MODE:P.mode||"observe",LBE_ROOT:process.cwd()}}).on("close",a=>process.exit(a??0));break}case"status":{let e=process.cwd();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"),console.log("workspace: "+e);let t=te();console.log("hook file: "+t+(E.existsSync(t)?" (found)":" (MISSING)"));let o=process.env.LBE_ROOT||"";console.log("LBE_ROOT: "+(o||"(not set)"));let r=(process.env.NODE_OPTIONS||"").includes("register.cjs");console.log("NODE_OPTIONS contains hook: "+(r?"yes":"no"));let s=A.join(e,".lbe","events.jsonl"),i=E.existsSync(s);if(console.log("audit log: "+(i?s:"(none yet)")),i)try{let c=E.readFileSync(s,"utf8").split(`
|
|
74
|
+
`).filter(l=>l.trim());if(c.length){let l=JSON.parse(c[c.length-1]),p=new Date((l.ts||0)*1e3).toISOString().replace("T"," ").slice(0,19),f=l.path||l.cmd||"?";console.log("last event: "+p+" "+l.action+" "+f+" \u2192 "+(l.decision||"?"))}else console.log("last event: (none)")}catch{console.log("last event: (unreadable)")}let a=A.join(e,".lbe","runtime","hook-status.json");if(E.existsSync(a)){let c;try{c=JSON.parse(E.readFileSync(a,"utf8"))}catch{}if(c){let l=!1;try{process.kill(c.pid,0),l=!0}catch{}if(console.log(`
|
|
75
|
+
hook process: `+(l?"ACTIVE":"stale (process exited)")),console.log("hook pid: "+c.pid+(l?" (alive)":" (gone)")),console.log("hook mode: "+c.mode),console.log("hook started: "+c.started_at),c.patched){console.log(`
|
|
76
|
+
Patched functions:`);for(let[p,f]of Object.entries(c.patched))console.log(" "+(f?"\u2713":"\u2013")+" "+p)}}}else console.log(`
|
|
77
|
+
hook process: inactive \u2014 run: lbe-exec run-node ./agent.js`),console.log(" or: lbe-exec activate then lbe-exec shell");break}case"audit":{let e=A.join(process.cwd(),".lbe","events.jsonl");if(!E.existsSync(e)){console.log("No events log found. Run an agent with: npx lbe-exec run-node ./agent.js");break}let t=E.readFileSync(e,"utf8").split(`
|
|
78
|
+
`).filter(o=>o.trim());if(!t.length){console.log("No events recorded yet.");break}console.log("\u2500\u2500 LBE Event Log ("+t.length+" entries) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");for(let o of t)try{let n=JSON.parse(o),r=new Date(n.ts*1e3).toISOString().replace("T"," ").slice(0,19),s=n.path||n.cmd||"?",i=n.enforced&&n.decision==="deny"?"BLOCKED":n.decision==="deny"?"WOULD-BLOCK":"allowed";console.log(`${r} [${n.mode}] ${n.action} ${s} \u2192 ${i}`)}catch{}break}case"init":$e(P).then(()=>{dn(P.wrap||null).length?(console.log(`
|
|
79
|
+
\u2713 Added LBE script variants to package.json`),console.log(" Run your agent through LBE: npm run <name>:lbe")):(console.log(`
|
|
80
|
+
No node agent scripts detected in package.json.`),console.log("Use: npx lbe-exec run-node [--mode observe|enforce] ./your-agent.js"))}).catch(e=>{console.error(e.message),process.exit(1)});break;case"activate":{let e=te();E.existsSync(e)||(console.error("Hook not found: "+e),console.error("Run: npm install @letterblack/lbe-exec"),process.exit(1));let t=P.mode||"observe",o=process.cwd(),n=A.join(o,".lbe");E.mkdirSync(n,{recursive:!0}),E.writeFileSync(A.join(n,"activation.json"),JSON.stringify({activated:!0,activatedAt:new Date().toISOString(),hookPath:e,mode:t,root:o},null,2)+`
|
|
81
|
+
`),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"),console.log("workspace: "+o),console.log("hook: "+e),console.log("mode: "+t),console.log(`
|
|
82
|
+
Next: open a governed shell session:`),console.log(" lbe-exec shell"),console.log(`
|
|
83
|
+
Any Node.js agent run inside that shell is intercepted.`),console.log("Python, Go, native binaries, and PowerShell are NOT governed.");break}case"shell":{let e=A.join(process.cwd(),".lbe","activation.json"),t=null;if(E.existsSync(e))try{t=JSON.parse(E.readFileSync(e,"utf8"))}catch{}let o=t&&t.hookPath||te();E.existsSync(o)||(console.error("Hook not found. Run: lbe-exec activate"),process.exit(1));let n=P.mode||t&&t.mode||"observe",r=t&&t.root||process.cwd(),s=o.replace(/\\/g,"/"),i='--require "'+s+'"',a={...process.env,NODE_OPTIONS:i,LBE_ROOT:r,LBE_MODE:n};console.log("[lbe] Opening governed shell \u2014 mode: "+n),console.log("[lbe] NODE_OPTIONS set. Node.js agents are intercepted."),console.log("[lbe] Python / Go / native binaries are NOT governed."),console.log(`[lbe] Type "exit" to close.
|
|
84
|
+
`);let c;if(process.platform==="win32"){let l=[`$env:NODE_OPTIONS='--require "${s}"'`,`$env:LBE_ROOT='${r}'`,`$env:LBE_MODE='${n}'`,`Write-Host '[lbe] Shell armed \u2014 mode: ${n}' -ForegroundColor Green`].join("; ");c=de("powershell.exe",["-NoExit","-Command",l],{stdio:"inherit",env:a})}else{let l=process.env.SHELL||"/bin/bash";c=de(l,[],{stdio:"inherit",env:a})}c.on("close",l=>{console.log(`
|
|
85
|
+
[lbe] Governed shell closed.`),process.exit(l??0)});break}case"deactivate":{let e=process.cwd(),t=[A.join(e,".lbe","activation.json"),A.join(e,".lbe","runtime","hook-status.json")],o=0;for(let n of t)E.existsSync(n)&&(E.unlinkSync(n),o++);console.log(o?"\u2713 LBE deactivated \u2014 workspace activation files removed.":"Nothing to deactivate (workspace was not activated)."),console.log('Close any open "lbe-exec shell" sessions to fully disarm.');break}case"observe":case"enforce":Je(Q,P).catch(e=>{console.error(e.message),process.exit(1)});break;case"policy":{let e=cn();if(!e){console.log("No policy found. Run: npx lbe-exec init");break}if(!e.rules?.length){console.log("No rules defined.");break}for(let t of e.rules)console.log(`[${t.effect.toUpperCase()}] ${t.type}:${t.pattern} \u2014 ${t.from||""} (${t.id||"?"})`);break}case"execute":{Promise.resolve().then(()=>(Tt(),Dt)).then(async({createLocalExecutor:e})=>{let t=e({rootDir:process.cwd()}),o="";if(P.input)o=E.readFileSync(A.resolve(P.input),"utf8");else for await(let s of process.stdin)o+=s;let n=JSON.parse(o),r=await t.execute(n);console.log(JSON.stringify(r,null,2)),process.exit(r.ok?0:r.decision==="deny"?1:2)}).catch(e=>{console.error(e.message),process.exit(2)});break}default:console.log(`Usage: lbe-exec <command>
|
|
86
|
+
`),console.log(" init Bootstrap governance \u2014 policy, keys, agent files"),console.log(" run-node Run a Node.js agent under LBE governance"),console.log(" [--mode observe|enforce] <script> [...args]"),console.log(" npm Wrap npm command with LBE hook (via NODE_OPTIONS)"),console.log(" [...npm-args]"),console.log(" status Show workspace, mode, hook state, patched functions"),console.log(" audit Show unified event log (.lbe/events.jsonl)"),console.log(" policy List active policy rules"),console.log(" activate Write workspace activation record (Node.js only)"),console.log(" [--mode observe|enforce]"),console.log(" shell Open a governed terminal (NODE_OPTIONS pre-set)"),console.log(" [--mode observe|enforce]"),console.log(" deactivate Remove workspace activation files"),console.log(" observe Switch to observer mode (log only, nothing blocked)"),console.log(" enforce Switch to enforcement mode (violations blocked)"),console.log(" execute Send a JSON request from stdin or --input file"),console.log(`
|
|
87
|
+
CLI: npx lbe-exec <command>`),Q&&Q!=="--help"&&Q!=="help"&&(console.error(`
|
|
88
|
+
Unknown command: `+Q),process.exit(1))}
|