@letterblack/lbe-exec 1.2.2 → 1.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +240 -14
- package/assets/lbe-gates.png +0 -0
- package/assets/runtime-boundary.svg +36 -0
- package/assets/story-allow.png +0 -0
- package/assets/story-deny.png +0 -0
- package/dist/cli.js +2614 -0
- package/dist/index.js +40 -6
- package/package.json +5 -1
- package/types.d.ts +43 -12
package/dist/index.js
CHANGED
|
@@ -252,7 +252,7 @@ function savePolicyState(statePath, stateObj) {
|
|
|
252
252
|
}
|
|
253
253
|
function validateAndUpdatePolicyVersionState({
|
|
254
254
|
policyObj,
|
|
255
|
-
statePath = path2.resolve("data/policy.state.json"),
|
|
255
|
+
statePath = path2.resolve(".lbe/data/policy.state.json"),
|
|
256
256
|
maxCreatedAtSkewSec = 31536e3,
|
|
257
257
|
nowSec = Math.floor(Date.now() / 1e3),
|
|
258
258
|
persist = true
|
|
@@ -1071,7 +1071,7 @@ import fs5 from "fs";
|
|
|
1071
1071
|
import path6 from "path";
|
|
1072
1072
|
import crypto2 from "crypto";
|
|
1073
1073
|
function createBackup(filePath, backupDir) {
|
|
1074
|
-
const dir = backupDir || path6.resolve("data/backups");
|
|
1074
|
+
const dir = backupDir || path6.resolve(".lbe/data/backups");
|
|
1075
1075
|
if (!fs5.existsSync(dir)) {
|
|
1076
1076
|
fs5.mkdirSync(dir, { recursive: true });
|
|
1077
1077
|
}
|
|
@@ -1491,7 +1491,7 @@ import fs8 from "fs";
|
|
|
1491
1491
|
import path9 from "path";
|
|
1492
1492
|
import crypto4 from "crypto";
|
|
1493
1493
|
var POLICY_FILE = "lbe.policy.json";
|
|
1494
|
-
var AUDIT_FILE = "lbe
|
|
1494
|
+
var AUDIT_FILE = ".lbe/audit.jsonl";
|
|
1495
1495
|
function glob(pattern) {
|
|
1496
1496
|
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
1497
1497
|
return new RegExp("^" + escaped.replace(/\*\*\//g, "(?:.*/)?").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*") + "$");
|
|
@@ -1605,6 +1605,26 @@ function underRoot(candidate, root) {
|
|
|
1605
1605
|
const resolvedRoot = physicalPath2(root);
|
|
1606
1606
|
return target === resolvedRoot || target.startsWith(resolvedRoot + path10.sep);
|
|
1607
1607
|
}
|
|
1608
|
+
var FORBIDDEN_CONTENT = [
|
|
1609
|
+
/\beval\s*\(/i,
|
|
1610
|
+
/\bFunction\s*\(/i,
|
|
1611
|
+
/\bexec\s*\(/i,
|
|
1612
|
+
/\brequire\s*\(/,
|
|
1613
|
+
/\bimport\s*\(/,
|
|
1614
|
+
/\bchild_process\b/,
|
|
1615
|
+
/\b__proto__\b/,
|
|
1616
|
+
/\bconstructor\s*\[/,
|
|
1617
|
+
/evalScript/i
|
|
1618
|
+
];
|
|
1619
|
+
function scanContent(value, fieldName) {
|
|
1620
|
+
if (typeof value !== "string") return null;
|
|
1621
|
+
for (const pattern of FORBIDDEN_CONTENT) {
|
|
1622
|
+
if (pattern.test(value)) {
|
|
1623
|
+
return error("PAYLOAD_CONTENT_REJECTED", `Forbidden pattern in ${fieldName}: ${pattern}`);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
return null;
|
|
1627
|
+
}
|
|
1608
1628
|
function normalize(rootDir, request, shell = {}) {
|
|
1609
1629
|
if (!request || typeof request !== "object") return { error: error("REQUEST_INVALID", "request must be an object") };
|
|
1610
1630
|
const detail = INTENTS[request.intent];
|
|
@@ -1618,6 +1638,8 @@ function normalize(rootDir, request, shell = {}) {
|
|
|
1618
1638
|
if (["write_file", "patch_file"].includes(request.intent) && typeof request.content !== "string") {
|
|
1619
1639
|
return { error: error("CONTENT_REQUIRED", "content is required for write and patch") };
|
|
1620
1640
|
}
|
|
1641
|
+
const contentScan = scanContent(request.content, "content");
|
|
1642
|
+
if (contentScan) return { error: contentScan };
|
|
1621
1643
|
}
|
|
1622
1644
|
let command = null;
|
|
1623
1645
|
if (detail.adapter === "shell") {
|
|
@@ -1702,7 +1724,7 @@ function createLocalExecutor(options = {}) {
|
|
|
1702
1724
|
return prepared.error;
|
|
1703
1725
|
}
|
|
1704
1726
|
if (prepared.local.policy.mode === "observe") {
|
|
1705
|
-
appendAudit(path10.join(rootDir, "lbe
|
|
1727
|
+
appendAudit(path10.join(rootDir, ".lbe/audit.jsonl"), {
|
|
1706
1728
|
kind: "local_execution",
|
|
1707
1729
|
commandId: prepared.proposal.commandId,
|
|
1708
1730
|
requesterId: prepared.normalized.actor,
|
|
@@ -1722,7 +1744,7 @@ function createLocalExecutor(options = {}) {
|
|
|
1722
1744
|
const requester = prepared.policy.requesters[prepared.normalized.actor];
|
|
1723
1745
|
const adapterResult = await executeAdapter(prepared.normalized.detail.adapter, prepared.proposal, prepared.policy, requester);
|
|
1724
1746
|
const ok = adapterResult.status === "completed";
|
|
1725
|
-
const audit = appendAudit(path10.join(rootDir, "lbe
|
|
1747
|
+
const audit = appendAudit(path10.join(rootDir, ".lbe/audit.jsonl"), {
|
|
1726
1748
|
kind: "local_execution",
|
|
1727
1749
|
commandId: prepared.proposal.commandId,
|
|
1728
1750
|
requesterId: prepared.normalized.actor,
|
|
@@ -1741,8 +1763,20 @@ function createLocalExecutor(options = {}) {
|
|
|
1741
1763
|
...ok ? {} : { error: { code: adapterResult.errorCode || "EXECUTION_FAILED", message: adapterResult.error || "Execution failed", recoverable: true } }
|
|
1742
1764
|
};
|
|
1743
1765
|
}
|
|
1766
|
+
const writeFile = (target, content) => execute({ intent: "write_file", target, content });
|
|
1767
|
+
const readFile = (target) => execute({ intent: "read_file", target });
|
|
1768
|
+
const patchFile = (target, content) => execute({ intent: "patch_file", target, content });
|
|
1769
|
+
const deleteFile = (target) => execute({ intent: "delete_file", target });
|
|
1770
|
+
const runShell = (cmd, args = [], opts = {}) => execute({ intent: "run_shell", command: { cmd, args, ...opts } });
|
|
1744
1771
|
return {
|
|
1745
1772
|
rootDir,
|
|
1773
|
+
// High-level API — use these
|
|
1774
|
+
writeFile,
|
|
1775
|
+
readFile,
|
|
1776
|
+
patchFile,
|
|
1777
|
+
deleteFile,
|
|
1778
|
+
runShell,
|
|
1779
|
+
// Low-level API — for advanced use
|
|
1746
1780
|
validate: async (request) => {
|
|
1747
1781
|
const preview = await dryRun(request);
|
|
1748
1782
|
return { ...preview, dryRun: false, executed: false };
|
|
@@ -1754,7 +1788,7 @@ function createLocalExecutor(options = {}) {
|
|
|
1754
1788
|
proposeRule: proposePolicyRule,
|
|
1755
1789
|
addRule: (rule) => addLocalPolicyRule(rootDir, rule, options.mode || "enforce")
|
|
1756
1790
|
},
|
|
1757
|
-
audit: { verify: () => verifyAuditLogIntegrity(path10.join(rootDir, "lbe
|
|
1791
|
+
audit: { verify: () => verifyAuditLogIntegrity(path10.join(rootDir, ".lbe/audit.jsonl")) }
|
|
1758
1792
|
};
|
|
1759
1793
|
}
|
|
1760
1794
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@letterblack/lbe-exec",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
4
4
|
"description": "Local host-signed execution layer for LetterBlack LBE.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -11,8 +11,12 @@
|
|
|
11
11
|
"default": "./dist/index.js"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"lbe-exec": "dist/cli.js"
|
|
16
|
+
},
|
|
14
17
|
"files": [
|
|
15
18
|
"dist/",
|
|
19
|
+
"assets/",
|
|
16
20
|
"README.md",
|
|
17
21
|
"types.d.ts",
|
|
18
22
|
"LICENSE"
|
package/types.d.ts
CHANGED
|
@@ -1,19 +1,50 @@
|
|
|
1
|
-
export type LBEExecutionIntent = 'read_file' | 'write_file' | 'patch_file' | 'delete_file' | 'run_shell';
|
|
2
|
-
export interface LBERequest {
|
|
3
|
-
id?: string; actor?: string; intent: LBEExecutionIntent; target?: string; content?: string; patch?: unknown;
|
|
4
|
-
command?: { cmd: string; args: string[]; cwd?: string; timeoutMs?: number; maxOutputBytes?: number }; reason?: string;
|
|
5
|
-
}
|
|
6
1
|
export interface LBEResult {
|
|
7
|
-
ok: boolean;
|
|
8
|
-
|
|
2
|
+
ok: boolean;
|
|
3
|
+
decision: 'allow' | 'deny' | 'observe';
|
|
4
|
+
executed: boolean;
|
|
5
|
+
dryRun: boolean;
|
|
6
|
+
error?: { code: string; message: string; recoverable: boolean };
|
|
7
|
+
matchedRules?: string[];
|
|
8
|
+
auditId?: string;
|
|
9
9
|
rollback?: { available: boolean; performed: boolean; backupId?: string };
|
|
10
10
|
}
|
|
11
|
+
|
|
12
|
+
export interface LBEPolicyRule {
|
|
13
|
+
effect: 'allow' | 'deny';
|
|
14
|
+
type: 'path' | 'command';
|
|
15
|
+
pattern: string;
|
|
16
|
+
from: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
export interface LocalExecutor {
|
|
12
20
|
rootDir: string;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
|
|
22
|
+
// High-level API — use these in agent code
|
|
23
|
+
writeFile(target: string, content: string): Promise<LBEResult>;
|
|
24
|
+
readFile(target: string): Promise<LBEResult>;
|
|
25
|
+
patchFile(target: string, content: string): Promise<LBEResult>;
|
|
26
|
+
deleteFile(target: string): Promise<LBEResult>;
|
|
27
|
+
runShell(cmd: string, args?: string[], opts?: { cwd?: string; timeoutMs?: number; maxOutputBytes?: number }): Promise<LBEResult>;
|
|
28
|
+
|
|
29
|
+
// Policy management
|
|
30
|
+
policy: {
|
|
31
|
+
read(): unknown;
|
|
32
|
+
proposeRule(rule: LBEPolicyRule): unknown;
|
|
33
|
+
addRule(rule: LBEPolicyRule): unknown;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Audit
|
|
17
37
|
audit: { verify(): unknown };
|
|
38
|
+
|
|
39
|
+
// Low-level — for advanced / non-standard use only
|
|
40
|
+
validate(request: unknown): Promise<LBEResult>;
|
|
41
|
+
dryRun(request: unknown): Promise<LBEResult>;
|
|
42
|
+
execute(request: unknown): Promise<LBEResult>;
|
|
18
43
|
}
|
|
19
|
-
|
|
44
|
+
|
|
45
|
+
export function createLocalExecutor(options?: {
|
|
46
|
+
rootDir?: string;
|
|
47
|
+
keyId?: string;
|
|
48
|
+
mode?: 'observe' | 'enforce';
|
|
49
|
+
shell?: { allowCommands?: string[]; denyCommands?: string[]; maxRequests?: number };
|
|
50
|
+
}): LocalExecutor;
|