prae-cli 0.7.0 → 0.7.1
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/lib/bin.js +2 -2
- package/lib/{cli-chtNj_el.js → cli-mahLCjoD.js} +15 -5
- package/lib/{evaluate-CkYySl7T.js → evaluate-wMPkjcIC.js} +26 -3
- package/lib/index.js +2 -2
- package/lib/{mcp-Bq0DNPPR.js → mcp-DDcejhwQ.js} +1 -1
- package/lib/playground.js +38 -5
- package/lib/prae-baseline.prae.json +60 -6
- package/lib/{test-run-04ubUcSb.js → test-run-DOaCKD1t.js} +1 -1
- package/package.json +2 -2
package/lib/bin.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./evaluate-
|
|
2
|
+
import "./evaluate-wMPkjcIC.js";
|
|
3
3
|
import "./views-UODslKwu.js";
|
|
4
|
-
import { t as main } from "./cli-
|
|
4
|
+
import { t as main } from "./cli-mahLCjoD.js";
|
|
5
5
|
import "./src-Ciggx9ja.js";
|
|
6
6
|
|
|
7
7
|
//#region src/bin.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as evaluate } from "./evaluate-
|
|
1
|
+
import { t as evaluate } from "./evaluate-wMPkjcIC.js";
|
|
2
2
|
import { a as renderPacks, c as renderNav, d as FRAMEWORKS, f as cite, i as renderOverview, l as renderShell, n as renderApprovals, o as CONTROLS, r as renderCompliance, s as esc, t as navFor, u as VERDICT_LABEL$1 } from "./views-UODslKwu.js";
|
|
3
3
|
import { f as chain, i as fingerprint, l as verifySignature, m as seal, n as readEntries, r as verifyEntries, t as Ledger, u as GENESIS } from "./src-Ciggx9ja.js";
|
|
4
4
|
import { existsSync, readFileSync, readdirSync, statSync, watch } from "node:fs";
|
|
@@ -72,9 +72,11 @@ function readCommandMatch(check$1, path, value) {
|
|
|
72
72
|
check$1.keys(path, value, [
|
|
73
73
|
"name",
|
|
74
74
|
"token",
|
|
75
|
-
"
|
|
75
|
+
"anyToken",
|
|
76
|
+
"pipedTo",
|
|
77
|
+
"pipeline"
|
|
76
78
|
]);
|
|
77
|
-
if (value.name === void 0 && value.token === void 0 && value.pipedTo === void 0) check$1.fail(path, "expected `name`, `token` or `
|
|
79
|
+
if (value.name === void 0 && value.token === void 0 && value.anyToken === void 0 && value.pipedTo === void 0 && value.pipeline === void 0) check$1.fail(path, "expected `name`, `token`, `anyToken`, `pipedTo` or `pipeline`; a command matcher with none matches nothing");
|
|
78
80
|
const out = {};
|
|
79
81
|
if (value.token !== void 0) {
|
|
80
82
|
const tokens = check$1.names(`${path}.token`, value.token);
|
|
@@ -84,10 +86,18 @@ function readCommandMatch(check$1, path, value) {
|
|
|
84
86
|
const names = check$1.names(`${path}.name`, value.name);
|
|
85
87
|
if (names !== void 0) out.name = names;
|
|
86
88
|
}
|
|
89
|
+
if (value.anyToken !== void 0) {
|
|
90
|
+
const tokens = check$1.names(`${path}.anyToken`, value.anyToken);
|
|
91
|
+
if (tokens !== void 0) out.anyToken = tokens;
|
|
92
|
+
}
|
|
87
93
|
if (value.pipedTo !== void 0) {
|
|
88
94
|
const pipes = check$1.names(`${path}.pipedTo`, value.pipedTo);
|
|
89
95
|
if (pipes !== void 0) out.pipedTo = pipes;
|
|
90
96
|
}
|
|
97
|
+
if (value.pipeline !== void 0) {
|
|
98
|
+
const shapes = check$1.names(`${path}.pipeline`, value.pipeline);
|
|
99
|
+
if (shapes !== void 0) out.pipeline = shapes;
|
|
100
|
+
}
|
|
91
101
|
return out;
|
|
92
102
|
}
|
|
93
103
|
function readPathMatch(check$1, path, value) {
|
|
@@ -1300,7 +1310,7 @@ async function scanFile(path, packPath, json) {
|
|
|
1300
1310
|
* not come up inspecting nothing.
|
|
1301
1311
|
*/
|
|
1302
1312
|
async function mcp(root, packPath, ledgerPath) {
|
|
1303
|
-
const { serveMcp } = await import("./mcp-
|
|
1313
|
+
const { serveMcp } = await import("./mcp-DDcejhwQ.js");
|
|
1304
1314
|
const { resolve: resolve$1 } = await import("node:path");
|
|
1305
1315
|
const bundled = [join(import.meta.dirname, "secret-catcher.prae.json"), join(import.meta.dirname, "..", "..", "..", "examples", "secret-catcher.prae.json")].find((candidate) => existsSync(candidate));
|
|
1306
1316
|
const chosen = packPath ?? bundled;
|
|
@@ -1435,7 +1445,7 @@ async function test(suitePath, packOverride, asJson) {
|
|
|
1435
1445
|
} catch (error) {
|
|
1436
1446
|
return fail(`${suitePath} is not valid JSON: ${error instanceof Error ? error.message : error}`);
|
|
1437
1447
|
}
|
|
1438
|
-
const { parseSuite, renderSuite, runSuite } = await import("./test-run-
|
|
1448
|
+
const { parseSuite, renderSuite, runSuite } = await import("./test-run-DOaCKD1t.js");
|
|
1439
1449
|
const suite = parseSuite(parsed);
|
|
1440
1450
|
if (!suite.ok) {
|
|
1441
1451
|
console.error(`prae: ${suitePath} is not a valid test suite`);
|
|
@@ -123,6 +123,22 @@ function matches$1(pattern, value) {
|
|
|
123
123
|
if (pattern.endsWith("*")) return value.startsWith(pattern.slice(0, -1));
|
|
124
124
|
return value === pattern;
|
|
125
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* A glob with `*` anywhere, for pipeline signatures: `curl*|*sh` is the
|
|
128
|
+
* shape of download-and-execute however the two ends are spelled.
|
|
129
|
+
*/
|
|
130
|
+
function globMatches(pattern, value) {
|
|
131
|
+
const source = pattern.split("*").map((part) => part.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join(".*");
|
|
132
|
+
return (/* @__PURE__ */ new RegExp(`^${source}$`)).test(value);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Every wanted token holds in a segment: a plain pattern must match some
|
|
136
|
+
* token, a `!pattern` must match none. `["-rf", "!--dry-run"]` is a real
|
|
137
|
+
* force-delete, not a rehearsal of one.
|
|
138
|
+
*/
|
|
139
|
+
function tokensHold(wanted, tokens) {
|
|
140
|
+
return wanted.every((pattern) => pattern.startsWith("!") ? !tokens.some((token) => matches$1(pattern.slice(1), token)) : tokens.some((token) => matches$1(pattern, token)));
|
|
141
|
+
}
|
|
126
142
|
const list = (v) => v === void 0 ? [] : typeof v === "string" ? [v] : v;
|
|
127
143
|
/**
|
|
128
144
|
* Judge one command line against one matcher. Every present field must hold.
|
|
@@ -132,17 +148,24 @@ function commandMatches(match, command) {
|
|
|
132
148
|
if (parts.length === 0) return false;
|
|
133
149
|
const names = list(match.name);
|
|
134
150
|
const args = list(match.token);
|
|
151
|
+
const anyArgs = list(match.anyToken);
|
|
135
152
|
const pipes = list(match.pipedTo);
|
|
153
|
+
const shapes = list(match.pipeline);
|
|
154
|
+
const argsHold = (segment) => tokensHold(args, segment.tokens) && (anyArgs.length === 0 || anyArgs.some((wanted) => segment.tokens.some((token) => matches$1(wanted, token))));
|
|
136
155
|
if (names.length > 0) {
|
|
137
156
|
const hit = parts.filter((segment) => names.some((pattern) => matches$1(pattern, segment.name) || segment.wrappers.some((wrapper) => matches$1(pattern, wrapper))));
|
|
138
157
|
if (hit.length === 0) return false;
|
|
139
|
-
if (args.length > 0
|
|
140
|
-
} else if (args.length > 0) {
|
|
141
|
-
if (!parts.some(
|
|
158
|
+
if ((args.length > 0 || anyArgs.length > 0) && !hit.some(argsHold)) return false;
|
|
159
|
+
} else if (args.length > 0 || anyArgs.length > 0) {
|
|
160
|
+
if (!parts.some(argsHold)) return false;
|
|
142
161
|
}
|
|
143
162
|
if (pipes.length > 0) {
|
|
144
163
|
if (!parts.slice(1).some((segment) => pipes.some((pattern) => matches$1(pattern, segment.name)))) return false;
|
|
145
164
|
}
|
|
165
|
+
if (shapes.length > 0) {
|
|
166
|
+
const signature = parts.map((segment) => segment.name).join("|");
|
|
167
|
+
if (!shapes.some((pattern) => globMatches(pattern, signature))) return false;
|
|
168
|
+
}
|
|
146
169
|
return true;
|
|
147
170
|
}
|
|
148
171
|
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import "./evaluate-
|
|
1
|
+
import "./evaluate-wMPkjcIC.js";
|
|
2
2
|
import "./views-UODslKwu.js";
|
|
3
|
-
import { a as planWrite, i as dshHome, n as applyWrite, o as praeRow, r as defaultProfile, s as profileAt, t as main } from "./cli-
|
|
3
|
+
import { a as planWrite, i as dshHome, n as applyWrite, o as praeRow, r as defaultProfile, s as profileAt, t as main } from "./cli-mahLCjoD.js";
|
|
4
4
|
import "./src-Ciggx9ja.js";
|
|
5
5
|
|
|
6
6
|
export { applyWrite, defaultProfile, dshHome, main, planWrite, praeRow, profileAt };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as evaluate } from "./evaluate-
|
|
1
|
+
import { t as evaluate } from "./evaluate-wMPkjcIC.js";
|
|
2
2
|
import { a as scan, n as MAX_SCAN_BYTES } from "./src-Br1tGPtS.js";
|
|
3
3
|
import { readFile, readdir, stat } from "node:fs/promises";
|
|
4
4
|
import { resolve, sep } from "node:path";
|
package/lib/playground.js
CHANGED
|
@@ -126,6 +126,22 @@
|
|
|
126
126
|
if (pattern.endsWith("*")) return value.startsWith(pattern.slice(0, -1));
|
|
127
127
|
return value === pattern;
|
|
128
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* A glob with `*` anywhere, for pipeline signatures: `curl*|*sh` is the
|
|
131
|
+
* shape of download-and-execute however the two ends are spelled.
|
|
132
|
+
*/
|
|
133
|
+
function globMatches(pattern, value) {
|
|
134
|
+
const source = pattern.split("*").map((part) => part.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join(".*");
|
|
135
|
+
return (/* @__PURE__ */ new RegExp(`^${source}$`)).test(value);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Every wanted token holds in a segment: a plain pattern must match some
|
|
139
|
+
* token, a `!pattern` must match none. `["-rf", "!--dry-run"]` is a real
|
|
140
|
+
* force-delete, not a rehearsal of one.
|
|
141
|
+
*/
|
|
142
|
+
function tokensHold(wanted, tokens) {
|
|
143
|
+
return wanted.every((pattern) => pattern.startsWith("!") ? !tokens.some((token) => matches$1(pattern.slice(1), token)) : tokens.some((token) => matches$1(pattern, token)));
|
|
144
|
+
}
|
|
129
145
|
const list = (v) => v === void 0 ? [] : typeof v === "string" ? [v] : v;
|
|
130
146
|
/**
|
|
131
147
|
* Judge one command line against one matcher. Every present field must hold.
|
|
@@ -135,17 +151,24 @@
|
|
|
135
151
|
if (parts.length === 0) return false;
|
|
136
152
|
const names = list(match.name);
|
|
137
153
|
const args = list(match.token);
|
|
154
|
+
const anyArgs = list(match.anyToken);
|
|
138
155
|
const pipes = list(match.pipedTo);
|
|
156
|
+
const shapes = list(match.pipeline);
|
|
157
|
+
const argsHold = (segment) => tokensHold(args, segment.tokens) && (anyArgs.length === 0 || anyArgs.some((wanted) => segment.tokens.some((token) => matches$1(wanted, token))));
|
|
139
158
|
if (names.length > 0) {
|
|
140
159
|
const hit = parts.filter((segment) => names.some((pattern) => matches$1(pattern, segment.name) || segment.wrappers.some((wrapper) => matches$1(pattern, wrapper))));
|
|
141
160
|
if (hit.length === 0) return false;
|
|
142
|
-
if (args.length > 0
|
|
143
|
-
} else if (args.length > 0) {
|
|
144
|
-
if (!parts.some(
|
|
161
|
+
if ((args.length > 0 || anyArgs.length > 0) && !hit.some(argsHold)) return false;
|
|
162
|
+
} else if (args.length > 0 || anyArgs.length > 0) {
|
|
163
|
+
if (!parts.some(argsHold)) return false;
|
|
145
164
|
}
|
|
146
165
|
if (pipes.length > 0) {
|
|
147
166
|
if (!parts.slice(1).some((segment) => pipes.some((pattern) => matches$1(pattern, segment.name)))) return false;
|
|
148
167
|
}
|
|
168
|
+
if (shapes.length > 0) {
|
|
169
|
+
const signature = parts.map((segment) => segment.name).join("|");
|
|
170
|
+
if (!shapes.some((pattern) => globMatches(pattern, signature))) return false;
|
|
171
|
+
}
|
|
149
172
|
return true;
|
|
150
173
|
}
|
|
151
174
|
|
|
@@ -580,9 +603,11 @@
|
|
|
580
603
|
check.keys(path, value, [
|
|
581
604
|
"name",
|
|
582
605
|
"token",
|
|
583
|
-
"
|
|
606
|
+
"anyToken",
|
|
607
|
+
"pipedTo",
|
|
608
|
+
"pipeline"
|
|
584
609
|
]);
|
|
585
|
-
if (value.name === void 0 && value.token === void 0 && value.pipedTo === void 0) check.fail(path, "expected `name`, `token` or `
|
|
610
|
+
if (value.name === void 0 && value.token === void 0 && value.anyToken === void 0 && value.pipedTo === void 0 && value.pipeline === void 0) check.fail(path, "expected `name`, `token`, `anyToken`, `pipedTo` or `pipeline`; a command matcher with none matches nothing");
|
|
586
611
|
const out = {};
|
|
587
612
|
if (value.token !== void 0) {
|
|
588
613
|
const tokens = check.names(`${path}.token`, value.token);
|
|
@@ -592,10 +617,18 @@
|
|
|
592
617
|
const names = check.names(`${path}.name`, value.name);
|
|
593
618
|
if (names !== void 0) out.name = names;
|
|
594
619
|
}
|
|
620
|
+
if (value.anyToken !== void 0) {
|
|
621
|
+
const tokens = check.names(`${path}.anyToken`, value.anyToken);
|
|
622
|
+
if (tokens !== void 0) out.anyToken = tokens;
|
|
623
|
+
}
|
|
595
624
|
if (value.pipedTo !== void 0) {
|
|
596
625
|
const pipes = check.names(`${path}.pipedTo`, value.pipedTo);
|
|
597
626
|
if (pipes !== void 0) out.pipedTo = pipes;
|
|
598
627
|
}
|
|
628
|
+
if (value.pipeline !== void 0) {
|
|
629
|
+
const shapes = check.names(`${path}.pipeline`, value.pipeline);
|
|
630
|
+
if (shapes !== void 0) out.pipeline = shapes;
|
|
631
|
+
}
|
|
599
632
|
return out;
|
|
600
633
|
}
|
|
601
634
|
function readPathMatch(check, path, value) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"prae": "0.1",
|
|
3
3
|
"pack": "prae-baseline",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"rules": [
|
|
6
6
|
{
|
|
7
7
|
"id": "BASE-001",
|
|
@@ -15,33 +15,65 @@
|
|
|
15
15
|
"effect": {
|
|
16
16
|
"kind": "deny",
|
|
17
17
|
"because": "piping a download into an interpreter runs code nobody has read"
|
|
18
|
+
},
|
|
19
|
+
"frameworks": {
|
|
20
|
+
"owasp-llm-top-10-2026": ["LLM03"]
|
|
18
21
|
}
|
|
19
22
|
},
|
|
20
23
|
{
|
|
21
24
|
"id": "BASE-002",
|
|
22
25
|
"title": "Recursive force-delete is held",
|
|
23
|
-
"when": {
|
|
26
|
+
"when": {
|
|
27
|
+
"command": {
|
|
28
|
+
"name": ["rm", "shred"],
|
|
29
|
+
"anyToken": ["-rf", "-fr", "-Rf", "-fR", "-r", "-R"],
|
|
30
|
+
"token": ["!--dry-run"]
|
|
31
|
+
}
|
|
32
|
+
},
|
|
24
33
|
"effect": {
|
|
25
34
|
"kind": "ask",
|
|
26
35
|
"because": "a recursive force-delete is not reversible and needs a human"
|
|
36
|
+
},
|
|
37
|
+
"frameworks": {
|
|
38
|
+
"owasp-llm-top-10-2026": ["LLM03"]
|
|
27
39
|
}
|
|
28
40
|
},
|
|
29
41
|
{
|
|
30
42
|
"id": "BASE-003",
|
|
31
43
|
"title": "Privilege escalation is held",
|
|
32
|
-
"when": {
|
|
44
|
+
"when": {
|
|
45
|
+
"command": {
|
|
46
|
+
"name": ["su", "doas", "pkexec"]
|
|
47
|
+
}
|
|
48
|
+
},
|
|
33
49
|
"effect": {
|
|
34
50
|
"kind": "ask",
|
|
35
51
|
"because": "escalating privileges needs a human"
|
|
52
|
+
},
|
|
53
|
+
"frameworks": {
|
|
54
|
+
"owasp-llm-top-10-2026": ["LLM03"]
|
|
36
55
|
}
|
|
37
56
|
},
|
|
38
57
|
{
|
|
39
58
|
"id": "BASE-004",
|
|
40
59
|
"title": "Filesystem-wide destruction is refused",
|
|
41
|
-
"when": {
|
|
60
|
+
"when": {
|
|
61
|
+
"command": {
|
|
62
|
+
"name": ["mkfs*", "diskutil", "fdisk", "dd"],
|
|
63
|
+
"token": [
|
|
64
|
+
"!of=/dev/null",
|
|
65
|
+
"!of=/dev/zero",
|
|
66
|
+
"!of=/dev/stdout",
|
|
67
|
+
"!of=/dev/stderr"
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
},
|
|
42
71
|
"effect": {
|
|
43
72
|
"kind": "deny",
|
|
44
73
|
"because": "block-device and filesystem tools are not granted to agents"
|
|
74
|
+
},
|
|
75
|
+
"frameworks": {
|
|
76
|
+
"owasp-llm-top-10-2026": ["LLM03"]
|
|
45
77
|
}
|
|
46
78
|
},
|
|
47
79
|
{
|
|
@@ -64,24 +96,43 @@
|
|
|
64
96
|
"effect": {
|
|
65
97
|
"kind": "deny",
|
|
66
98
|
"because": "secrets and credential stores are out of scope for agents"
|
|
99
|
+
},
|
|
100
|
+
"frameworks": {
|
|
101
|
+
"owasp-llm-top-10-2026": ["LLM02"]
|
|
67
102
|
}
|
|
68
103
|
},
|
|
69
104
|
{
|
|
70
105
|
"id": "BASE-006",
|
|
71
106
|
"title": "Writes outside the workspace are held",
|
|
72
|
-
"when": {
|
|
107
|
+
"when": {
|
|
108
|
+
"capability": "fs:write",
|
|
109
|
+
"path": {
|
|
110
|
+
"outside": ["./**"]
|
|
111
|
+
}
|
|
112
|
+
},
|
|
73
113
|
"effect": {
|
|
74
114
|
"kind": "ask",
|
|
75
115
|
"because": "writing outside the working tree needs a human"
|
|
116
|
+
},
|
|
117
|
+
"frameworks": {
|
|
118
|
+
"owasp-llm-top-10-2026": ["LLM03"]
|
|
76
119
|
}
|
|
77
120
|
},
|
|
78
121
|
{
|
|
79
122
|
"id": "BASE-007",
|
|
80
123
|
"title": "Force-push is held",
|
|
81
|
-
"when": {
|
|
124
|
+
"when": {
|
|
125
|
+
"command": {
|
|
126
|
+
"name": "git",
|
|
127
|
+
"token": "--force*"
|
|
128
|
+
}
|
|
129
|
+
},
|
|
82
130
|
"effect": {
|
|
83
131
|
"kind": "ask",
|
|
84
132
|
"because": "rewriting shared history needs a human"
|
|
133
|
+
},
|
|
134
|
+
"frameworks": {
|
|
135
|
+
"owasp-llm-top-10-2026": ["LLM03"]
|
|
85
136
|
}
|
|
86
137
|
},
|
|
87
138
|
{
|
|
@@ -96,6 +147,9 @@
|
|
|
96
147
|
"effect": {
|
|
97
148
|
"kind": "ask",
|
|
98
149
|
"because": "publishing is outward-facing and needs a human"
|
|
150
|
+
},
|
|
151
|
+
"frameworks": {
|
|
152
|
+
"owasp-llm-top-10-2026": ["LLM03"]
|
|
99
153
|
}
|
|
100
154
|
}
|
|
101
155
|
]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prae-cli",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Operator policy for agent harnesses: validate packs, explain a decision, test a pack against security invariants, verify a decision log, and provision a governed DSH profile.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh-plugin",
|
|
@@ -40,12 +40,12 @@
|
|
|
40
40
|
"tsdown": "^0.15.6",
|
|
41
41
|
"typescript": "^5.9.2",
|
|
42
42
|
"vitest": "^3.2.4",
|
|
43
|
-
"@prae/controls": "0.1.0",
|
|
44
43
|
"@prae/extract": "0.1.0",
|
|
45
44
|
"@prae/inspect": "0.1.0",
|
|
46
45
|
"@prae/instructions": "0.1.0",
|
|
47
46
|
"@prae/kernel": "0.1.0",
|
|
48
47
|
"@prae/ledger": "0.1.0",
|
|
48
|
+
"@prae/controls": "0.1.0",
|
|
49
49
|
"@prae/tokens": "0.1.0"
|
|
50
50
|
},
|
|
51
51
|
"scripts": {
|