@aarmos/bridge 0.1.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/LICENSE +190 -0
- package/README.md +280 -0
- package/dist/bridge.js +129 -0
- package/dist/bridge.js.map +1 -0
- package/dist/commands/attest.js +62 -0
- package/dist/commands/attest.js.map +1 -0
- package/dist/commands/demo.js +169 -0
- package/dist/commands/demo.js.map +1 -0
- package/dist/commands/dev.js +56 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/pack.js +94 -0
- package/dist/commands/pack.js.map +1 -0
- package/dist/commands/playbook.js +55 -0
- package/dist/commands/playbook.js.map +1 -0
- package/dist/commands/run.js +279 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/simulate.js +181 -0
- package/dist/commands/simulate.js.map +1 -0
- package/dist/commands/verify.js +81 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/index.js +231 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/attest.js +160 -0
- package/dist/lib/attest.js.map +1 -0
- package/dist/lib/http-broker.js +82 -0
- package/dist/lib/http-broker.js.map +1 -0
- package/dist/lib/mcp-broker.js +80 -0
- package/dist/lib/mcp-broker.js.map +1 -0
- package/dist/lib/pack.js +80 -0
- package/dist/lib/pack.js.map +1 -0
- package/dist/lib/playbook.js +199 -0
- package/dist/lib/playbook.js.map +1 -0
- package/dist/lib/scope-templates.js +129 -0
- package/dist/lib/scope-templates.js.map +1 -0
- package/dist/rpc/router.js +321 -0
- package/dist/rpc/router.js.map +1 -0
- package/dist/session.js +100 -0
- package/dist/session.js.map +1 -0
- package/dist/watchers/files.js +31 -0
- package/dist/watchers/files.js.map +1 -0
- package/dist/watchers/git.js +39 -0
- package/dist/watchers/git.js.map +1 -0
- package/package.json +67 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { startDev } from "./commands/dev.js";
|
|
4
|
+
import { runDemo } from "./commands/demo.js";
|
|
5
|
+
import { startRun } from "./commands/run.js";
|
|
6
|
+
import { runSimulate } from "./commands/simulate.js";
|
|
7
|
+
import { playbookKeygen, playbookSign, playbookVerify } from "./commands/playbook.js";
|
|
8
|
+
import { packInstall, packList, packVerify } from "./commands/pack.js";
|
|
9
|
+
import { runAttest, runAttestVerify } from "./commands/attest.js";
|
|
10
|
+
const program = new Command();
|
|
11
|
+
program
|
|
12
|
+
.name("aarmos")
|
|
13
|
+
.description("Aarmos CLI bridge — native host for the Aarmos PWA")
|
|
14
|
+
.version("0.0.0-dev");
|
|
15
|
+
// Repeatable-option collector. Commander calls the coerce function once per
|
|
16
|
+
// occurrence with the previous accumulator as the second arg.
|
|
17
|
+
function collect(value, prev) {
|
|
18
|
+
return [...prev, value];
|
|
19
|
+
}
|
|
20
|
+
program
|
|
21
|
+
.command("dev")
|
|
22
|
+
.description("Start the local bridge and pair with the PWA")
|
|
23
|
+
.option("-p, --port <port>", "WebSocket port to bind on 127.0.0.1", "7423")
|
|
24
|
+
.option("--pwa <url>", "PWA base URL to open for pairing", process.env.AARMOS_PWA_URL ?? "https://www.aarmatix.com")
|
|
25
|
+
.option("--no-open", "Do not auto-open the PWA in the browser")
|
|
26
|
+
.option("--allow-shell <bins>", "Comma-separated additional shell binaries to allow (in addition to defaults)", "")
|
|
27
|
+
.option("--allow-sql <alias=dsn>", "Register a read-only Postgres target. Repeatable. Example: --allow-sql prod=postgres://readonly@…/db", collect, [])
|
|
28
|
+
.option("--allow-read <path>", "Allow fs.scopedRead / fs.scopedList inside this absolute path (outside the workspace). Repeatable.", collect, [])
|
|
29
|
+
.action(async (opts) => {
|
|
30
|
+
const sqlAllow = opts.allowSql.map((raw) => {
|
|
31
|
+
const eq = raw.indexOf("=");
|
|
32
|
+
if (eq < 0) {
|
|
33
|
+
throw new Error(`--allow-sql expects <alias>=<dsn>, got: ${raw}`);
|
|
34
|
+
}
|
|
35
|
+
const alias = raw.slice(0, eq).trim();
|
|
36
|
+
const dsn = raw.slice(eq + 1).trim();
|
|
37
|
+
if (!alias || !dsn) {
|
|
38
|
+
throw new Error(`--allow-sql alias and dsn must be non-empty: ${raw}`);
|
|
39
|
+
}
|
|
40
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(alias)) {
|
|
41
|
+
throw new Error(`--allow-sql alias must be [a-zA-Z0-9._-]+: ${alias}`);
|
|
42
|
+
}
|
|
43
|
+
return { alias, dsn };
|
|
44
|
+
});
|
|
45
|
+
await startDev({
|
|
46
|
+
port: Number(opts.port),
|
|
47
|
+
pwaUrl: opts.pwa,
|
|
48
|
+
autoOpen: opts.open !== false,
|
|
49
|
+
extraShellAllow: opts.allowShell
|
|
50
|
+
? String(opts.allowShell).split(",").map((s) => s.trim()).filter(Boolean)
|
|
51
|
+
: [],
|
|
52
|
+
sqlAllow,
|
|
53
|
+
readAllow: opts.allowRead,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
program
|
|
57
|
+
.command("demo")
|
|
58
|
+
.description("Scripted end-to-end walkthrough of the dry-run pipeline (no PWA required)")
|
|
59
|
+
.option("--apply", "Commit the proposed batch after previewing (default: preview only)", false)
|
|
60
|
+
.option("--workspace <path>", "Workspace to run against (default: cwd)")
|
|
61
|
+
.action(async (opts) => {
|
|
62
|
+
await runDemo({ apply: Boolean(opts.apply), workspace: opts.workspace });
|
|
63
|
+
});
|
|
64
|
+
program
|
|
65
|
+
.command("run")
|
|
66
|
+
.description("Headless unattended loop — executes a playbook against the local dry-run pipeline")
|
|
67
|
+
.requiredOption("--playbook <path>", "Path to a JSON playbook file")
|
|
68
|
+
.option("--once", "Run one pass and exit (default: loop)", false)
|
|
69
|
+
.option("--dry", "Simulate only — no side effects. Prints per-step verdicts and exits.", false)
|
|
70
|
+
.option("--enforce-signatures", "Refuse to run unless the playbook is signed (Ed25519)", false)
|
|
71
|
+
.option("--trusted-pubkey <key>", "Only accept playbooks signed by this pubkey. Repeatable. Requires --enforce-signatures.", collect, [])
|
|
72
|
+
.option("--kill-switch <file>", "Halt gracefully when this file exists")
|
|
73
|
+
.option("--receipts <file>", "Append signed receipts here (JSONL)", ".aarmos/receipts.jsonl")
|
|
74
|
+
.option("--max-per-hour <n>", "Ceiling on applied actions per rolling hour", "60")
|
|
75
|
+
.option("--heartbeat-url <url>", "POST receipts + heartbeats to this base URL")
|
|
76
|
+
.option("--heartbeat-token <token>", "Bearer token attached to heartbeat POSTs")
|
|
77
|
+
.option("--workspace <path>", "Workspace to run against (default: cwd)")
|
|
78
|
+
.option("--allow-shell <bins>", "Comma-separated additional shell binaries to allow (in addition to defaults)", "")
|
|
79
|
+
.option("--allow-http <host>", "Allow governed http.call steps to reach this host. Repeatable.", collect, [])
|
|
80
|
+
.option("--allow-mcp <url>", "Allow governed mcp.tool steps to invoke this MCP endpoint. Repeatable.", collect, [])
|
|
81
|
+
.action(async (opts) => {
|
|
82
|
+
await startRun({
|
|
83
|
+
playbook: opts.playbook,
|
|
84
|
+
once: Boolean(opts.once),
|
|
85
|
+
dry: Boolean(opts.dry),
|
|
86
|
+
enforceSignatures: Boolean(opts.enforceSignatures),
|
|
87
|
+
trustedPubKeys: opts.trustedPubkey,
|
|
88
|
+
killSwitch: opts.killSwitch,
|
|
89
|
+
receipts: opts.receipts,
|
|
90
|
+
maxPerHour: Number(opts.maxPerHour),
|
|
91
|
+
heartbeatUrl: opts.heartbeatUrl,
|
|
92
|
+
heartbeatToken: opts.heartbeatToken,
|
|
93
|
+
workspace: opts.workspace,
|
|
94
|
+
extraShellAllow: opts.allowShell
|
|
95
|
+
? String(opts.allowShell).split(",").map((s) => s.trim()).filter(Boolean)
|
|
96
|
+
: [],
|
|
97
|
+
httpAllow: opts.allowHttp,
|
|
98
|
+
mcpAllow: opts.allowMcp,
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
program
|
|
102
|
+
.command("simulate")
|
|
103
|
+
.description("Compile-time governance: replay a playbook through the dry-run pipeline without applying anything")
|
|
104
|
+
.requiredOption("--playbook <path>", "Path to a JSON playbook file")
|
|
105
|
+
.option("--workspace <path>", "Workspace to simulate against (default: cwd)")
|
|
106
|
+
.option("--json", "Emit machine-readable JSON (for CI)", false)
|
|
107
|
+
.option("--strict", "Exit non-zero if any step would fail", false)
|
|
108
|
+
.option("--allow-shell <bins>", "Comma-separated additional shell binaries to allow", "")
|
|
109
|
+
.option("--allow-http <host>", "Allow http.call to this host. Repeatable.", collect, [])
|
|
110
|
+
.option("--allow-mcp <url>", "Allow mcp.tool against this MCP endpoint. Repeatable.", collect, [])
|
|
111
|
+
.action(async (opts) => {
|
|
112
|
+
await runSimulate({
|
|
113
|
+
playbook: opts.playbook,
|
|
114
|
+
workspace: opts.workspace,
|
|
115
|
+
json: Boolean(opts.json),
|
|
116
|
+
strict: Boolean(opts.strict),
|
|
117
|
+
extraShellAllow: opts.allowShell
|
|
118
|
+
? String(opts.allowShell).split(",").map((s) => s.trim()).filter(Boolean)
|
|
119
|
+
: [],
|
|
120
|
+
httpAllow: opts.allowHttp,
|
|
121
|
+
mcpAllow: opts.allowMcp,
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
const playbook = program
|
|
125
|
+
.command("playbook")
|
|
126
|
+
.description("Sign, verify, and manage Ed25519 keys for playbooks (optional; only enforced under --enforce-signatures)");
|
|
127
|
+
playbook
|
|
128
|
+
.command("keygen")
|
|
129
|
+
.description("Generate an Ed25519 signing keypair")
|
|
130
|
+
.option("--out <dir>", "Directory to write keys into", ".aarmos/keys")
|
|
131
|
+
.option("--name <name>", "Basename for the key files", "playbook")
|
|
132
|
+
.action(async (opts) => {
|
|
133
|
+
await playbookKeygen({ out: opts.out, name: opts.name });
|
|
134
|
+
});
|
|
135
|
+
playbook
|
|
136
|
+
.command("sign")
|
|
137
|
+
.description("Sign a playbook in place (or to --out)")
|
|
138
|
+
.requiredOption("--playbook <path>", "Playbook to sign")
|
|
139
|
+
.requiredOption("--key <pem>", "Ed25519 private key PEM file")
|
|
140
|
+
.option("--out <path>", "Write signed playbook here (default: overwrite in place)")
|
|
141
|
+
.action(async (opts) => {
|
|
142
|
+
await playbookSign({ playbook: opts.playbook, key: opts.key, out: opts.out });
|
|
143
|
+
});
|
|
144
|
+
playbook
|
|
145
|
+
.command("verify")
|
|
146
|
+
.description("Verify a signed playbook")
|
|
147
|
+
.requiredOption("--playbook <path>", "Playbook to verify")
|
|
148
|
+
.option("--trusted-pubkey <key>", "Reject if signer is not this pubkey")
|
|
149
|
+
.action(async (opts) => {
|
|
150
|
+
await playbookVerify({ playbook: opts.playbook, trustedPubkey: opts.trustedPubkey });
|
|
151
|
+
});
|
|
152
|
+
const pack = program
|
|
153
|
+
.command("pack")
|
|
154
|
+
.description("Install / list / verify certified playbook packs (signed bundles of playbooks + scope allowlists)");
|
|
155
|
+
pack
|
|
156
|
+
.command("install")
|
|
157
|
+
.description("Install a pack from a local path (or URL — future).")
|
|
158
|
+
.requiredOption("--source <path>", "Path to a pack JSON file")
|
|
159
|
+
.option("--workspace <path>", "Workspace to install into (default: cwd)")
|
|
160
|
+
.action(async (opts) => {
|
|
161
|
+
await packInstall({ source: opts.source, workspace: opts.workspace });
|
|
162
|
+
});
|
|
163
|
+
pack
|
|
164
|
+
.command("list")
|
|
165
|
+
.description("List installed packs")
|
|
166
|
+
.option("--workspace <path>", "Workspace to inspect (default: cwd)")
|
|
167
|
+
.action(async (opts) => {
|
|
168
|
+
await packList({ workspace: opts.workspace });
|
|
169
|
+
});
|
|
170
|
+
pack
|
|
171
|
+
.command("verify")
|
|
172
|
+
.description("Verify a pack's Ed25519 signature")
|
|
173
|
+
.requiredOption("--source <path>", "Path to a pack JSON file")
|
|
174
|
+
.option("--trusted-pubkey <key>", "Reject if signer is not this pubkey")
|
|
175
|
+
.action(async (opts) => {
|
|
176
|
+
await packVerify({ source: opts.source, trustedPubkey: opts.trustedPubkey });
|
|
177
|
+
});
|
|
178
|
+
const attest = program
|
|
179
|
+
.command("attest")
|
|
180
|
+
.description("Export a signed audit bundle (receipts + policy hash + pack hashes + rootHash) — verifiable offline")
|
|
181
|
+
.option("--since <window>", "Only include receipts from this window (e.g. 24h, 7d, or ISO date)", "24h")
|
|
182
|
+
.option("--out <file>", "Where to write the attestation JSON", "audit.attest.json")
|
|
183
|
+
.option("--workspace <path>", "Workspace to attest against (default: cwd)")
|
|
184
|
+
.option("--receipts <file>", "Receipts JSONL to fold in", ".aarmos/receipts.jsonl")
|
|
185
|
+
.option("--playbook <path>", "Include this playbook's hash + signature")
|
|
186
|
+
.option("--policy <path>", "Path to aarmos.json (default: <workspace>/aarmos.json)")
|
|
187
|
+
.option("--sign-key <pem>", "Ed25519 private key PEM — signs the bundle so auditors can verify offline")
|
|
188
|
+
.action(async (opts) => {
|
|
189
|
+
await runAttest({
|
|
190
|
+
out: opts.out,
|
|
191
|
+
since: opts.since,
|
|
192
|
+
workspace: opts.workspace,
|
|
193
|
+
receipts: opts.receipts,
|
|
194
|
+
playbook: opts.playbook,
|
|
195
|
+
policy: opts.policy,
|
|
196
|
+
signKey: opts.signKey,
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
attest
|
|
200
|
+
.command("verify")
|
|
201
|
+
.description("Verify an attestation bundle offline — recomputes rootHash and checks signature")
|
|
202
|
+
.requiredOption("--file <path>", "Attestation JSON to verify")
|
|
203
|
+
.option("--trusted-pubkey <key>", "Reject if signer is not this pubkey")
|
|
204
|
+
.action(async (opts) => {
|
|
205
|
+
await runAttestVerify({ file: opts.file, trustedPubkey: opts.trustedPubkey });
|
|
206
|
+
});
|
|
207
|
+
// aarmos verify — offline AVAR bundle verifier (AVAR spec §6).
|
|
208
|
+
// Uses the same @aarmos/avar-core module the browser drop-zone at
|
|
209
|
+
// /trust/verify uses, so results are bit-identical across runtimes.
|
|
210
|
+
program
|
|
211
|
+
.command("verify")
|
|
212
|
+
.description("Verify an AVAR bundle offline (.avar.zip)")
|
|
213
|
+
.argument("<bundle>", "Path to a .avar.zip bundle")
|
|
214
|
+
.option("--json", "Emit the VerificationReport as JSON on stdout")
|
|
215
|
+
.option("--quiet", "Suppress all human-readable output")
|
|
216
|
+
.option("--strict", "Exit non-zero on 'valid-with-warnings'")
|
|
217
|
+
.action(async (bundle, opts) => {
|
|
218
|
+
const { runVerify } = await import("./commands/verify.js");
|
|
219
|
+
const code = await runVerify({
|
|
220
|
+
file: bundle,
|
|
221
|
+
json: !!opts.json,
|
|
222
|
+
quiet: !!opts.quiet,
|
|
223
|
+
strict: !!opts.strict,
|
|
224
|
+
});
|
|
225
|
+
process.exit(code);
|
|
226
|
+
});
|
|
227
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
228
|
+
console.error("[aarmos] fatal:", err);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
});
|
|
231
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// Attestation export — the audit-ready wedge.
|
|
2
|
+
//
|
|
3
|
+
// `aarmos attest` produces a single self-contained JSON artifact:
|
|
4
|
+
//
|
|
5
|
+
// {
|
|
6
|
+
// version: 1,
|
|
7
|
+
// createdAt, since, until,
|
|
8
|
+
// playbook?: { name, hash, signed, pubKey? },
|
|
9
|
+
// policyHash?: <sha256 of aarmos.json>,
|
|
10
|
+
// packs: [{ name, version, hash }],
|
|
11
|
+
// receipts: [ ... hash-chained records from .aarmos/receipts.jsonl ... ],
|
|
12
|
+
// rootHash: <sha256 of the concatenated per-receipt hashes>,
|
|
13
|
+
// signature?: { alg: "ed25519", pubKey, sig } // over the body sans signature
|
|
14
|
+
// }
|
|
15
|
+
//
|
|
16
|
+
// `aarmos attest verify <file>` re-computes rootHash from the embedded
|
|
17
|
+
// receipts and, if a signature is present, verifies it. The verifier
|
|
18
|
+
// is a pure function of the file — no network, no daemon — so an
|
|
19
|
+
// auditor can run it air-gapped.
|
|
20
|
+
import fs from "node:fs/promises";
|
|
21
|
+
import path from "node:path";
|
|
22
|
+
import crypto from "node:crypto";
|
|
23
|
+
import { canonicalize, hashPlaybook, loadPlaybook, } from "./playbook.js";
|
|
24
|
+
import { listInstalledPacks, hashPack } from "./pack.js";
|
|
25
|
+
export async function buildAttestation(opts) {
|
|
26
|
+
const until = opts.until ?? new Date();
|
|
27
|
+
const since = opts.since ?? new Date(0);
|
|
28
|
+
// --- receipts ---
|
|
29
|
+
const receipts = [];
|
|
30
|
+
try {
|
|
31
|
+
const raw = await fs.readFile(opts.receiptsFile, "utf8");
|
|
32
|
+
for (const line of raw.split("\n")) {
|
|
33
|
+
const t = line.trim();
|
|
34
|
+
if (!t)
|
|
35
|
+
continue;
|
|
36
|
+
let obj;
|
|
37
|
+
try {
|
|
38
|
+
obj = JSON.parse(t);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const at = Date.parse(obj.ts);
|
|
44
|
+
if (Number.isFinite(at) && at >= since.getTime() && at <= until.getTime()) {
|
|
45
|
+
receipts.push(obj);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch { /* no receipts yet */ }
|
|
50
|
+
const rootHash = merkleRoot(receipts.map((r) => r.hash ?? ""));
|
|
51
|
+
// --- playbook ---
|
|
52
|
+
let playbookEntry;
|
|
53
|
+
if (opts.playbookFile) {
|
|
54
|
+
const pb = await loadPlaybook(opts.playbookFile);
|
|
55
|
+
playbookEntry = {
|
|
56
|
+
name: pb.name,
|
|
57
|
+
hash: hashPlaybook(pb),
|
|
58
|
+
signed: Boolean(pb.signature),
|
|
59
|
+
pubKey: pb.signature?.pubKey,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// --- policy config (aarmos.json) ---
|
|
63
|
+
let policyHash;
|
|
64
|
+
if (opts.policyFile) {
|
|
65
|
+
try {
|
|
66
|
+
const raw = await fs.readFile(opts.policyFile, "utf8");
|
|
67
|
+
policyHash = crypto.createHash("sha256").update(raw).digest("hex");
|
|
68
|
+
}
|
|
69
|
+
catch { /* no policy file — omit */ }
|
|
70
|
+
}
|
|
71
|
+
// --- installed packs ---
|
|
72
|
+
const packs = opts.packsDir
|
|
73
|
+
? (await listInstalledPacks(opts.packsDir)).map((p) => ({
|
|
74
|
+
name: p.name,
|
|
75
|
+
version: p.version,
|
|
76
|
+
hash: hashPack(p),
|
|
77
|
+
}))
|
|
78
|
+
: [];
|
|
79
|
+
return {
|
|
80
|
+
version: 1,
|
|
81
|
+
createdAt: new Date().toISOString(),
|
|
82
|
+
since: since.toISOString(),
|
|
83
|
+
until: until.toISOString(),
|
|
84
|
+
playbook: playbookEntry,
|
|
85
|
+
policyHash,
|
|
86
|
+
packs,
|
|
87
|
+
receipts,
|
|
88
|
+
rootHash,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
export function attestationBody(a) {
|
|
92
|
+
const { signature: _s, ...rest } = a;
|
|
93
|
+
void _s;
|
|
94
|
+
return canonicalize(rest);
|
|
95
|
+
}
|
|
96
|
+
export async function signAttestation(a, privateKeyPem) {
|
|
97
|
+
const key = crypto.createPrivateKey(privateKeyPem);
|
|
98
|
+
const sig = crypto.sign(null, Buffer.from(attestationBody(a)), key);
|
|
99
|
+
const pubDer = crypto.createPublicKey(key).export({ format: "der", type: "spki" });
|
|
100
|
+
const rawPub = pubDer.subarray(pubDer.length - 32);
|
|
101
|
+
const b64u = (b) => b.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
102
|
+
return { ...a, signature: { alg: "ed25519", pubKey: b64u(rawPub), sig: b64u(sig) } };
|
|
103
|
+
}
|
|
104
|
+
export function verifyAttestation(a) {
|
|
105
|
+
const recomputed = merkleRoot(a.receipts.map((r) => r.hash ?? ""));
|
|
106
|
+
if (recomputed !== a.rootHash) {
|
|
107
|
+
return { ok: false, reason: `rootHash mismatch (bundle=${a.rootHash} computed=${recomputed})` };
|
|
108
|
+
}
|
|
109
|
+
if (!a.signature)
|
|
110
|
+
return { ok: true };
|
|
111
|
+
if (a.signature.alg !== "ed25519") {
|
|
112
|
+
return { ok: false, reason: `unsupported alg: ${a.signature.alg}` };
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
const spkiPrefix = Buffer.from([
|
|
116
|
+
0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00,
|
|
117
|
+
]);
|
|
118
|
+
const b64uDecode = (s) => Buffer.from(s.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((s.length + 3) % 4), "base64");
|
|
119
|
+
const rawPub = b64uDecode(a.signature.pubKey);
|
|
120
|
+
if (rawPub.length !== 32)
|
|
121
|
+
return { ok: false, reason: "pubKey must be 32 bytes" };
|
|
122
|
+
const der = Buffer.concat([spkiPrefix, rawPub]);
|
|
123
|
+
const key = crypto.createPublicKey({ key: der, format: "der", type: "spki" });
|
|
124
|
+
const ok = crypto.verify(null, Buffer.from(attestationBody(a)), key, b64uDecode(a.signature.sig));
|
|
125
|
+
return ok ? { ok: true } : { ok: false, reason: "signature does not match bundle body" };
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
return { ok: false, reason: err.message };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/** Sequential hash chain: h(0) = sha256(""), h(i) = sha256(h(i-1) + receipts[i]). */
|
|
132
|
+
function merkleRoot(hashes) {
|
|
133
|
+
let acc = crypto.createHash("sha256").update("").digest("hex");
|
|
134
|
+
for (const h of hashes) {
|
|
135
|
+
acc = crypto.createHash("sha256").update(acc + h).digest("hex");
|
|
136
|
+
}
|
|
137
|
+
return acc;
|
|
138
|
+
}
|
|
139
|
+
export function parseSince(s) {
|
|
140
|
+
const m = /^(\d+)\s*([hdm])$/.exec(s.trim());
|
|
141
|
+
if (m) {
|
|
142
|
+
const n = Number(m[1]);
|
|
143
|
+
const unit = m[2];
|
|
144
|
+
const ms = unit === "h" ? n * 3_600_000 : unit === "d" ? n * 86_400_000 : n * 60_000;
|
|
145
|
+
return new Date(Date.now() - ms);
|
|
146
|
+
}
|
|
147
|
+
const t = Date.parse(s);
|
|
148
|
+
if (Number.isFinite(t))
|
|
149
|
+
return new Date(t);
|
|
150
|
+
throw new Error(`--since: expected e.g. 24h, 7d, 30m, or an ISO date (got: ${s})`);
|
|
151
|
+
}
|
|
152
|
+
export async function writeAttestation(file, a) {
|
|
153
|
+
await fs.mkdir(path.dirname(path.resolve(file)), { recursive: true });
|
|
154
|
+
await fs.writeFile(path.resolve(file), JSON.stringify(a, null, 2) + "\n", "utf8");
|
|
155
|
+
}
|
|
156
|
+
export async function readAttestation(file) {
|
|
157
|
+
const raw = await fs.readFile(path.resolve(file), "utf8");
|
|
158
|
+
return JSON.parse(raw);
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=attest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attest.js","sourceRoot":"","sources":["../../src/lib/attest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// HTTP broker step kind — governed `http.call`.
|
|
2
|
+
//
|
|
3
|
+
// Scope model:
|
|
4
|
+
// - The operator opts hosts in via `--allow-http <host>` (repeatable).
|
|
5
|
+
// A step that targets a host outside the allowlist fails at simulate
|
|
6
|
+
// time and at run time, before any bytes leave the machine.
|
|
7
|
+
// - Bearer tokens are read from env vars named by the step
|
|
8
|
+
// (`bearerEnv`) — never inlined into the playbook JSON. Signing a
|
|
9
|
+
// playbook therefore does NOT bind a secret; the token is bound at
|
|
10
|
+
// the daemon.
|
|
11
|
+
// - Responses are capped at 1 MiB and previewed as
|
|
12
|
+
// `{ method, url, headers (redacted), bodyBytes }` so the PWA card
|
|
13
|
+
// stays useful without leaking a token.
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
export const HttpCallParams = z.object({
|
|
16
|
+
method: z.enum(["GET", "HEAD", "OPTIONS", "POST", "PUT", "PATCH", "DELETE"]).default("GET"),
|
|
17
|
+
url: z.string().url(),
|
|
18
|
+
headers: z.record(z.string()).optional(),
|
|
19
|
+
body: z.string().optional(), // caller stringifies JSON — keeps signing deterministic
|
|
20
|
+
bearerEnv: z.string().optional(),
|
|
21
|
+
timeoutMs: z.number().int().min(100).max(60_000).default(15_000),
|
|
22
|
+
});
|
|
23
|
+
export function hostOf(url) {
|
|
24
|
+
try {
|
|
25
|
+
return new URL(url).host;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return "";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function previewHttpCall(p, allowed) {
|
|
32
|
+
const host = hostOf(p.url);
|
|
33
|
+
if (!allowed.has(host)) {
|
|
34
|
+
throw new Error(`http.call host not allowed: ${host} (start with --allow-http ${host} or add it to the pack)`);
|
|
35
|
+
}
|
|
36
|
+
const redactedHeaders = { ...(p.headers ?? {}) };
|
|
37
|
+
for (const k of Object.keys(redactedHeaders)) {
|
|
38
|
+
if (/^authorization$/i.test(k))
|
|
39
|
+
redactedHeaders[k] = "***";
|
|
40
|
+
}
|
|
41
|
+
if (p.bearerEnv)
|
|
42
|
+
redactedHeaders["Authorization"] = `Bearer $${p.bearerEnv}`;
|
|
43
|
+
return {
|
|
44
|
+
method: p.method,
|
|
45
|
+
url: p.url,
|
|
46
|
+
host,
|
|
47
|
+
headers: redactedHeaders,
|
|
48
|
+
bodyBytes: p.body?.length ?? 0,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export async function executeHttpCall(p, allowed) {
|
|
52
|
+
const host = hostOf(p.url);
|
|
53
|
+
if (!allowed.has(host))
|
|
54
|
+
throw new Error(`http.call host not allowed: ${host}`);
|
|
55
|
+
const headers = { ...(p.headers ?? {}) };
|
|
56
|
+
if (p.bearerEnv) {
|
|
57
|
+
const tok = process.env[p.bearerEnv];
|
|
58
|
+
if (!tok)
|
|
59
|
+
throw new Error(`http.call: env ${p.bearerEnv} is not set on this host`);
|
|
60
|
+
headers["Authorization"] = `Bearer ${tok}`;
|
|
61
|
+
}
|
|
62
|
+
const controller = new AbortController();
|
|
63
|
+
const t = setTimeout(() => controller.abort(), p.timeoutMs);
|
|
64
|
+
try {
|
|
65
|
+
const res = await fetch(p.url, {
|
|
66
|
+
method: p.method,
|
|
67
|
+
headers,
|
|
68
|
+
body: p.body,
|
|
69
|
+
signal: controller.signal,
|
|
70
|
+
});
|
|
71
|
+
const bodyBuf = new Uint8Array(await res.arrayBuffer());
|
|
72
|
+
const capped = bodyBuf.length > 1_048_576 ? bodyBuf.slice(0, 1_048_576) : bodyBuf;
|
|
73
|
+
const bodySnippet = new TextDecoder("utf-8", { fatal: false }).decode(capped).slice(0, 4_000);
|
|
74
|
+
const outHeaders = {};
|
|
75
|
+
res.headers.forEach((v, k) => (outHeaders[k] = v));
|
|
76
|
+
return { status: res.status, headers: outHeaders, bodySnippet, bytes: bodyBuf.length };
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
clearTimeout(t);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=http-broker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-broker.js","sourceRoot":"","sources":["../../src/lib/http-broker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// MCP broker step kind — governed `mcp.tool` calls over Streamable HTTP.
|
|
2
|
+
//
|
|
3
|
+
// The daemon speaks raw MCP JSON-RPC 2.0 to remote servers so the CLI
|
|
4
|
+
// stays dependency-free (no @ai-sdk/mcp on the server surface). Every
|
|
5
|
+
// endpoint must be opted in via `--allow-mcp <url>` — this is how we
|
|
6
|
+
// stop a playbook from silently rerouting itself to an attacker's MCP
|
|
7
|
+
// server after signing.
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
export const McpToolParams = z.object({
|
|
10
|
+
endpoint: z.string().url(),
|
|
11
|
+
tool: z.string().min(1),
|
|
12
|
+
args: z.record(z.unknown()).default({}),
|
|
13
|
+
bearerEnv: z.string().optional(),
|
|
14
|
+
/** Optional operator hint for the risk classifier. Defaults to "write". */
|
|
15
|
+
riskHint: z.enum(["read", "write", "destructive"]).optional(),
|
|
16
|
+
timeoutMs: z.number().int().min(100).max(60_000).default(15_000),
|
|
17
|
+
});
|
|
18
|
+
export function previewMcpTool(p, allowed) {
|
|
19
|
+
if (!allowed.has(p.endpoint)) {
|
|
20
|
+
throw new Error(`mcp.tool endpoint not allowed: ${p.endpoint} (start with --allow-mcp ${p.endpoint})`);
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
endpoint: p.endpoint,
|
|
24
|
+
tool: p.tool,
|
|
25
|
+
argKeys: Object.keys(p.args ?? {}),
|
|
26
|
+
riskHint: p.riskHint ?? "write",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export async function executeMcpTool(p, allowed) {
|
|
30
|
+
if (!allowed.has(p.endpoint))
|
|
31
|
+
throw new Error(`mcp.tool endpoint not allowed: ${p.endpoint}`);
|
|
32
|
+
const headers = {
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
// Streamable HTTP servers reject requests without both types.
|
|
35
|
+
Accept: "application/json, text/event-stream",
|
|
36
|
+
};
|
|
37
|
+
if (p.bearerEnv) {
|
|
38
|
+
const tok = process.env[p.bearerEnv];
|
|
39
|
+
if (!tok)
|
|
40
|
+
throw new Error(`mcp.tool: env ${p.bearerEnv} is not set on this host`);
|
|
41
|
+
headers["Authorization"] = `Bearer ${tok}`;
|
|
42
|
+
}
|
|
43
|
+
const body = JSON.stringify({
|
|
44
|
+
jsonrpc: "2.0",
|
|
45
|
+
id: Date.now(),
|
|
46
|
+
method: "tools/call",
|
|
47
|
+
params: { name: p.tool, arguments: p.args ?? {} },
|
|
48
|
+
});
|
|
49
|
+
const controller = new AbortController();
|
|
50
|
+
const t = setTimeout(() => controller.abort(), p.timeoutMs);
|
|
51
|
+
try {
|
|
52
|
+
const res = await fetch(p.endpoint, { method: "POST", headers, body, signal: controller.signal });
|
|
53
|
+
const text = await res.text();
|
|
54
|
+
// Streamable HTTP may return SSE frames; extract the first `data:` payload if so.
|
|
55
|
+
const payload = text.startsWith("event:") || text.includes("\ndata:")
|
|
56
|
+
? extractFirstSseData(text)
|
|
57
|
+
: text;
|
|
58
|
+
let parsed = payload;
|
|
59
|
+
try {
|
|
60
|
+
parsed = JSON.parse(payload);
|
|
61
|
+
}
|
|
62
|
+
catch { /* leave as text */ }
|
|
63
|
+
const envelope = parsed;
|
|
64
|
+
if (envelope && typeof envelope === "object" && "error" in envelope && envelope.error) {
|
|
65
|
+
return { ok: false, error: envelope.error };
|
|
66
|
+
}
|
|
67
|
+
return { ok: res.ok, result: envelope?.result ?? parsed };
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
clearTimeout(t);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function extractFirstSseData(text) {
|
|
74
|
+
for (const line of text.split(/\r?\n/)) {
|
|
75
|
+
if (line.startsWith("data:"))
|
|
76
|
+
return line.slice(5).trim();
|
|
77
|
+
}
|
|
78
|
+
return text;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=mcp-broker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-broker.js","sourceRoot":"","sources":["../../src/lib/mcp-broker.ts"],"names":[],"mappings":"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"}
|