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 CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import "./evaluate-CkYySl7T.js";
2
+ import "./evaluate-wMPkjcIC.js";
3
3
  import "./views-UODslKwu.js";
4
- import { t as main } from "./cli-chtNj_el.js";
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-CkYySl7T.js";
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
- "pipedTo"
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 `pipedTo`; a command matcher with none matches nothing");
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-Bq0DNPPR.js");
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-04ubUcSb.js");
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 && !hit.some((segment) => args.every((wanted) => segment.tokens.some((token) => matches$1(wanted, token))))) return false;
140
- } else if (args.length > 0) {
141
- if (!parts.some((segment) => args.every((wanted) => segment.tokens.some((token) => matches$1(wanted, token))))) return false;
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-CkYySl7T.js";
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-chtNj_el.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-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-CkYySl7T.js";
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 && !hit.some((segment) => args.every((wanted) => segment.tokens.some((token) => matches$1(wanted, token))))) return false;
143
- } else if (args.length > 0) {
144
- if (!parts.some((segment) => args.every((wanted) => segment.tokens.some((token) => matches$1(wanted, token))))) return false;
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
- "pipedTo"
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 `pipedTo`; a command matcher with none matches nothing");
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.0.0",
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": { "command": { "name": ["rm", "shred"], "token": "-rf" } },
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": { "command": { "name": ["su", "doas", "pkexec"] } },
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": { "command": { "name": ["mkfs*", "dd", "diskutil", "fdisk"] } },
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": { "capability": "fs:write", "path": { "outside": ["./**"] } },
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": { "command": { "name": "git", "token": "--force*" } },
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
  ]
@@ -1,4 +1,4 @@
1
- import { t as evaluate } from "./evaluate-CkYySl7T.js";
1
+ import { t as evaluate } from "./evaluate-wMPkjcIC.js";
2
2
 
3
3
  //#region src/test-run.ts
4
4
  /** The path argument a scenario's call carries, if any. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prae-cli",
3
- "version": "0.7.0",
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": {