prae-cli 0.7.0 → 0.8.0
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 +3 -3
- package/lib/checkpoint-DiznOVpS.js +99 -0
- package/lib/{cli-chtNj_el.js → cli-UAE968If.js} +124 -23
- package/lib/{evaluate-CkYySl7T.js → evaluate-wMPkjcIC.js} +26 -3
- package/lib/index.js +3 -3
- package/lib/{instructions-C104EUaE.js → instructions-DulxSqFd.js} +1 -1
- package/lib/{mcp-Bq0DNPPR.js → mcp-doNYU-1C.js} +2 -2
- package/lib/ocsf-DhVGbBUw.js +211 -0
- package/lib/playground.js +130 -49
- package/lib/prae-baseline.prae.json +68 -6
- package/lib/secret-catcher.prae.json +7 -4
- package/lib/{src-Vnr0fcpK.js → src-CwBEFhww.js} +1 -1
- package/lib/{test-run-04ubUcSb.js → test-run-XNIRHKOu.js} +1 -1
- package/lib/{views-UODslKwu.js → views-CCwVXpo2.js} +175 -63
- package/package.json +2 -2
- /package/lib/{receipt-check-DrDE3Wwi.js → receipt-check-s3Aryyz-.js} +0 -0
- /package/lib/{src-Br1tGPtS.js → src-B25SHWcz.js} +0 -0
- /package/lib/{src-CQ0UmFUK.js → src-CBGZVvt7.js} +0 -0
- /package/lib/{src-CraUX64d.js → src-d4c_n6wd.js} +0 -0
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
|
|
|
@@ -468,52 +491,100 @@
|
|
|
468
491
|
|
|
469
492
|
//#endregion
|
|
470
493
|
//#region ../kernel/src/frameworks.ts
|
|
471
|
-
const FRAMEWORKS = {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
494
|
+
const FRAMEWORKS = {
|
|
495
|
+
"owasp-llm-top-10-2026": {
|
|
496
|
+
name: "OWASP Top 10 for LLM Applications — 2026",
|
|
497
|
+
short: "OWASP LLM 2026",
|
|
498
|
+
controls: {
|
|
499
|
+
LLM01: {
|
|
500
|
+
name: "Prompt Injection",
|
|
501
|
+
summary: "Instructions in a prompt or in retrieved content alter what the model does."
|
|
502
|
+
},
|
|
503
|
+
LLM02: {
|
|
504
|
+
name: "Sensitive Information Disclosure",
|
|
505
|
+
summary: "Credentials, personal data or proprietary content reach the model or its output."
|
|
506
|
+
},
|
|
507
|
+
LLM03: {
|
|
508
|
+
name: "Excessive Agency",
|
|
509
|
+
summary: "The model can take actions, or holds permissions, beyond what its task needs."
|
|
510
|
+
},
|
|
511
|
+
LLM04: {
|
|
512
|
+
name: "Supply Chain",
|
|
513
|
+
summary: "A compromised model, dataset, plugin or dependency enters the application."
|
|
514
|
+
},
|
|
515
|
+
LLM05: {
|
|
516
|
+
name: "Data and Model Poisoning",
|
|
517
|
+
summary: "Training, fine-tuning or embedding data is manipulated to change behaviour."
|
|
518
|
+
},
|
|
519
|
+
LLM06: {
|
|
520
|
+
name: "Unbounded Consumption",
|
|
521
|
+
summary: "The application permits resource use without a limit, exhausting budget or service."
|
|
522
|
+
},
|
|
523
|
+
LLM07: {
|
|
524
|
+
name: "Misinformation",
|
|
525
|
+
summary: "The model produces false or misleading content that is acted on as if true."
|
|
526
|
+
},
|
|
527
|
+
LLM08: {
|
|
528
|
+
name: "Hidden Context Exposure",
|
|
529
|
+
summary: "System prompts, hidden instructions or internal context are revealed."
|
|
530
|
+
},
|
|
531
|
+
LLM09: {
|
|
532
|
+
name: "Vector and Embedding Weaknesses",
|
|
533
|
+
summary: "Retrieval stores are manipulated or leak across tenants and permissions."
|
|
534
|
+
},
|
|
535
|
+
LLM10: {
|
|
536
|
+
name: "Improper Output Handling",
|
|
537
|
+
summary: "Model output is passed to downstream systems without validation."
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
},
|
|
541
|
+
"owasp-agentic-top-10-2026": {
|
|
542
|
+
name: "OWASP Top 10 for Agentic Applications — 2026",
|
|
543
|
+
short: "OWASP Agentic 2026",
|
|
544
|
+
controls: {
|
|
545
|
+
ASI01: {
|
|
546
|
+
name: "Agent Goal Hijack",
|
|
547
|
+
summary: "Untrusted input redirects what the agent is trying to do, turning its objectives against its owner."
|
|
548
|
+
},
|
|
549
|
+
ASI02: {
|
|
550
|
+
name: "Tool Misuse and Exploitation",
|
|
551
|
+
summary: "A legitimate tool the agent already holds is turned to an attacker's ends."
|
|
552
|
+
},
|
|
553
|
+
ASI03: {
|
|
554
|
+
name: "Identity and Privilege Abuse",
|
|
555
|
+
summary: "The agent's own authority, credentials or delegated access is inherited or escalated."
|
|
556
|
+
},
|
|
557
|
+
ASI04: {
|
|
558
|
+
name: "Agentic Supply Chain",
|
|
559
|
+
summary: "A poisoned tool, model, plugin, skill or dependency is trusted before the agent runs."
|
|
560
|
+
},
|
|
561
|
+
ASI05: {
|
|
562
|
+
name: "Unexpected Code Execution",
|
|
563
|
+
summary: "The agent's output reaches something that runs it: a shell, an interpreter, a parser."
|
|
564
|
+
},
|
|
565
|
+
ASI06: {
|
|
566
|
+
name: "Memory and Context Poisoning",
|
|
567
|
+
summary: "Corrupted memory or retrieved context persists across sessions and steers future reasoning."
|
|
568
|
+
},
|
|
569
|
+
ASI07: {
|
|
570
|
+
name: "Insecure Inter-Agent Communication",
|
|
571
|
+
summary: "One agent trusts another's messages by default, so a compromised agent can steer its peers."
|
|
572
|
+
},
|
|
573
|
+
ASI08: {
|
|
574
|
+
name: "Cascading Failures",
|
|
575
|
+
summary: "A single fault propagates across agents, tools and workflows into system-wide impact."
|
|
576
|
+
},
|
|
577
|
+
ASI09: {
|
|
578
|
+
name: "Human-Agent Trust Exploitation",
|
|
579
|
+
summary: "Persuasive or misleading agent output moves a person to an unsafe action or approval."
|
|
580
|
+
},
|
|
581
|
+
ASI10: {
|
|
582
|
+
name: "Rogue Agents",
|
|
583
|
+
summary: "A compromised, misaligned or drifting agent keeps operating in unintended ways."
|
|
584
|
+
}
|
|
514
585
|
}
|
|
515
586
|
}
|
|
516
|
-
}
|
|
587
|
+
};
|
|
517
588
|
|
|
518
589
|
//#endregion
|
|
519
590
|
//#region ../kernel/src/pack.ts
|
|
@@ -580,9 +651,11 @@
|
|
|
580
651
|
check.keys(path, value, [
|
|
581
652
|
"name",
|
|
582
653
|
"token",
|
|
583
|
-
"
|
|
654
|
+
"anyToken",
|
|
655
|
+
"pipedTo",
|
|
656
|
+
"pipeline"
|
|
584
657
|
]);
|
|
585
|
-
if (value.name === void 0 && value.token === void 0 && value.pipedTo === void 0) check.fail(path, "expected `name`, `token` or `
|
|
658
|
+
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
659
|
const out = {};
|
|
587
660
|
if (value.token !== void 0) {
|
|
588
661
|
const tokens = check.names(`${path}.token`, value.token);
|
|
@@ -592,10 +665,18 @@
|
|
|
592
665
|
const names = check.names(`${path}.name`, value.name);
|
|
593
666
|
if (names !== void 0) out.name = names;
|
|
594
667
|
}
|
|
668
|
+
if (value.anyToken !== void 0) {
|
|
669
|
+
const tokens = check.names(`${path}.anyToken`, value.anyToken);
|
|
670
|
+
if (tokens !== void 0) out.anyToken = tokens;
|
|
671
|
+
}
|
|
595
672
|
if (value.pipedTo !== void 0) {
|
|
596
673
|
const pipes = check.names(`${path}.pipedTo`, value.pipedTo);
|
|
597
674
|
if (pipes !== void 0) out.pipedTo = pipes;
|
|
598
675
|
}
|
|
676
|
+
if (value.pipeline !== void 0) {
|
|
677
|
+
const shapes = check.names(`${path}.pipeline`, value.pipeline);
|
|
678
|
+
if (shapes !== void 0) out.pipeline = shapes;
|
|
679
|
+
}
|
|
599
680
|
return out;
|
|
600
681
|
}
|
|
601
682
|
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.2.0",
|
|
5
5
|
"rules": [
|
|
6
6
|
{
|
|
7
7
|
"id": "BASE-001",
|
|
@@ -15,33 +15,69 @@
|
|
|
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"],
|
|
21
|
+
"owasp-agentic-top-10-2026": ["ASI05", "ASI02"]
|
|
18
22
|
}
|
|
19
23
|
},
|
|
20
24
|
{
|
|
21
25
|
"id": "BASE-002",
|
|
22
26
|
"title": "Recursive force-delete is held",
|
|
23
|
-
"when": {
|
|
27
|
+
"when": {
|
|
28
|
+
"command": {
|
|
29
|
+
"name": ["rm", "shred"],
|
|
30
|
+
"anyToken": ["-rf", "-fr", "-Rf", "-fR", "-r", "-R"],
|
|
31
|
+
"token": ["!--dry-run"]
|
|
32
|
+
}
|
|
33
|
+
},
|
|
24
34
|
"effect": {
|
|
25
35
|
"kind": "ask",
|
|
26
36
|
"because": "a recursive force-delete is not reversible and needs a human"
|
|
37
|
+
},
|
|
38
|
+
"frameworks": {
|
|
39
|
+
"owasp-llm-top-10-2026": ["LLM03"],
|
|
40
|
+
"owasp-agentic-top-10-2026": ["ASI02"]
|
|
27
41
|
}
|
|
28
42
|
},
|
|
29
43
|
{
|
|
30
44
|
"id": "BASE-003",
|
|
31
45
|
"title": "Privilege escalation is held",
|
|
32
|
-
"when": {
|
|
46
|
+
"when": {
|
|
47
|
+
"command": {
|
|
48
|
+
"name": ["su", "doas", "pkexec"]
|
|
49
|
+
}
|
|
50
|
+
},
|
|
33
51
|
"effect": {
|
|
34
52
|
"kind": "ask",
|
|
35
53
|
"because": "escalating privileges needs a human"
|
|
54
|
+
},
|
|
55
|
+
"frameworks": {
|
|
56
|
+
"owasp-llm-top-10-2026": ["LLM03"],
|
|
57
|
+
"owasp-agentic-top-10-2026": ["ASI03"]
|
|
36
58
|
}
|
|
37
59
|
},
|
|
38
60
|
{
|
|
39
61
|
"id": "BASE-004",
|
|
40
62
|
"title": "Filesystem-wide destruction is refused",
|
|
41
|
-
"when": {
|
|
63
|
+
"when": {
|
|
64
|
+
"command": {
|
|
65
|
+
"name": ["mkfs*", "diskutil", "fdisk", "dd"],
|
|
66
|
+
"token": [
|
|
67
|
+
"!of=/dev/null",
|
|
68
|
+
"!of=/dev/zero",
|
|
69
|
+
"!of=/dev/stdout",
|
|
70
|
+
"!of=/dev/stderr"
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
},
|
|
42
74
|
"effect": {
|
|
43
75
|
"kind": "deny",
|
|
44
76
|
"because": "block-device and filesystem tools are not granted to agents"
|
|
77
|
+
},
|
|
78
|
+
"frameworks": {
|
|
79
|
+
"owasp-llm-top-10-2026": ["LLM03"],
|
|
80
|
+
"owasp-agentic-top-10-2026": ["ASI02"]
|
|
45
81
|
}
|
|
46
82
|
},
|
|
47
83
|
{
|
|
@@ -64,24 +100,46 @@
|
|
|
64
100
|
"effect": {
|
|
65
101
|
"kind": "deny",
|
|
66
102
|
"because": "secrets and credential stores are out of scope for agents"
|
|
103
|
+
},
|
|
104
|
+
"frameworks": {
|
|
105
|
+
"owasp-llm-top-10-2026": ["LLM02"],
|
|
106
|
+
"owasp-agentic-top-10-2026": ["ASI03"]
|
|
67
107
|
}
|
|
68
108
|
},
|
|
69
109
|
{
|
|
70
110
|
"id": "BASE-006",
|
|
71
111
|
"title": "Writes outside the workspace are held",
|
|
72
|
-
"when": {
|
|
112
|
+
"when": {
|
|
113
|
+
"capability": "fs:write",
|
|
114
|
+
"path": {
|
|
115
|
+
"outside": ["./**"]
|
|
116
|
+
}
|
|
117
|
+
},
|
|
73
118
|
"effect": {
|
|
74
119
|
"kind": "ask",
|
|
75
120
|
"because": "writing outside the working tree needs a human"
|
|
121
|
+
},
|
|
122
|
+
"frameworks": {
|
|
123
|
+
"owasp-llm-top-10-2026": ["LLM03"],
|
|
124
|
+
"owasp-agentic-top-10-2026": ["ASI02"]
|
|
76
125
|
}
|
|
77
126
|
},
|
|
78
127
|
{
|
|
79
128
|
"id": "BASE-007",
|
|
80
129
|
"title": "Force-push is held",
|
|
81
|
-
"when": {
|
|
130
|
+
"when": {
|
|
131
|
+
"command": {
|
|
132
|
+
"name": "git",
|
|
133
|
+
"token": "--force*"
|
|
134
|
+
}
|
|
135
|
+
},
|
|
82
136
|
"effect": {
|
|
83
137
|
"kind": "ask",
|
|
84
138
|
"because": "rewriting shared history needs a human"
|
|
139
|
+
},
|
|
140
|
+
"frameworks": {
|
|
141
|
+
"owasp-llm-top-10-2026": ["LLM03"],
|
|
142
|
+
"owasp-agentic-top-10-2026": ["ASI02"]
|
|
85
143
|
}
|
|
86
144
|
},
|
|
87
145
|
{
|
|
@@ -96,6 +154,10 @@
|
|
|
96
154
|
"effect": {
|
|
97
155
|
"kind": "ask",
|
|
98
156
|
"because": "publishing is outward-facing and needs a human"
|
|
157
|
+
},
|
|
158
|
+
"frameworks": {
|
|
159
|
+
"owasp-llm-top-10-2026": ["LLM03"],
|
|
160
|
+
"owasp-agentic-top-10-2026": ["ASI02"]
|
|
99
161
|
}
|
|
100
162
|
}
|
|
101
163
|
]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"prae": "0.1",
|
|
3
3
|
"pack": "secret-catcher",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"rules": [
|
|
6
6
|
{
|
|
7
7
|
"id": "SEC-001",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"because": "the requested file contains a protected credential; the contents were not returned"
|
|
27
27
|
},
|
|
28
28
|
"frameworks": {
|
|
29
|
-
"owasp-llm-top-10-2026": ["LLM02"]
|
|
29
|
+
"owasp-llm-top-10-2026": ["LLM02"],
|
|
30
|
+
"owasp-agentic-top-10-2026": ["ASI03"]
|
|
30
31
|
}
|
|
31
32
|
},
|
|
32
33
|
{
|
|
@@ -43,7 +44,8 @@
|
|
|
43
44
|
"because": "the requested file contains a private key; the contents were not returned"
|
|
44
45
|
},
|
|
45
46
|
"frameworks": {
|
|
46
|
-
"owasp-llm-top-10-2026": ["LLM02"]
|
|
47
|
+
"owasp-llm-top-10-2026": ["LLM02"],
|
|
48
|
+
"owasp-agentic-top-10-2026": ["ASI03"]
|
|
47
49
|
}
|
|
48
50
|
},
|
|
49
51
|
{
|
|
@@ -74,7 +76,8 @@
|
|
|
74
76
|
"because": "the command ran and its output contained a protected credential; the output was withheld from the model"
|
|
75
77
|
},
|
|
76
78
|
"frameworks": {
|
|
77
|
-
"owasp-llm-top-10-2026": ["LLM02"]
|
|
79
|
+
"owasp-llm-top-10-2026": ["LLM02"],
|
|
80
|
+
"owasp-agentic-top-10-2026": ["ASI03"]
|
|
78
81
|
}
|
|
79
82
|
}
|
|
80
83
|
]
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as scan, i as looksBinary, n as MAX_SCAN_BYTES, r as entropy, t as DETECTORS } from "./src-
|
|
1
|
+
import { a as scan, i as looksBinary, n as MAX_SCAN_BYTES, r as entropy, t as DETECTORS } from "./src-B25SHWcz.js";
|
|
2
2
|
|
|
3
3
|
export { DETECTORS, MAX_SCAN_BYTES, entropy, looksBinary, scan };
|