prae-cli 0.7.1 → 0.9.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 +3 -3
- package/lib/checkpoint-BWSuhZ5h.js +99 -0
- package/lib/{cli-mahLCjoD.js → cli-7yLG89gV.js} +210 -28
- package/lib/index.js +3 -3
- package/lib/{instructions-C104EUaE.js → instructions-Bg5puOen.js} +1 -1
- package/lib/{mcp-DDcejhwQ.js → mcp-doNYU-1C.js} +1 -1
- package/lib/ocsf-DhVGbBUw.js +211 -0
- package/lib/playground.js +92 -44
- package/lib/prae-baseline.prae.json +17 -9
- package/lib/{receipt-check-DrDE3Wwi.js → receipt-check-CD-W1wXs.js} +1 -1
- package/lib/secret-catcher.prae.json +7 -4
- package/lib/{src-Vnr0fcpK.js → src-CwBEFhww.js} +1 -1
- package/lib/src-D7g4lvaY.js +392 -0
- package/lib/src-d82lSKVQ.js +3 -0
- package/lib/{views-UODslKwu.js → views-CIg2ZNG0.js} +327 -63
- package/package.json +2 -2
- package/lib/src-C9L6NBvu.js +0 -3
- package/lib/src-Ciggx9ja.js +0 -242
- /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/{test-run-DOaCKD1t.js → test-run-XNIRHKOu.js} +0 -0
package/lib/bin.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "./evaluate-wMPkjcIC.js";
|
|
3
|
-
import "./views-
|
|
4
|
-
import { t as main } from "./cli-
|
|
5
|
-
import "./src-
|
|
3
|
+
import "./views-CIg2ZNG0.js";
|
|
4
|
+
import { t as main } from "./cli-7yLG89gV.js";
|
|
5
|
+
import "./src-D7g4lvaY.js";
|
|
6
6
|
|
|
7
7
|
//#region src/bin.ts
|
|
8
8
|
main(process.argv.slice(2)).catch((error) => {
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { m as canonical, s as fingerprint } from "./src-D7g4lvaY.js";
|
|
2
|
+
import { createPrivateKey, createPublicKey, sign, verify } from "node:crypto";
|
|
3
|
+
|
|
4
|
+
//#region src/checkpoint.ts
|
|
5
|
+
/** What the signature covers: the facts, never the timestamp or the name of the key. */
|
|
6
|
+
function signable(cp) {
|
|
7
|
+
return Buffer.from(canonical({
|
|
8
|
+
ledger: cp.ledger,
|
|
9
|
+
entries: cp.entries,
|
|
10
|
+
head: cp.head
|
|
11
|
+
}));
|
|
12
|
+
}
|
|
13
|
+
function makeCheckpoint(rows, ledger, privateKeyPem, at = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
14
|
+
const head = rows.at(-1);
|
|
15
|
+
const cp = {
|
|
16
|
+
prae: "checkpoint/1",
|
|
17
|
+
at,
|
|
18
|
+
ledger,
|
|
19
|
+
entries: rows.length,
|
|
20
|
+
head: head === void 0 ? {
|
|
21
|
+
seq: -1,
|
|
22
|
+
hash: ""
|
|
23
|
+
} : {
|
|
24
|
+
seq: head.seq,
|
|
25
|
+
hash: head.hash
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
if (privateKeyPem === void 0) return cp;
|
|
29
|
+
const key = createPrivateKey(privateKeyPem);
|
|
30
|
+
const publicKey = createPublicKey(key).export({
|
|
31
|
+
type: "spki",
|
|
32
|
+
format: "pem"
|
|
33
|
+
}).toString();
|
|
34
|
+
return {
|
|
35
|
+
...cp,
|
|
36
|
+
fingerprint: fingerprint(publicKey),
|
|
37
|
+
sig: sign(null, signable(cp), key).toString("base64")
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The ledger as it is now, against the checkpoint as it was then. Rows may
|
|
42
|
+
* have been appended since; nothing before the head may differ.
|
|
43
|
+
*/
|
|
44
|
+
function checkCheckpoint(rows, cp, publicKeyPem) {
|
|
45
|
+
const lines = [];
|
|
46
|
+
let ok = true;
|
|
47
|
+
if (cp.prae !== "checkpoint/1") return {
|
|
48
|
+
ok: false,
|
|
49
|
+
lines: [`not a checkpoint: prae is ${String(cp.prae)}`]
|
|
50
|
+
};
|
|
51
|
+
if (publicKeyPem !== void 0) if (cp.sig === void 0) {
|
|
52
|
+
ok = false;
|
|
53
|
+
lines.push("checkpoint is not signed; a key was given to check it against");
|
|
54
|
+
} else {
|
|
55
|
+
let good = false;
|
|
56
|
+
try {
|
|
57
|
+
good = verify(null, signable(cp), createPublicKey(publicKeyPem), Buffer.from(cp.sig, "base64"));
|
|
58
|
+
} catch {
|
|
59
|
+
good = false;
|
|
60
|
+
}
|
|
61
|
+
if (good) lines.push(`checkpoint signature verifies against ${fingerprint(publicKeyPem)}`);
|
|
62
|
+
else {
|
|
63
|
+
ok = false;
|
|
64
|
+
lines.push(`checkpoint signature FAILS against ${fingerprint(publicKeyPem)} — the checkpoint itself has been altered or was signed by another key`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (rows.length < cp.entries) {
|
|
68
|
+
ok = false;
|
|
69
|
+
lines.push(`ledger holds ${rows.length} rows; the checkpoint of ${cp.at} recorded ${cp.entries} — ${cp.entries - rows.length} row${cp.entries - rows.length === 1 ? "" : "s"} removed since`);
|
|
70
|
+
return {
|
|
71
|
+
ok,
|
|
72
|
+
lines
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (cp.entries === 0) {
|
|
76
|
+
lines.push(`checkpoint of ${cp.at} recorded an empty ledger; ${rows.length} rows since`);
|
|
77
|
+
return {
|
|
78
|
+
ok,
|
|
79
|
+
lines
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const then = rows.find((row) => row.seq === cp.head.seq);
|
|
83
|
+
if (then === void 0 || then.hash !== cp.head.hash) {
|
|
84
|
+
ok = false;
|
|
85
|
+
lines.push(`row ${cp.head.seq} does not match the checkpoint head (${cp.head.hash.slice(0, 16)}…) — the ledger was rewritten since ${cp.at}`);
|
|
86
|
+
return {
|
|
87
|
+
ok,
|
|
88
|
+
lines
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
lines.push(`checkpoint of ${cp.at} holds: ${cp.entries} rows then, ${rows.length} now, head ${cp.head.hash.slice(0, 16)}… intact`);
|
|
92
|
+
return {
|
|
93
|
+
ok,
|
|
94
|
+
lines
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
//#endregion
|
|
99
|
+
export { checkCheckpoint, makeCheckpoint };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as evaluate } from "./evaluate-wMPkjcIC.js";
|
|
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-
|
|
3
|
-
import {
|
|
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-CIg2ZNG0.js";
|
|
3
|
+
import { _ as seal, f as verifySignature, h as chain, n as readEntries, p as GENESIS, r as verifyEntries, s as fingerprint, t as Ledger } from "./src-D7g4lvaY.js";
|
|
4
4
|
import { existsSync, readFileSync, readdirSync, statSync, watch } from "node:fs";
|
|
5
5
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
6
6
|
import { dirname, extname, join, normalize, resolve, sep } from "node:path";
|
|
@@ -972,7 +972,6 @@ function verifyRecord(record, tamper, repair = false) {
|
|
|
972
972
|
signatures
|
|
973
973
|
};
|
|
974
974
|
}
|
|
975
|
-
const OWASP = "owasp-llm-top-10-2026";
|
|
976
975
|
/**
|
|
977
976
|
* Evidence toward each control, from the pack and the ledger as they are
|
|
978
977
|
* now — the rows the controls package defines, so the console, the exporter and
|
|
@@ -1001,15 +1000,18 @@ function complianceOf(record) {
|
|
|
1001
1000
|
verification: verifyEntries(text),
|
|
1002
1001
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1003
1002
|
};
|
|
1004
|
-
const owasp = FRAMEWORKS[OWASP]?.controls ?? {};
|
|
1005
1003
|
return { controls: CONTROLS.map((control) => {
|
|
1006
|
-
const
|
|
1007
|
-
const
|
|
1004
|
+
const found = Object.entries(FRAMEWORKS).find(([, f]) => control.id.startsWith(`${f.short} · `));
|
|
1005
|
+
const code = found === void 0 ? void 0 : control.id.split(" · ")[1];
|
|
1006
|
+
const named = found === void 0 || code === void 0 ? void 0 : found[1].controls[code];
|
|
1007
|
+
const standard = control.standard;
|
|
1008
|
+
const name = named?.name ?? control.name;
|
|
1008
1009
|
return {
|
|
1009
|
-
id: code ?? control.id,
|
|
1010
|
-
...
|
|
1010
|
+
id: code ?? (standard === void 0 ? control.id : control.id.split(" · ")[1] ?? control.id),
|
|
1011
|
+
...name === void 0 ? {} : { name },
|
|
1011
1012
|
asks: control.asks,
|
|
1012
|
-
framework:
|
|
1013
|
+
framework: found?.[0] ?? standard?.id ?? "obligation",
|
|
1014
|
+
...found === void 0 ? standard === void 0 ? {} : { frameworkName: standard.name } : { frameworkName: found[1].name },
|
|
1013
1015
|
...control.evidence(data)
|
|
1014
1016
|
};
|
|
1015
1017
|
}) };
|
|
@@ -1187,14 +1189,42 @@ const USAGE = `⌜▪⌟ prae — operator policy for agent harnesses
|
|
|
1187
1189
|
the exit code is non-zero if any invariant no longer holds, so a
|
|
1188
1190
|
control pack can be defended in CI. --pack overrides the suite's own.
|
|
1189
1191
|
|
|
1190
|
-
prae verify <ledger.jsonl> [--key <public.pem>]
|
|
1192
|
+
prae verify <ledger.jsonl> [--key <public.pem>] [--checkpoint <file>]
|
|
1191
1193
|
Re-derive every hash in a decision log and report any break. With
|
|
1192
|
-
--key, also verify each row's signature against that pinned key.
|
|
1194
|
+
--key, also verify each row's signature against that pinned key. With
|
|
1195
|
+
--checkpoint, also check that nothing before that checkpoint's head
|
|
1196
|
+
has been removed or rewritten since it was taken.
|
|
1197
|
+
|
|
1198
|
+
prae checkpoint <ledger.jsonl> [--key <private.pem>] [--out <file>]
|
|
1199
|
+
Record the ledger's length and head, signed, to keep somewhere the
|
|
1200
|
+
ledger's machine cannot write. A chain proves no row inside the file
|
|
1201
|
+
was touched; a checkpoint proves the file was not cut at the end.
|
|
1202
|
+
|
|
1203
|
+
prae export <ledger.jsonl> --format ocsf [--out <file>]
|
|
1204
|
+
[--hec <url> --hec-token <token>]
|
|
1205
|
+
The record as OCSF 1.5.0 events (NDJSON) for a SIEM or data lake:
|
|
1206
|
+
refusals, holds and withheld outputs as Detection Findings, allowed
|
|
1207
|
+
calls as API Activity, every event carrying the row's seal. With --hec,
|
|
1208
|
+
post them to a Splunk HTTP Event Collector instead of writing a file.
|
|
1193
1209
|
|
|
1194
1210
|
prae keygen [--out <dir>]
|
|
1195
1211
|
Generate an Ed25519 keypair for signing ledger rows. Prints the
|
|
1196
1212
|
fingerprint a counterparty pins.
|
|
1197
1213
|
|
|
1214
|
+
prae keygen --role operator|policy|ledger [--out <dir>]
|
|
1215
|
+
A keypair whose private half is sealed under a passphrase (asked for
|
|
1216
|
+
without echo, or PRAE_KEY_PASSPHRASE). The operator key signs answers
|
|
1217
|
+
to held calls; the policy key signs packs. Pin the public half in the
|
|
1218
|
+
gate (operator.publicKey / policyKey); the agent can read the sealed
|
|
1219
|
+
file and still cannot sign. The ledger key is for the signer that
|
|
1220
|
+
holds it on the harness's behalf (signerFd): prae-signing.*.
|
|
1221
|
+
|
|
1222
|
+
prae sign-pack <pack.json> --key <policy.key.sealed.pem>
|
|
1223
|
+
Sign a pack's exact bytes with the sealed policy key (passphrase asked
|
|
1224
|
+
without echo, or PRAE_KEY_PASSPHRASE), writing <pack.json>.sig. A gate
|
|
1225
|
+
with policyKey pinned governs only by a signed pack whose version
|
|
1226
|
+
rises; raise the version before signing.
|
|
1227
|
+
|
|
1198
1228
|
prae receipt <ledger.jsonl> <seq> --key <private.pem> [--out <file>]
|
|
1199
1229
|
Export one decision as a self-contained signed receipt.
|
|
1200
1230
|
|
|
@@ -1270,7 +1300,7 @@ async function check(path) {
|
|
|
1270
1300
|
* from the findings, so there is nothing here to be careful about printing.
|
|
1271
1301
|
*/
|
|
1272
1302
|
async function scanFile(path, packPath, json) {
|
|
1273
|
-
const { scan } = await import("./src-
|
|
1303
|
+
const { scan } = await import("./src-CwBEFhww.js");
|
|
1274
1304
|
let content;
|
|
1275
1305
|
try {
|
|
1276
1306
|
content = await readFile(path, "utf8");
|
|
@@ -1310,13 +1340,13 @@ async function scanFile(path, packPath, json) {
|
|
|
1310
1340
|
* not come up inspecting nothing.
|
|
1311
1341
|
*/
|
|
1312
1342
|
async function mcp(root, packPath, ledgerPath) {
|
|
1313
|
-
const { serveMcp } = await import("./mcp-
|
|
1343
|
+
const { serveMcp } = await import("./mcp-doNYU-1C.js");
|
|
1314
1344
|
const { resolve: resolve$1 } = await import("node:path");
|
|
1315
1345
|
const bundled = [join(import.meta.dirname, "secret-catcher.prae.json"), join(import.meta.dirname, "..", "..", "..", "examples", "secret-catcher.prae.json")].find((candidate) => existsSync(candidate));
|
|
1316
1346
|
const chosen = packPath ?? bundled;
|
|
1317
1347
|
if (chosen === void 0) return fail("mcp needs --pack <file>; no bundled secret-catcher found");
|
|
1318
1348
|
const pack = await loadPack(chosen);
|
|
1319
|
-
const { Ledger: Ledger$1 } = await import("./src-
|
|
1349
|
+
const { Ledger: Ledger$1 } = await import("./src-d82lSKVQ.js");
|
|
1320
1350
|
const ledger = ledgerPath === void 0 ? void 0 : Ledger$1.open(ledgerPath);
|
|
1321
1351
|
console.error(`prae mcp: serving ${resolve$1(root)} under ${pack.pack} ${pack.version}` + (ledgerPath === void 0 ? " (no ledger)" : ` · ledger ${ledgerPath}`));
|
|
1322
1352
|
await serveMcp({
|
|
@@ -1368,7 +1398,7 @@ async function explain(path, tool, pairs) {
|
|
|
1368
1398
|
* a key that arrived beside it is checked against nothing.
|
|
1369
1399
|
*/
|
|
1370
1400
|
async function keygen(dir) {
|
|
1371
|
-
const { generateKeypair, fingerprint: fingerprint$1 } = await import("./src-
|
|
1401
|
+
const { generateKeypair, fingerprint: fingerprint$1 } = await import("./src-d82lSKVQ.js");
|
|
1372
1402
|
const { writeFileSync: writeFileSync$1 } = await import("node:fs");
|
|
1373
1403
|
const keys = generateKeypair();
|
|
1374
1404
|
const priv = join(dir, "prae-signing.pem");
|
|
@@ -1383,8 +1413,73 @@ async function keygen(dir) {
|
|
|
1383
1413
|
console.log("receipts, through a channel they trust. Point the plugin at the");
|
|
1384
1414
|
console.log("private key with `signingKey` to sign every row.");
|
|
1385
1415
|
}
|
|
1416
|
+
/**
|
|
1417
|
+
* A sealed keypair for a human authority (PRA-106/107): written as
|
|
1418
|
+
* `<role>.pub.pem` and `<role>.key.sealed.pem`. The passphrase is never an
|
|
1419
|
+
* argument — arguments land in process listings and shell history.
|
|
1420
|
+
*/
|
|
1421
|
+
async function keygenSealed(role, dir) {
|
|
1422
|
+
if (role !== "operator" && role !== "policy" && role !== "ledger") fail("--role is operator, policy or ledger");
|
|
1423
|
+
const stem = role === "ledger" ? "prae-signing" : role;
|
|
1424
|
+
const { sealedKeypair, fingerprint: fingerprint$1 } = await import("./src-d82lSKVQ.js");
|
|
1425
|
+
const { writeFileSync: writeFileSync$1 } = await import("node:fs");
|
|
1426
|
+
const keys = sealedKeypair(process.env.PRAE_KEY_PASSPHRASE ?? await askHidden("passphrase: "));
|
|
1427
|
+
const pub = join(dir, `${stem}.pub.pem`);
|
|
1428
|
+
const sealed = join(dir, `${stem}.key.sealed.pem`);
|
|
1429
|
+
writeFileSync$1(sealed, keys.sealedPrivateKey, { mode: 384 });
|
|
1430
|
+
writeFileSync$1(pub, keys.publicKey);
|
|
1431
|
+
console.log(`sealed ${sealed} (useless without the passphrase)`);
|
|
1432
|
+
console.log(`public ${pub}`);
|
|
1433
|
+
console.log(`fingerprint ${fingerprint$1(keys.publicKey)}`);
|
|
1434
|
+
}
|
|
1435
|
+
/** A detached signature over a pack's bytes (PRA-107). */
|
|
1436
|
+
async function signPack(packPath, keyPath) {
|
|
1437
|
+
const { signBytes, unsealPrivateKey, fingerprint: fingerprint$1 } = await import("./src-d82lSKVQ.js");
|
|
1438
|
+
const { createPublicKey } = await import("node:crypto");
|
|
1439
|
+
const bytes = readFileSync(packPath);
|
|
1440
|
+
const parsed = parsePack(bytes.toString("utf8"));
|
|
1441
|
+
if (!parsed.ok) fail(`${packPath} does not parse; nothing signed`);
|
|
1442
|
+
const passphrase = process.env.PRAE_KEY_PASSPHRASE ?? await askHidden("passphrase: ");
|
|
1443
|
+
let key;
|
|
1444
|
+
try {
|
|
1445
|
+
key = unsealPrivateKey(readFileSync(keyPath, "utf8"), passphrase);
|
|
1446
|
+
} catch {
|
|
1447
|
+
fail("wrong passphrase, or not a sealed key");
|
|
1448
|
+
}
|
|
1449
|
+
await writeFile(`${packPath}.sig`, `${signBytes(bytes, key)}\n`);
|
|
1450
|
+
const pub = createPublicKey(key).export({
|
|
1451
|
+
type: "spki",
|
|
1452
|
+
format: "pem"
|
|
1453
|
+
});
|
|
1454
|
+
console.log(`signed ${packPath}.sig (${parsed.pack.pack} ${parsed.pack.version})`);
|
|
1455
|
+
console.log(`policy key ${fingerprint$1(pub.toString())}`);
|
|
1456
|
+
}
|
|
1457
|
+
/** Read a line from the terminal without echoing it. */
|
|
1458
|
+
function askHidden(prompt) {
|
|
1459
|
+
const input = process.stdin;
|
|
1460
|
+
if (!input.isTTY) fail("no terminal to ask on: set PRAE_KEY_PASSPHRASE");
|
|
1461
|
+
process.stdout.write(prompt);
|
|
1462
|
+
input.setRawMode(true);
|
|
1463
|
+
input.resume();
|
|
1464
|
+
input.setEncoding("utf8");
|
|
1465
|
+
return new Promise((done) => {
|
|
1466
|
+
let typed = "";
|
|
1467
|
+
const onData = (ch) => {
|
|
1468
|
+
if (ch === "\r" || ch === "\n") {
|
|
1469
|
+
input.setRawMode(false);
|
|
1470
|
+
input.pause();
|
|
1471
|
+
input.off("data", onData);
|
|
1472
|
+
process.stdout.write("\n");
|
|
1473
|
+
done(typed);
|
|
1474
|
+
} else if (ch === "") process.exit(130);
|
|
1475
|
+
else if (ch === "") typed = typed.slice(0, -1);
|
|
1476
|
+
else typed += ch;
|
|
1477
|
+
};
|
|
1478
|
+
input.on("data", onData);
|
|
1479
|
+
});
|
|
1480
|
+
}
|
|
1386
1481
|
async function receipt(path, seq, keyPath, out) {
|
|
1387
|
-
const { Ledger: Ledger$1, makeReceipt, fingerprint: fingerprint$1 } = await import("./src-
|
|
1482
|
+
const { Ledger: Ledger$1, makeReceipt, fingerprint: fingerprint$1 } = await import("./src-d82lSKVQ.js");
|
|
1388
1483
|
const { readFileSync: readFileSync$1 } = await import("node:fs");
|
|
1389
1484
|
const { createPublicKey } = await import("node:crypto");
|
|
1390
1485
|
const privateKey = readFileSync$1(keyPath, "utf8");
|
|
@@ -1414,7 +1509,7 @@ async function receipt(path, seq, keyPath, out) {
|
|
|
1414
1509
|
* file and sets the exit code, so scripts can branch on it.
|
|
1415
1510
|
*/
|
|
1416
1511
|
async function verifyReceiptCmd(path) {
|
|
1417
|
-
const { checkReceipt } = await import("./receipt-check-
|
|
1512
|
+
const { checkReceipt } = await import("./receipt-check-CD-W1wXs.js");
|
|
1418
1513
|
let parsed;
|
|
1419
1514
|
try {
|
|
1420
1515
|
parsed = JSON.parse(await readFile(path, "utf8"));
|
|
@@ -1445,7 +1540,7 @@ async function test(suitePath, packOverride, asJson) {
|
|
|
1445
1540
|
} catch (error) {
|
|
1446
1541
|
return fail(`${suitePath} is not valid JSON: ${error instanceof Error ? error.message : error}`);
|
|
1447
1542
|
}
|
|
1448
|
-
const { parseSuite, renderSuite, runSuite } = await import("./test-run-
|
|
1543
|
+
const { parseSuite, renderSuite, runSuite } = await import("./test-run-XNIRHKOu.js");
|
|
1449
1544
|
const suite = parseSuite(parsed);
|
|
1450
1545
|
if (!suite.ok) {
|
|
1451
1546
|
console.error(`prae: ${suitePath} is not a valid test suite`);
|
|
@@ -1456,7 +1551,7 @@ async function test(suitePath, packOverride, asJson) {
|
|
|
1456
1551
|
if (chosen === void 0) return fail("test needs a pack: set `pack` in the suite or pass --pack");
|
|
1457
1552
|
const pack = await loadPack(chosen);
|
|
1458
1553
|
const { capabilityForTool } = await import("capmark");
|
|
1459
|
-
const { scan } = await import("./src-
|
|
1554
|
+
const { scan } = await import("./src-CwBEFhww.js");
|
|
1460
1555
|
const inspect = pack.rules.some((rule) => rule.when.secrets !== void 0) ? (path) => {
|
|
1461
1556
|
try {
|
|
1462
1557
|
return scan(readFileSync(path, "utf8"));
|
|
@@ -1472,7 +1567,7 @@ async function test(suitePath, packOverride, asJson) {
|
|
|
1472
1567
|
else for (const line of renderSuite(result)) console.log(line);
|
|
1473
1568
|
if (!result.ok) process.exit(1);
|
|
1474
1569
|
}
|
|
1475
|
-
async function verify(path, keyPath) {
|
|
1570
|
+
async function verify(path, keyPath, checkpointPath) {
|
|
1476
1571
|
let ledger;
|
|
1477
1572
|
try {
|
|
1478
1573
|
ledger = Ledger.open(path);
|
|
@@ -1484,8 +1579,15 @@ async function verify(path, keyPath) {
|
|
|
1484
1579
|
const entries = ledger.read();
|
|
1485
1580
|
const last = entries.at(-1);
|
|
1486
1581
|
console.log(`${result.entries} entries · chain intact${last === void 0 ? "" : ` · seal ${seal(last)}`}`);
|
|
1582
|
+
if (checkpointPath !== void 0) {
|
|
1583
|
+
const { checkCheckpoint } = await import("./checkpoint-BWSuhZ5h.js");
|
|
1584
|
+
const publicKey = keyPath === void 0 ? void 0 : readFileSync(keyPath, "utf8");
|
|
1585
|
+
const verdict = checkCheckpoint(entries, JSON.parse(readFileSync(checkpointPath, "utf8")), publicKey);
|
|
1586
|
+
for (const line of verdict.lines) (verdict.ok ? console.log : console.error)(`checkpoint: ${line}`);
|
|
1587
|
+
if (!verdict.ok) process.exitCode = 1;
|
|
1588
|
+
}
|
|
1487
1589
|
if (keyPath !== void 0) {
|
|
1488
|
-
const { verifySignature: verifySignature$1, fingerprint: fingerprint$1 } = await import("./src-
|
|
1590
|
+
const { verifySignature: verifySignature$1, fingerprint: fingerprint$1 } = await import("./src-d82lSKVQ.js");
|
|
1489
1591
|
const { readFileSync: readFileSync$1 } = await import("node:fs");
|
|
1490
1592
|
const publicKey = readFileSync$1(keyPath, "utf8");
|
|
1491
1593
|
let signed = 0;
|
|
@@ -1563,7 +1665,7 @@ async function report(path, out, packPath) {
|
|
|
1563
1665
|
* that step is a proposal a human approves, and it starts from this list.
|
|
1564
1666
|
*/
|
|
1565
1667
|
async function instructions(from, asJson) {
|
|
1566
|
-
const { ingest } = await import("./src-
|
|
1668
|
+
const { ingest } = await import("./src-d4c_n6wd.js");
|
|
1567
1669
|
const result = ingest(from);
|
|
1568
1670
|
if (asJson) {
|
|
1569
1671
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -1599,7 +1701,7 @@ async function instructions(from, asJson) {
|
|
|
1599
1701
|
* on purpose rather than something that happened to their policy.
|
|
1600
1702
|
*/
|
|
1601
1703
|
async function propose(from, options) {
|
|
1602
|
-
const [{ ingest }, { extract }] = await Promise.all([import("./src-
|
|
1704
|
+
const [{ ingest }, { extract }] = await Promise.all([import("./src-d4c_n6wd.js"), import("./src-CBGZVvt7.js")]);
|
|
1603
1705
|
const read = ingest(from);
|
|
1604
1706
|
if (read.files.length === 0) {
|
|
1605
1707
|
console.log(`No instruction files found at or above ${from}.`);
|
|
@@ -1659,9 +1761,9 @@ async function consoleOut(path, argv) {
|
|
|
1659
1761
|
const instructionsDir = flag(argv, "instructions");
|
|
1660
1762
|
if (instructionsDir !== void 0) {
|
|
1661
1763
|
const [{ ingest }, { extract }, { renderInstructions }] = await Promise.all([
|
|
1662
|
-
import("./src-
|
|
1663
|
-
import("./src-
|
|
1664
|
-
import("./instructions-
|
|
1764
|
+
import("./src-d4c_n6wd.js"),
|
|
1765
|
+
import("./src-CBGZVvt7.js"),
|
|
1766
|
+
import("./instructions-Bg5puOen.js")
|
|
1665
1767
|
]);
|
|
1666
1768
|
const read = ingest(instructionsDir);
|
|
1667
1769
|
instructionsView = renderInstructions(data, read, extract(read.statements));
|
|
@@ -1759,6 +1861,60 @@ async function init(argv) {
|
|
|
1759
1861
|
return fail(error instanceof Error ? error.message : String(error));
|
|
1760
1862
|
}
|
|
1761
1863
|
}
|
|
1864
|
+
async function checkpoint(path, keyPath, out) {
|
|
1865
|
+
const { makeCheckpoint } = await import("./checkpoint-BWSuhZ5h.js");
|
|
1866
|
+
let ledger;
|
|
1867
|
+
try {
|
|
1868
|
+
ledger = Ledger.open(path);
|
|
1869
|
+
} catch (error) {
|
|
1870
|
+
return fail(error instanceof Error ? error.message : String(error));
|
|
1871
|
+
}
|
|
1872
|
+
const chain$1 = ledger.verify();
|
|
1873
|
+
if (!chain$1.ok) return fail(`chain broken at line ${chain$1.problems[0]?.line}: ${chain$1.problems[0]?.message} — a checkpoint of a broken chain would certify the break`);
|
|
1874
|
+
const privateKey = keyPath === void 0 ? void 0 : readFileSync(keyPath, "utf8");
|
|
1875
|
+
const cp = makeCheckpoint(ledger.read(), path.split("/").at(-1) ?? path, privateKey);
|
|
1876
|
+
const text = `${JSON.stringify(cp, null, 2)}\n`;
|
|
1877
|
+
if (out === void 0) console.log(text);
|
|
1878
|
+
else {
|
|
1879
|
+
await mkdir(dirname(out), { recursive: true });
|
|
1880
|
+
await writeFile(out, text);
|
|
1881
|
+
console.log(`checkpoint ${out} · ${cp.entries} rows · head ${cp.head.hash.slice(0, 16)}…${cp.sig === void 0 ? " · unsigned" : ` · signed ${cp.fingerprint}`}`);
|
|
1882
|
+
console.log("Keep it where this machine cannot write. `prae verify --checkpoint` later detects rows removed or rewritten since.");
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
async function exportLedger(path, options) {
|
|
1886
|
+
const { toOcsf, ndjson, sendToHec } = await import("./ocsf-DhVGbBUw.js");
|
|
1887
|
+
const { createRequire } = await import("node:module");
|
|
1888
|
+
const { version } = createRequire(import.meta.url)("../package.json");
|
|
1889
|
+
let ledger;
|
|
1890
|
+
try {
|
|
1891
|
+
ledger = Ledger.open(path);
|
|
1892
|
+
} catch (error) {
|
|
1893
|
+
return fail(error instanceof Error ? error.message : String(error));
|
|
1894
|
+
}
|
|
1895
|
+
const events = toOcsf(ledger.read(), {
|
|
1896
|
+
product: {
|
|
1897
|
+
name: "prae-gate",
|
|
1898
|
+
version
|
|
1899
|
+
},
|
|
1900
|
+
ledger: path
|
|
1901
|
+
});
|
|
1902
|
+
if (options.hec !== void 0) {
|
|
1903
|
+
if (options.token === void 0) fail("--hec needs --hec-token");
|
|
1904
|
+
const sent = await sendToHec(events, options.hec, options.token);
|
|
1905
|
+
if (sent.status < 200 || sent.status >= 300) return fail(`HEC answered ${sent.status}: ${sent.body.slice(0, 200)}`);
|
|
1906
|
+
console.log(`${sent.sent} events → ${options.hec} (${sent.status})`);
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
const text = ndjson(events);
|
|
1910
|
+
if (options.out === void 0) {
|
|
1911
|
+
process.stdout.write(text);
|
|
1912
|
+
return;
|
|
1913
|
+
}
|
|
1914
|
+
await mkdir(dirname(options.out), { recursive: true });
|
|
1915
|
+
await writeFile(options.out, text);
|
|
1916
|
+
console.log(`${events.length} OCSF events → ${options.out} · seals carried under unmapped; the signature covers the ledger row, not the event`);
|
|
1917
|
+
}
|
|
1762
1918
|
async function main(argv) {
|
|
1763
1919
|
const [command, ...rest] = argv;
|
|
1764
1920
|
switch (command) {
|
|
@@ -1782,7 +1938,17 @@ async function main(argv) {
|
|
|
1782
1938
|
if (root === void 0) fail("mcp needs --root <dir>; serving without a boundary is not a gate");
|
|
1783
1939
|
return mcp(root, flag(rest, "pack"), flag(rest, "ledger"));
|
|
1784
1940
|
}
|
|
1785
|
-
case "keygen":
|
|
1941
|
+
case "keygen": {
|
|
1942
|
+
const role = flag(rest, "role");
|
|
1943
|
+
if (role !== void 0) return keygenSealed(role, flag(rest, "out") ?? ".");
|
|
1944
|
+
return keygen(flag(rest, "out") ?? ".");
|
|
1945
|
+
}
|
|
1946
|
+
case "sign-pack": {
|
|
1947
|
+
const [path] = rest;
|
|
1948
|
+
const key = flag(rest, "key");
|
|
1949
|
+
if (path === void 0 || key === void 0) fail("sign-pack needs <pack.json> --key <policy.key.sealed.pem>");
|
|
1950
|
+
return signPack(path, key);
|
|
1951
|
+
}
|
|
1786
1952
|
case "receipt": {
|
|
1787
1953
|
const [path, seq] = rest;
|
|
1788
1954
|
const key = flag(rest, "key");
|
|
@@ -1798,7 +1964,23 @@ async function main(argv) {
|
|
|
1798
1964
|
case "verify": {
|
|
1799
1965
|
const path = rest[0];
|
|
1800
1966
|
if (path === void 0) fail("verify needs a ledger file");
|
|
1801
|
-
return verify(path, flag(rest, "key"));
|
|
1967
|
+
return verify(path, flag(rest, "key"), flag(rest, "checkpoint"));
|
|
1968
|
+
}
|
|
1969
|
+
case "checkpoint": {
|
|
1970
|
+
const path = rest[0];
|
|
1971
|
+
if (path === void 0) fail("checkpoint needs a ledger file");
|
|
1972
|
+
return checkpoint(path, flag(rest, "key"), flag(rest, "out"));
|
|
1973
|
+
}
|
|
1974
|
+
case "export": {
|
|
1975
|
+
const path = rest[0];
|
|
1976
|
+
if (path === void 0) fail("export needs a ledger file");
|
|
1977
|
+
const format = flag(rest, "format") ?? "ocsf";
|
|
1978
|
+
if (format !== "ocsf") fail(`unknown format ${format}; only ocsf today`);
|
|
1979
|
+
return exportLedger(path, {
|
|
1980
|
+
out: flag(rest, "out"),
|
|
1981
|
+
hec: flag(rest, "hec"),
|
|
1982
|
+
token: flag(rest, "hec-token")
|
|
1983
|
+
});
|
|
1802
1984
|
}
|
|
1803
1985
|
case "verify-receipt": {
|
|
1804
1986
|
const path = rest[0];
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "./evaluate-wMPkjcIC.js";
|
|
2
|
-
import "./views-
|
|
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-
|
|
4
|
-
import "./src-
|
|
2
|
+
import "./views-CIg2ZNG0.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-7yLG89gV.js";
|
|
4
|
+
import "./src-D7g4lvaY.js";
|
|
5
5
|
|
|
6
6
|
export { applyWrite, defaultProfile, dshHome, main, planWrite, praeRow, profileAt };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as evaluate } from "./evaluate-wMPkjcIC.js";
|
|
2
|
-
import { a as scan, n as MAX_SCAN_BYTES } from "./src-
|
|
2
|
+
import { a as scan, n as MAX_SCAN_BYTES } from "./src-B25SHWcz.js";
|
|
3
3
|
import { readFile, readdir, stat } from "node:fs/promises";
|
|
4
4
|
import { resolve, sep } from "node:path";
|
|
5
5
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|