protect-mcp 0.9.3 → 0.9.5
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/CHANGELOG.md +49 -0
- package/README.md +21 -1
- package/dist/{chunk-DX7QOA3E.mjs → chunk-H332ZNJ6.mjs} +1 -1
- package/dist/{chunk-HPKHMVZX.mjs → chunk-WWPQNIVF.mjs} +34 -1
- package/dist/{claim-3EBRPP5U.mjs → claim-RIXFOQM7.mjs} +93 -16
- package/dist/cli.js +728 -311
- package/dist/cli.mjs +224 -12
- package/dist/hook-server.js +34 -1
- package/dist/hook-server.mjs +2 -2
- package/dist/index.js +34 -1
- package/dist/index.mjs +2 -2
- package/dist/sample-6LRP73ZD.mjs +99 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5146,6 +5146,33 @@ function deriveResource(input) {
|
|
|
5146
5146
|
}
|
|
5147
5147
|
return void 0;
|
|
5148
5148
|
}
|
|
5149
|
+
function findField(input, names, depth = 0) {
|
|
5150
|
+
if (depth > 4 || input === null || typeof input !== "object") return void 0;
|
|
5151
|
+
const o = input;
|
|
5152
|
+
const keys = Object.keys(o).sort();
|
|
5153
|
+
for (const k of keys) {
|
|
5154
|
+
if (names.indexOf(k.toLowerCase()) >= 0 && o[k] !== void 0 && o[k] !== null) return o[k];
|
|
5155
|
+
}
|
|
5156
|
+
for (const k of keys) {
|
|
5157
|
+
const v = findField(o[k], names, depth + 1);
|
|
5158
|
+
if (v !== void 0) return v;
|
|
5159
|
+
}
|
|
5160
|
+
return void 0;
|
|
5161
|
+
}
|
|
5162
|
+
function derivePayment(tool, input) {
|
|
5163
|
+
if (deriveCapabilities(tool, input).indexOf("payment") < 0) return void 0;
|
|
5164
|
+
const p = { amount: null, asset: null, recipient_digest: null };
|
|
5165
|
+
const amt = findField(input, ["amount"]);
|
|
5166
|
+
if (typeof amt === "number" && Number.isFinite(amt) && amt >= 0) p.amount = amt;
|
|
5167
|
+
else if (typeof amt === "string" && /^\d{1,15}(\.\d{1,18})?$/.test(amt.trim()) && amt.indexOf(".") >= 0) p.amount = parseFloat(amt);
|
|
5168
|
+
const asset = findField(input, ["asset", "currency", "token"]);
|
|
5169
|
+
if (typeof asset === "string" && asset.trim()) p.asset = asset.trim().slice(0, 64);
|
|
5170
|
+
const to = findField(input, ["payto", "pay_to", "recipient", "destination", "to"]);
|
|
5171
|
+
if (typeof to === "string" && to.trim()) p.recipient_digest = sha256Hex2(to.trim().toLowerCase());
|
|
5172
|
+
const scheme = findField(input, ["scheme"]);
|
|
5173
|
+
if (typeof scheme === "string" && scheme.trim()) p.scheme = scheme.trim().slice(0, 32);
|
|
5174
|
+
return p;
|
|
5175
|
+
}
|
|
5149
5176
|
function buildEnrichment(tool, input) {
|
|
5150
5177
|
const e = {
|
|
5151
5178
|
v: ENRICHMENT_VERSION,
|
|
@@ -5154,6 +5181,8 @@ function buildEnrichment(tool, input) {
|
|
|
5154
5181
|
};
|
|
5155
5182
|
const resource = deriveResource(input);
|
|
5156
5183
|
if (resource) e.resource = resource;
|
|
5184
|
+
const payment = derivePayment(tool, input);
|
|
5185
|
+
if (payment) e.payment = payment;
|
|
5157
5186
|
return e;
|
|
5158
5187
|
}
|
|
5159
5188
|
var ENRICHMENT_VERSION, RULES;
|
|
@@ -5174,7 +5203,11 @@ var init_receipt_enrichment = __esm({
|
|
|
5174
5203
|
{ cap: "secret.adjacent", text: /\.env\b|secret|credential|passwd|password|api[_-]?key|private[_-]?key|\.pem\b|\.key\b|id_rsa|bearer\s|aws_(access|secret)|authorization/ },
|
|
5175
5204
|
{ cap: "destructive", text: /rm\s+-[a-z]*[rf]|\brmdir\b|drop\s+table|truncate\s+table|delete\s+from|reset\s+--hard|--force\b|\bmkfs\b|\bdd\s+if=|shutdown|reboot|kill\s+-9|>\s*\/dev\/sd/ },
|
|
5176
5205
|
{ cap: "financial", text: /\b(order|trade|buy|sell|transfer|wire|payment|withdraw|deposit|swap|invoice|charge|refund|settle)\b/ },
|
|
5177
|
-
{ cap: "data.query", text: /\bselect\s+[\s\S]+\bfrom\b|\binsert\s+into\b|\bupdate\s+[\s\S]+\bset\b|\bdelete\s+from\b/ }
|
|
5206
|
+
{ cap: "data.query", text: /\bselect\s+[\s\S]+\bfrom\b|\binsert\s+into\b|\bupdate\s+[\s\S]+\bset\b|\bdelete\s+from\b/ },
|
|
5207
|
+
// Agent payments (x402 / value transfer). Deliberately BROAD: a false positive
|
|
5208
|
+
// only makes a `claim --no payment` harder to assert (conservative); a false
|
|
5209
|
+
// negative would let a real payment escape the record's payment claims.
|
|
5210
|
+
{ cap: "payment", tool: /(^|[_.-])(pay|payment|x402|checkout)($|[_.-])|wallet.*send|send.*payment/, text: /x402|x-payment|paymentrequirements|maxamountrequired|payto|"pay_to"|eip-3009|transferwithauthorization|payment_intent|send_payment|create_payment/ }
|
|
5178
5211
|
];
|
|
5179
5212
|
}
|
|
5180
5213
|
});
|
|
@@ -5183,15 +5216,19 @@ var init_receipt_enrichment = __esm({
|
|
|
5183
5216
|
var claim_exports = {};
|
|
5184
5217
|
__export(claim_exports, {
|
|
5185
5218
|
ANCHOR_SCHEMA: () => ANCHOR_SCHEMA,
|
|
5219
|
+
CHECKPOINT_SCHEMA: () => CHECKPOINT_SCHEMA,
|
|
5186
5220
|
CLAIM_TYPE: () => CLAIM_TYPE,
|
|
5187
5221
|
DEFAULT_LOG: () => DEFAULT_LOG,
|
|
5188
5222
|
anchorClaim: () => anchorClaim,
|
|
5223
|
+
anchorRecordCheckpoint: () => anchorRecordCheckpoint,
|
|
5189
5224
|
buildAnchorEnvelope: () => buildAnchorEnvelope,
|
|
5190
5225
|
buildClaim: () => buildClaim,
|
|
5226
|
+
buildRecordCheckpoint: () => buildRecordCheckpoint,
|
|
5191
5227
|
checkClaimAnchor: () => checkClaimAnchor,
|
|
5192
5228
|
claimDigest: () => claimDigest,
|
|
5193
5229
|
evaluate: () => evaluate,
|
|
5194
5230
|
leafHash: () => leafHash,
|
|
5231
|
+
lookupPinnedIdentity: () => lookupPinnedIdentity,
|
|
5195
5232
|
merkleRoot: () => merkleRoot,
|
|
5196
5233
|
receiptToLeaf: () => receiptToLeaf,
|
|
5197
5234
|
verifyAnchorEnvelope: () => verifyAnchorEnvelope,
|
|
@@ -5211,7 +5248,13 @@ function receiptToLeaf(e) {
|
|
|
5211
5248
|
const ms = typeof tsRaw === "number" ? tsRaw : typeof tsRaw === "string" ? Date.parse(tsRaw) : NaN;
|
|
5212
5249
|
const t = isFinite(ms) ? new Date(ms).toISOString() : "";
|
|
5213
5250
|
const d = sha256Hex3(canonicalJson(e));
|
|
5214
|
-
|
|
5251
|
+
const leaf = { d, v, c, t };
|
|
5252
|
+
const pay = enr && enr.payment;
|
|
5253
|
+
if (pay && typeof pay === "object") {
|
|
5254
|
+
const amt = pay.amount;
|
|
5255
|
+
leaf.p = typeof amt === "number" && Number.isFinite(amt) ? amt : null;
|
|
5256
|
+
}
|
|
5257
|
+
return leaf;
|
|
5215
5258
|
}
|
|
5216
5259
|
function leafHash(leaf) {
|
|
5217
5260
|
return sha256Hex3(canonicalJson(leaf));
|
|
@@ -5244,6 +5287,10 @@ function evaluate(pred, leaves) {
|
|
|
5244
5287
|
const matched2 = leaves.filter((l) => l.v === pred.verdict).length;
|
|
5245
5288
|
return { statement: `No action was ${pred.verdict}`, holds: matched2 === 0, matched: matched2 };
|
|
5246
5289
|
}
|
|
5290
|
+
if (pred.kind === "payment_under") {
|
|
5291
|
+
const matched2 = leaves.filter((l) => "p" in l && (l.p === null || l.p >= pred.cap)).length;
|
|
5292
|
+
return { statement: `Every payment stayed under ${pred.cap} (unknown amounts count as over)`, holds: matched2 === 0, matched: matched2 };
|
|
5293
|
+
}
|
|
5247
5294
|
const matched = leaves.filter((l) => l.v === pred.verdict).length;
|
|
5248
5295
|
return { statement: `${matched} action${matched === 1 ? " was" : "s were"} ${pred.verdict}`, holds: true, matched };
|
|
5249
5296
|
}
|
|
@@ -5408,11 +5455,9 @@ async function checkClaimAnchor(pack, sidecar, opts) {
|
|
|
5408
5455
|
return out;
|
|
5409
5456
|
}
|
|
5410
5457
|
}
|
|
5411
|
-
async function
|
|
5412
|
-
const
|
|
5413
|
-
|
|
5414
|
-
const doFetch = opts.fetchImpl || globalThis.fetch;
|
|
5415
|
-
if (!doFetch) return { ok: false, claim_digest: envelope.claim_digest, error: "fetch_unavailable", envelope };
|
|
5458
|
+
async function submitEnvelope(envelope, base, fetchImpl) {
|
|
5459
|
+
const doFetch = fetchImpl || globalThis.fetch;
|
|
5460
|
+
if (!doFetch) return { ok: false, error: "fetch_unavailable" };
|
|
5416
5461
|
const encoded = toBase64(new TextEncoder().encode(JSON.stringify(envelope)));
|
|
5417
5462
|
try {
|
|
5418
5463
|
const resp = await doFetch(`${base}/fn/log/anchor-pack`, {
|
|
@@ -5422,22 +5467,86 @@ async function anchorClaim(pack, key, opts) {
|
|
|
5422
5467
|
});
|
|
5423
5468
|
const data = await resp.json().catch(() => null);
|
|
5424
5469
|
if (!resp.ok || !data || !data.ok || typeof data.seq !== "number") {
|
|
5425
|
-
return { ok: false,
|
|
5470
|
+
return { ok: false, error: data && data.error || `http_${resp.status}` };
|
|
5426
5471
|
}
|
|
5472
|
+
return { ok: true, seq: data.seq, anchored_at: data.anchored_at, already_anchored: !!data.already_anchored };
|
|
5473
|
+
} catch {
|
|
5474
|
+
return { ok: false, error: "network_error" };
|
|
5475
|
+
}
|
|
5476
|
+
}
|
|
5477
|
+
async function anchorClaim(pack, key, opts) {
|
|
5478
|
+
const envelope = buildAnchorEnvelope(pack, key, opts.issuedAt);
|
|
5479
|
+
const base = (opts.log || DEFAULT_LOG).replace(/\/+$/, "");
|
|
5480
|
+
const out = await submitEnvelope(envelope, base, opts.fetchImpl);
|
|
5481
|
+
if (!out.ok) return { ok: false, claim_digest: envelope.claim_digest, error: out.error, envelope };
|
|
5482
|
+
return {
|
|
5483
|
+
ok: true,
|
|
5484
|
+
claim_digest: envelope.claim_digest,
|
|
5485
|
+
seq: out.seq,
|
|
5486
|
+
entry_url: `${base}/fn/log/${out.seq}`,
|
|
5487
|
+
anchored_at: out.anchored_at,
|
|
5488
|
+
already_anchored: out.already_anchored,
|
|
5489
|
+
envelope
|
|
5490
|
+
};
|
|
5491
|
+
}
|
|
5492
|
+
function buildRecordCheckpoint(receipts, key, issuedAt) {
|
|
5493
|
+
const leaves = receipts.map(receiptToLeaf);
|
|
5494
|
+
const times = leaves.map((l) => l.t).filter(Boolean).sort();
|
|
5495
|
+
const signed = {
|
|
5496
|
+
type: "evidence_pack",
|
|
5497
|
+
schema: CHECKPOINT_SCHEMA,
|
|
5498
|
+
anchors: "protect-mcp-record",
|
|
5499
|
+
record_root: merkleRoot(leaves.map(leafHash)),
|
|
5500
|
+
total: leaves.length,
|
|
5501
|
+
from: times[0] || "",
|
|
5502
|
+
to: times[times.length - 1] || "",
|
|
5503
|
+
issued_at: issuedAt,
|
|
5504
|
+
verification_key: key.publicKey,
|
|
5505
|
+
disclosure: "internal"
|
|
5506
|
+
};
|
|
5507
|
+
const hash = sha2562(new TextEncoder().encode(JSON.stringify(anchorDeepSort(signed))));
|
|
5508
|
+
const digest = bytesToHex(hash);
|
|
5509
|
+
const signature = bytesToHex(ed25519.sign(hash, hexToBytes(key.privateKey)));
|
|
5510
|
+
return { ...signed, signature, digest };
|
|
5511
|
+
}
|
|
5512
|
+
async function anchorRecordCheckpoint(receipts, key, opts) {
|
|
5513
|
+
const checkpoint = buildRecordCheckpoint(receipts, key, opts.issuedAt);
|
|
5514
|
+
const base = (opts.log || DEFAULT_LOG).replace(/\/+$/, "");
|
|
5515
|
+
const out = await submitEnvelope(checkpoint, base, opts.fetchImpl);
|
|
5516
|
+
if (!out.ok) return { ok: false, record_root: checkpoint.record_root, total: checkpoint.total, checkpoint, error: out.error };
|
|
5517
|
+
return {
|
|
5518
|
+
ok: true,
|
|
5519
|
+
record_root: checkpoint.record_root,
|
|
5520
|
+
total: checkpoint.total,
|
|
5521
|
+
seq: out.seq,
|
|
5522
|
+
entry_url: `${base}/fn/log/${out.seq}`,
|
|
5523
|
+
anchored_at: out.anchored_at,
|
|
5524
|
+
already_anchored: out.already_anchored,
|
|
5525
|
+
checkpoint
|
|
5526
|
+
};
|
|
5527
|
+
}
|
|
5528
|
+
async function lookupPinnedIdentity(publicKey, opts) {
|
|
5529
|
+
const base = (opts && opts.log || DEFAULT_LOG).replace(/\/+$/, "");
|
|
5530
|
+
const doFetch = opts && opts.fetchImpl || globalThis.fetch;
|
|
5531
|
+
if (!doFetch || !/^[0-9a-f]{64}$/i.test(publicKey)) return null;
|
|
5532
|
+
try {
|
|
5533
|
+
const resp = await doFetch(`${base}/fn/log/keys/lookup/${publicKey.toLowerCase()}`, { headers: { accept: "application/json" } });
|
|
5534
|
+
const data = await resp.json().catch(() => null);
|
|
5535
|
+
if (!data || data.ok !== true) return null;
|
|
5536
|
+
if (!data.found) return { found: false };
|
|
5427
5537
|
return {
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
envelope
|
|
5538
|
+
found: true,
|
|
5539
|
+
name: data.name,
|
|
5540
|
+
slug: data.slug,
|
|
5541
|
+
kid: data.kid,
|
|
5542
|
+
enrolled_at: data.enrolled_at,
|
|
5543
|
+
revoked: !!(data.revoked || data.revoked_at)
|
|
5435
5544
|
};
|
|
5436
5545
|
} catch {
|
|
5437
|
-
return
|
|
5546
|
+
return null;
|
|
5438
5547
|
}
|
|
5439
5548
|
}
|
|
5440
|
-
var CLAIM_TYPE, ANCHOR_SCHEMA, DEFAULT_LOG;
|
|
5549
|
+
var CLAIM_TYPE, ANCHOR_SCHEMA, DEFAULT_LOG, CHECKPOINT_SCHEMA;
|
|
5441
5550
|
var init_claim = __esm({
|
|
5442
5551
|
"src/claim.ts"() {
|
|
5443
5552
|
"use strict";
|
|
@@ -5448,6 +5557,7 @@ var init_claim = __esm({
|
|
|
5448
5557
|
CLAIM_TYPE = "scopeblind.claim.v1";
|
|
5449
5558
|
ANCHOR_SCHEMA = "scopeblind.protect-mcp.anchor.v1";
|
|
5450
5559
|
DEFAULT_LOG = "https://scopeblind.com";
|
|
5560
|
+
CHECKPOINT_SCHEMA = "scopeblind.protect-mcp.record-checkpoint.v1";
|
|
5451
5561
|
}
|
|
5452
5562
|
});
|
|
5453
5563
|
|
|
@@ -6209,6 +6319,101 @@ var init_hook_patterns = __esm({
|
|
|
6209
6319
|
}
|
|
6210
6320
|
});
|
|
6211
6321
|
|
|
6322
|
+
// src/sample.ts
|
|
6323
|
+
var sample_exports = {};
|
|
6324
|
+
__export(sample_exports, {
|
|
6325
|
+
SAMPLE_KID: () => SAMPLE_KID,
|
|
6326
|
+
buildSampleKit: () => buildSampleKit
|
|
6327
|
+
});
|
|
6328
|
+
function signEnvelope(unsigned, privHex) {
|
|
6329
|
+
const msg = new TextEncoder().encode(canonicalJson(unsigned));
|
|
6330
|
+
const signature = bytesToHex(ed25519.sign(msg, hexToBytes(privHex)));
|
|
6331
|
+
return { ...unsigned, signature };
|
|
6332
|
+
}
|
|
6333
|
+
function buildSampleKit(dir, opts) {
|
|
6334
|
+
const receiptsPath = (0, import_node_path7.join)(dir, ".protect-mcp-receipts.jsonl");
|
|
6335
|
+
const keyPath = (0, import_node_path7.join)(dir, "keys", "gateway.json");
|
|
6336
|
+
if (!opts?.force && ((0, import_node_fs10.existsSync)(receiptsPath) || (0, import_node_fs10.existsSync)(keyPath))) {
|
|
6337
|
+
throw Object.assign(
|
|
6338
|
+
new Error(`refusing to overwrite an existing record or signing key in ${dir}`),
|
|
6339
|
+
{ code: "SAMPLE_EXISTS" }
|
|
6340
|
+
);
|
|
6341
|
+
}
|
|
6342
|
+
(0, import_node_fs10.mkdirSync)((0, import_node_path7.join)(dir, "keys"), { recursive: true });
|
|
6343
|
+
const priv = ed25519.utils.randomPrivateKey();
|
|
6344
|
+
const privHex = bytesToHex(priv);
|
|
6345
|
+
const pub = bytesToHex(ed25519.getPublicKey(priv));
|
|
6346
|
+
(0, import_node_fs10.writeFileSync)(keyPath, JSON.stringify({ privateKey: privHex, publicKey: pub, kid: SAMPLE_KID }, null, 2));
|
|
6347
|
+
(0, import_node_fs10.writeFileSync)((0, import_node_path7.join)(dir, "keys", ".gitignore"), "# Never commit signing keys\n*.json\n");
|
|
6348
|
+
const now = opts?.now ?? /* @__PURE__ */ new Date();
|
|
6349
|
+
const stamp = (i) => new Date(now.getTime() - (7 - i) * 5 * 6e4).toISOString();
|
|
6350
|
+
const receipt = (i, tool, decision, caps, extra) => {
|
|
6351
|
+
const ts = stamp(i);
|
|
6352
|
+
const payload = {
|
|
6353
|
+
tool,
|
|
6354
|
+
decision,
|
|
6355
|
+
reason_code: decision === "deny" ? "policy_deny" : "policy_ok",
|
|
6356
|
+
policy_digest: SAMPLE_KID,
|
|
6357
|
+
scope: `${tool}-${ts}`,
|
|
6358
|
+
mode: "enforce",
|
|
6359
|
+
request_id: `${tool}-${ts}`,
|
|
6360
|
+
spec: "draft-farley-acta-signed-receipts-01",
|
|
6361
|
+
issuer_certification: "self-signed",
|
|
6362
|
+
public_key: pub,
|
|
6363
|
+
hook_event: "PreToolUse",
|
|
6364
|
+
enrichment: { v: 2, input_digest: sha256Hex4(tool + ts), capabilities: caps, ...extra || {} }
|
|
6365
|
+
};
|
|
6366
|
+
return signEnvelope({
|
|
6367
|
+
v: 2,
|
|
6368
|
+
type: decision === "deny" ? "gateway_restraint" : "decision_receipt",
|
|
6369
|
+
algorithm: "ed25519",
|
|
6370
|
+
kid: SAMPLE_KID,
|
|
6371
|
+
issuer: "protect-mcp",
|
|
6372
|
+
issued_at: ts,
|
|
6373
|
+
payload
|
|
6374
|
+
}, privHex);
|
|
6375
|
+
};
|
|
6376
|
+
const pay = (amount) => ({
|
|
6377
|
+
payment: { amount, asset: "USDC", recipient_digest: sha256Hex4("sample-merchant"), scheme: "exact" }
|
|
6378
|
+
});
|
|
6379
|
+
const rows = [
|
|
6380
|
+
receipt(0, "Read", "allow", ["fs.read"]),
|
|
6381
|
+
receipt(1, "Bash", "allow", ["exec.shell"]),
|
|
6382
|
+
receipt(2, "Write", "allow", ["fs.write"]),
|
|
6383
|
+
receipt(3, "WebFetch", "deny", ["net.egress"]),
|
|
6384
|
+
receipt(4, "x402_pay", "allow", ["financial", "payment"], pay(0.02)),
|
|
6385
|
+
receipt(5, "Read", "allow", ["fs.read", "secret.adjacent"]),
|
|
6386
|
+
receipt(6, "wallet_send_payment", "allow", ["financial", "payment"], pay(12.5)),
|
|
6387
|
+
receipt(7, "Bash", "allow", ["exec.shell", "vcs"])
|
|
6388
|
+
];
|
|
6389
|
+
(0, import_node_fs10.writeFileSync)(receiptsPath, rows.map((r) => JSON.stringify(r)).join("\n") + "\n");
|
|
6390
|
+
const tampered = rows.map((r) => JSON.parse(JSON.stringify(r)));
|
|
6391
|
+
tampered[3].payload.decision = "allow";
|
|
6392
|
+
(0, import_node_fs10.writeFileSync)((0, import_node_path7.join)(dir, "demo-tampered.jsonl"), tampered.map((r) => JSON.stringify(r)).join("\n") + "\n");
|
|
6393
|
+
return {
|
|
6394
|
+
dir,
|
|
6395
|
+
publicKey: pub,
|
|
6396
|
+
kid: SAMPLE_KID,
|
|
6397
|
+
receipts: rows,
|
|
6398
|
+
paymentsUsd: [0.02, 12.5],
|
|
6399
|
+
files: [".protect-mcp-receipts.jsonl", "demo-tampered.jsonl", "keys/gateway.json"]
|
|
6400
|
+
};
|
|
6401
|
+
}
|
|
6402
|
+
var import_node_fs10, import_node_path7, SAMPLE_KID, sha256Hex4;
|
|
6403
|
+
var init_sample = __esm({
|
|
6404
|
+
"src/sample.ts"() {
|
|
6405
|
+
"use strict";
|
|
6406
|
+
import_node_fs10 = require("fs");
|
|
6407
|
+
import_node_path7 = require("path");
|
|
6408
|
+
init_sha256();
|
|
6409
|
+
init_utils();
|
|
6410
|
+
init_ed25519();
|
|
6411
|
+
init_receipt_enrichment();
|
|
6412
|
+
SAMPLE_KID = "sample-demo";
|
|
6413
|
+
sha256Hex4 = (s) => bytesToHex(sha2562(new TextEncoder().encode(s)));
|
|
6414
|
+
}
|
|
6415
|
+
});
|
|
6416
|
+
|
|
6212
6417
|
// src/scopeblind-bridge.ts
|
|
6213
6418
|
function getScopeBlindBridge() {
|
|
6214
6419
|
if (!singleton) singleton = new ScopeBlindBridge();
|
|
@@ -6417,7 +6622,7 @@ function detectSandboxState() {
|
|
|
6417
6622
|
}
|
|
6418
6623
|
if (process.platform === "linux") {
|
|
6419
6624
|
try {
|
|
6420
|
-
const procStatus = (0,
|
|
6625
|
+
const procStatus = (0, import_node_fs11.readFileSync)("/proc/self/status", "utf-8");
|
|
6421
6626
|
if (procStatus.includes("Seccomp: 2")) return "enabled";
|
|
6422
6627
|
} catch {
|
|
6423
6628
|
}
|
|
@@ -6862,14 +7067,14 @@ function emitDecisionLog(state, entry) {
|
|
|
6862
7067
|
process.stderr.write(`[PROTECT_MCP] ${JSON.stringify(log)}
|
|
6863
7068
|
`);
|
|
6864
7069
|
try {
|
|
6865
|
-
(0,
|
|
7070
|
+
(0, import_node_fs11.appendFileSync)(state.logFilePath, JSON.stringify(log) + "\n");
|
|
6866
7071
|
} catch {
|
|
6867
7072
|
}
|
|
6868
7073
|
if (isSigningEnabled()) {
|
|
6869
7074
|
const signed = signDecision(log);
|
|
6870
7075
|
if (signed.signed) {
|
|
6871
7076
|
try {
|
|
6872
|
-
(0,
|
|
7077
|
+
(0, import_node_fs11.appendFileSync)(state.receiptFilePath, signed.signed + "\n");
|
|
6873
7078
|
} catch {
|
|
6874
7079
|
}
|
|
6875
7080
|
state.receiptBuffer.add(log.request_id, signed.signed);
|
|
@@ -6893,7 +7098,7 @@ function emitDecisionLog(state, entry) {
|
|
|
6893
7098
|
at: new Date(log.timestamp).toISOString()
|
|
6894
7099
|
});
|
|
6895
7100
|
try {
|
|
6896
|
-
(0,
|
|
7101
|
+
(0, import_node_fs11.appendFileSync)(state.receiptFilePath, tombstone + "\n");
|
|
6897
7102
|
} catch {
|
|
6898
7103
|
}
|
|
6899
7104
|
process.stderr.write(`[PROTECT_MCP_SIGNING_FAILURE] ${tombstone}
|
|
@@ -6981,8 +7186,8 @@ async function startHookServer(options = {}) {
|
|
|
6981
7186
|
}
|
|
6982
7187
|
}
|
|
6983
7188
|
if (!jsonPolicy?.signing) {
|
|
6984
|
-
const keyPath = (0,
|
|
6985
|
-
if ((0,
|
|
7189
|
+
const keyPath = (0, import_node_path8.join)(process.cwd(), "keys", "gateway.json");
|
|
7190
|
+
if ((0, import_node_fs11.existsSync)(keyPath)) {
|
|
6986
7191
|
const warnings = await initSigning({ key_path: keyPath, issuer: "protect-mcp", enabled: true });
|
|
6987
7192
|
for (const w of warnings) {
|
|
6988
7193
|
process.stderr.write(`[PROTECT_MCP] Warning: ${w}
|
|
@@ -7004,8 +7209,8 @@ async function startHookServer(options = {}) {
|
|
|
7004
7209
|
verbose,
|
|
7005
7210
|
enforce,
|
|
7006
7211
|
policyDigest,
|
|
7007
|
-
logFilePath: (0,
|
|
7008
|
-
receiptFilePath: (0,
|
|
7212
|
+
logFilePath: (0, import_node_path8.join)(process.cwd(), LOG_FILE3),
|
|
7213
|
+
receiptFilePath: (0, import_node_path8.join)(process.cwd(), RECEIPTS_FILE2),
|
|
7009
7214
|
permissionSuggestions: /* @__PURE__ */ new Map(),
|
|
7010
7215
|
configAlerts: []
|
|
7011
7216
|
};
|
|
@@ -7154,7 +7359,7 @@ async function startHookServer(options = {}) {
|
|
|
7154
7359
|
`);
|
|
7155
7360
|
w(`
|
|
7156
7361
|
`);
|
|
7157
|
-
const hasSlug = process.env.SCOPEBLIND_SLUG || (0,
|
|
7362
|
+
const hasSlug = process.env.SCOPEBLIND_SLUG || (0, import_node_fs11.existsSync)((0, import_node_path8.join)(process.cwd(), ".scopeblind"));
|
|
7158
7363
|
if (!hasSlug) {
|
|
7159
7364
|
w(` Dashboard npx protect-mcp connect
|
|
7160
7365
|
`);
|
|
@@ -7185,8 +7390,8 @@ async function startHookServer(options = {}) {
|
|
|
7185
7390
|
function findCedarDir() {
|
|
7186
7391
|
for (const candidate of ["cedar", "policies", "."]) {
|
|
7187
7392
|
try {
|
|
7188
|
-
if ((0,
|
|
7189
|
-
const files = (0,
|
|
7393
|
+
if ((0, import_node_fs11.existsSync)(candidate)) {
|
|
7394
|
+
const files = (0, import_node_fs11.readdirSync)(candidate, { encoding: "utf-8" });
|
|
7190
7395
|
if (files.some((f) => f.endsWith(".cedar"))) {
|
|
7191
7396
|
return candidate;
|
|
7192
7397
|
}
|
|
@@ -7207,14 +7412,14 @@ function normalizeHookInput(raw) {
|
|
|
7207
7412
|
}
|
|
7208
7413
|
return result;
|
|
7209
7414
|
}
|
|
7210
|
-
var import_node_http2, import_node_crypto6,
|
|
7415
|
+
var import_node_http2, import_node_crypto6, import_node_fs11, import_node_path8, DEFAULT_PORT, LOG_FILE3, RECEIPTS_FILE2, PAYLOAD_HASH_THRESHOLD, SNAKE_TO_CAMEL_MAP;
|
|
7211
7416
|
var init_hook_server = __esm({
|
|
7212
7417
|
"src/hook-server.ts"() {
|
|
7213
7418
|
"use strict";
|
|
7214
7419
|
import_node_http2 = require("http");
|
|
7215
7420
|
import_node_crypto6 = require("crypto");
|
|
7216
|
-
|
|
7217
|
-
|
|
7421
|
+
import_node_fs11 = require("fs");
|
|
7422
|
+
import_node_path8 = require("path");
|
|
7218
7423
|
init_cedar_evaluator();
|
|
7219
7424
|
init_signing();
|
|
7220
7425
|
init_policy();
|
|
@@ -7440,8 +7645,8 @@ function generateReport(logPath, receiptPath, periodDays) {
|
|
|
7440
7645
|
const now = /* @__PURE__ */ new Date();
|
|
7441
7646
|
const from = new Date(now.getTime() - periodDays * 864e5);
|
|
7442
7647
|
const entries = [];
|
|
7443
|
-
if ((0,
|
|
7444
|
-
const raw = (0,
|
|
7648
|
+
if ((0, import_node_fs12.existsSync)(logPath)) {
|
|
7649
|
+
const raw = (0, import_node_fs12.readFileSync)(logPath, "utf-8");
|
|
7445
7650
|
for (const line of raw.split("\n")) {
|
|
7446
7651
|
const trimmed = line.trim();
|
|
7447
7652
|
if (!trimmed) continue;
|
|
@@ -7461,8 +7666,8 @@ function generateReport(logPath, receiptPath, periodDays) {
|
|
|
7461
7666
|
let receiptsSigned = 0;
|
|
7462
7667
|
let signerKid = "";
|
|
7463
7668
|
let signerIssuer = "";
|
|
7464
|
-
if ((0,
|
|
7465
|
-
const raw = (0,
|
|
7669
|
+
if ((0, import_node_fs12.existsSync)(receiptPath)) {
|
|
7670
|
+
const raw = (0, import_node_fs12.readFileSync)(receiptPath, "utf-8");
|
|
7466
7671
|
for (const line of raw.split("\n")) {
|
|
7467
7672
|
const trimmed = line.trim();
|
|
7468
7673
|
if (!trimmed) continue;
|
|
@@ -7594,11 +7799,11 @@ function formatReportMarkdown(report) {
|
|
|
7594
7799
|
lines.push("*Generated by protect-mcp \xB7 scopeblind.com*");
|
|
7595
7800
|
return lines.join("\n");
|
|
7596
7801
|
}
|
|
7597
|
-
var
|
|
7802
|
+
var import_node_fs12;
|
|
7598
7803
|
var init_report = __esm({
|
|
7599
7804
|
"src/report.ts"() {
|
|
7600
7805
|
"use strict";
|
|
7601
|
-
|
|
7806
|
+
import_node_fs12 = require("fs");
|
|
7602
7807
|
}
|
|
7603
7808
|
});
|
|
7604
7809
|
|
|
@@ -8642,8 +8847,8 @@ Next: run \`npx protect-mcp dashboard --open\` and review tool inventory, policy
|
|
|
8642
8847
|
|
|
8643
8848
|
// src/cli.ts
|
|
8644
8849
|
var import_node_crypto7 = require("crypto");
|
|
8645
|
-
var
|
|
8646
|
-
var
|
|
8850
|
+
var import_node_fs13 = require("fs");
|
|
8851
|
+
var import_node_path9 = require("path");
|
|
8647
8852
|
var import_node_os = require("os");
|
|
8648
8853
|
function printHelp() {
|
|
8649
8854
|
process.stderr.write(`
|
|
@@ -8665,14 +8870,16 @@ Usage:
|
|
|
8665
8870
|
protect-mcp policy-packs list|show|install [pack] [--dir ./cedar] [--force]
|
|
8666
8871
|
protect-mcp connect
|
|
8667
8872
|
protect-mcp init [--dir <path>]
|
|
8873
|
+
protect-mcp sample [--dir <path>] [--force]
|
|
8668
8874
|
protect-mcp demo
|
|
8669
8875
|
protect-mcp trace <receipt_id> [--endpoint <url>] [--depth <n>]
|
|
8670
8876
|
protect-mcp status [--dir <path>]
|
|
8671
8877
|
protect-mcp digest [--today] [--dir <path>]
|
|
8672
8878
|
protect-mcp receipts [--last <n>] [--dir <path>]
|
|
8673
8879
|
protect-mcp record [--dir <path>] [--live] [--no-open]
|
|
8674
|
-
protect-mcp claim [--no <cap>] [--only <c,c>] [--count <verdict>] [--dir <path>] [--output <path>]
|
|
8675
|
-
protect-mcp verify-claim <claim.json> [--key <public-hex>]
|
|
8880
|
+
protect-mcp claim [--no <cap>] [--only <c,c>] [--count <verdict>] [--payment-under <amount>] [--anchor] [--dir <path>] [--output <path>]
|
|
8881
|
+
protect-mcp verify-claim <claim.json> [--key <public-hex>] [--check-anchor] [--offline]
|
|
8882
|
+
protect-mcp anchor-record [--dir <path>] [--force]
|
|
8676
8883
|
protect-mcp bundle [--output <path>] [--dir <path>]
|
|
8677
8884
|
protect-mcp simulate --policy <path> [--log <path>] [--tier <tier>] [--json]
|
|
8678
8885
|
protect-mcp report [--period <days>d] [--format md|json] [--output <path>] [--dir <path>]
|
|
@@ -8686,6 +8893,7 @@ Options:
|
|
|
8686
8893
|
--port <port> HTTP server port (default: 3000 for --http, 9377 for serve)
|
|
8687
8894
|
--verbose Enable debug logging to stderr
|
|
8688
8895
|
--help Show this help
|
|
8896
|
+
--version Print the installed version
|
|
8689
8897
|
|
|
8690
8898
|
Commands:
|
|
8691
8899
|
serve Start HTTP hook server for Claude Code integration (port 9377)
|
|
@@ -8711,8 +8919,12 @@ Commands:
|
|
|
8711
8919
|
digest Generate a human-readable summary of agent activity
|
|
8712
8920
|
receipts Show recent persisted signed receipts
|
|
8713
8921
|
record Open a local, searchable view of your record in the browser
|
|
8714
|
-
claim Attest a signed, position-blind claim over your record (e.g. no egress
|
|
8715
|
-
|
|
8922
|
+
claim Attest a signed, position-blind claim over your record (e.g. no egress,
|
|
8923
|
+
no payment, every payment under a cap)
|
|
8924
|
+
verify-claim Verify a claim attestation offline (signature + predicate + commitment
|
|
8925
|
+
+ the anchor sidecar and issuer identity when present)
|
|
8926
|
+
anchor-record Checkpoint the record's Merkle root + count into the public log
|
|
8927
|
+
(heartbeat-friendly: skips when unchanged; only hashes leave)
|
|
8716
8928
|
bundle Export an offline-verifiable audit bundle
|
|
8717
8929
|
|
|
8718
8930
|
Examples:
|
|
@@ -8792,17 +9004,17 @@ function parseArgs(argv) {
|
|
|
8792
9004
|
return { policyPath, cedarDir, slug, enforce, verbose, childCommand };
|
|
8793
9005
|
}
|
|
8794
9006
|
async function handleInit(argv) {
|
|
8795
|
-
const { writeFileSync:
|
|
8796
|
-
const { join:
|
|
9007
|
+
const { writeFileSync: writeFileSync5, existsSync: existsSync10, mkdirSync: mkdirSync4 } = await import("fs");
|
|
9008
|
+
const { join: join9 } = await import("path");
|
|
8797
9009
|
let dir = process.cwd();
|
|
8798
9010
|
const dirIdx = argv.indexOf("--dir");
|
|
8799
9011
|
if (dirIdx !== -1 && argv[dirIdx + 1]) {
|
|
8800
9012
|
dir = argv[dirIdx + 1];
|
|
8801
9013
|
}
|
|
8802
|
-
const configPath =
|
|
8803
|
-
const keysDir =
|
|
8804
|
-
const keyPath =
|
|
8805
|
-
if (
|
|
9014
|
+
const configPath = join9(dir, "protect-mcp.json");
|
|
9015
|
+
const keysDir = join9(dir, "keys");
|
|
9016
|
+
const keyPath = join9(keysDir, "gateway.json");
|
|
9017
|
+
if (existsSync10(configPath)) {
|
|
8806
9018
|
process.stderr.write(`[PROTECT_MCP] Config already exists at ${configPath}
|
|
8807
9019
|
`);
|
|
8808
9020
|
process.stderr.write("[PROTECT_MCP] Delete it first if you want to regenerate.\n");
|
|
@@ -8821,19 +9033,19 @@ async function handleInit(argv) {
|
|
|
8821
9033
|
kid: "generated"
|
|
8822
9034
|
};
|
|
8823
9035
|
}
|
|
8824
|
-
if (!
|
|
8825
|
-
|
|
9036
|
+
if (!existsSync10(keysDir)) {
|
|
9037
|
+
mkdirSync4(keysDir, { recursive: true });
|
|
8826
9038
|
}
|
|
8827
|
-
|
|
9039
|
+
writeFileSync5(keyPath, JSON.stringify({
|
|
8828
9040
|
privateKey: keypair.privateKey,
|
|
8829
9041
|
publicKey: keypair.publicKey,
|
|
8830
9042
|
kid: keypair.kid,
|
|
8831
9043
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8832
9044
|
warning: "KEEP THIS FILE SECRET. Never commit to version control."
|
|
8833
9045
|
}, null, 2) + "\n");
|
|
8834
|
-
const gitignorePath =
|
|
8835
|
-
if (!
|
|
8836
|
-
|
|
9046
|
+
const gitignorePath = join9(keysDir, ".gitignore");
|
|
9047
|
+
if (!existsSync10(gitignorePath)) {
|
|
9048
|
+
writeFileSync5(gitignorePath, "# Never commit signing keys\n*.json\n");
|
|
8837
9049
|
}
|
|
8838
9050
|
const config = {
|
|
8839
9051
|
tools: {
|
|
@@ -8867,7 +9079,7 @@ async function handleInit(argv) {
|
|
|
8867
9079
|
}
|
|
8868
9080
|
}
|
|
8869
9081
|
};
|
|
8870
|
-
|
|
9082
|
+
writeFileSync5(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
8871
9083
|
const claudeConfig = {
|
|
8872
9084
|
"mcpServers": {
|
|
8873
9085
|
"my-server": {
|
|
@@ -8905,8 +9117,8 @@ Add --enforce when ready to block policy violations.
|
|
|
8905
9117
|
`);
|
|
8906
9118
|
}
|
|
8907
9119
|
async function handleDemo() {
|
|
8908
|
-
const { existsSync:
|
|
8909
|
-
const { join:
|
|
9120
|
+
const { existsSync: existsSync10 } = await import("fs");
|
|
9121
|
+
const { join: join9, dirname: dirname3, resolve } = await import("path");
|
|
8910
9122
|
const { realpathSync } = await import("fs");
|
|
8911
9123
|
const cliPath = resolve(process.argv[1] || "dist/cli.js");
|
|
8912
9124
|
let cliDir;
|
|
@@ -8915,9 +9127,9 @@ async function handleDemo() {
|
|
|
8915
9127
|
} catch {
|
|
8916
9128
|
cliDir = dirname3(cliPath);
|
|
8917
9129
|
}
|
|
8918
|
-
const demoServerPath =
|
|
8919
|
-
const configPath =
|
|
8920
|
-
const hasConfig =
|
|
9130
|
+
const demoServerPath = join9(cliDir, "demo-server.js");
|
|
9131
|
+
const configPath = join9(process.cwd(), "protect-mcp.json");
|
|
9132
|
+
const hasConfig = existsSync10(configPath);
|
|
8921
9133
|
if (!hasConfig) {
|
|
8922
9134
|
process.stderr.write(`
|
|
8923
9135
|
${bold("protect-mcp demo")}
|
|
@@ -8991,15 +9203,15 @@ Starting demo server with 5 tools...
|
|
|
8991
9203
|
await gateway.start();
|
|
8992
9204
|
}
|
|
8993
9205
|
async function handleStatus2(argv) {
|
|
8994
|
-
const { readFileSync: readFileSync11, existsSync:
|
|
8995
|
-
const { join:
|
|
9206
|
+
const { readFileSync: readFileSync11, existsSync: existsSync10 } = await import("fs");
|
|
9207
|
+
const { join: join9 } = await import("path");
|
|
8996
9208
|
let dir = process.cwd();
|
|
8997
9209
|
const dirIdx = argv.indexOf("--dir");
|
|
8998
9210
|
if (dirIdx !== -1 && argv[dirIdx + 1]) {
|
|
8999
9211
|
dir = argv[dirIdx + 1];
|
|
9000
9212
|
}
|
|
9001
|
-
const logPath =
|
|
9002
|
-
if (!
|
|
9213
|
+
const logPath = join9(dir, ".protect-mcp-log.jsonl");
|
|
9214
|
+
if (!existsSync10(logPath)) {
|
|
9003
9215
|
process.stderr.write(`${bold("protect-mcp status")}
|
|
9004
9216
|
|
|
9005
9217
|
`);
|
|
@@ -9088,8 +9300,8 @@ ${bold("protect-mcp status")}
|
|
|
9088
9300
|
process.stdout.write(` ${reason.padEnd(25)} ${count}
|
|
9089
9301
|
`);
|
|
9090
9302
|
}
|
|
9091
|
-
const evidencePath =
|
|
9092
|
-
if (
|
|
9303
|
+
const evidencePath = join9(dir, ".protect-mcp-evidence.json");
|
|
9304
|
+
if (existsSync10(evidencePath)) {
|
|
9093
9305
|
try {
|
|
9094
9306
|
const evidenceRaw = readFileSync11(evidencePath, "utf-8");
|
|
9095
9307
|
const evidence = JSON.parse(evidenceRaw);
|
|
@@ -9100,8 +9312,8 @@ ${bold("protect-mcp status")}
|
|
|
9100
9312
|
} catch {
|
|
9101
9313
|
}
|
|
9102
9314
|
}
|
|
9103
|
-
const keyPath =
|
|
9104
|
-
if (
|
|
9315
|
+
const keyPath = join9(dir, "keys", "gateway.json");
|
|
9316
|
+
if (existsSync10(keyPath)) {
|
|
9105
9317
|
try {
|
|
9106
9318
|
const keyData = JSON.parse(readFileSync11(keyPath, "utf-8"));
|
|
9107
9319
|
if (keyData.publicKey) {
|
|
@@ -9131,7 +9343,7 @@ function commandNeedsValue(argv, flag) {
|
|
|
9131
9343
|
return Boolean(value && !value.startsWith("--"));
|
|
9132
9344
|
}
|
|
9133
9345
|
function absoluteOrCwd(pathValue) {
|
|
9134
|
-
return (0,
|
|
9346
|
+
return (0, import_node_path9.resolve)(process.cwd(), pathValue);
|
|
9135
9347
|
}
|
|
9136
9348
|
function shellQuoteArg(arg) {
|
|
9137
9349
|
if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(arg)) return arg;
|
|
@@ -9150,18 +9362,18 @@ function wrapperArgsFor(command, opts) {
|
|
|
9150
9362
|
}
|
|
9151
9363
|
function claudeDesktopConfigPath() {
|
|
9152
9364
|
if (process.platform === "darwin") {
|
|
9153
|
-
return (0,
|
|
9365
|
+
return (0, import_node_path9.join)((0, import_node_os.homedir)(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
9154
9366
|
}
|
|
9155
9367
|
if (process.platform === "win32") {
|
|
9156
|
-
return (0,
|
|
9368
|
+
return (0, import_node_path9.join)(process.env.APPDATA || (0, import_node_path9.join)((0, import_node_os.homedir)(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
9157
9369
|
}
|
|
9158
|
-
return (0,
|
|
9370
|
+
return (0, import_node_path9.join)((0, import_node_os.homedir)(), ".config", "Claude", "claude_desktop_config.json");
|
|
9159
9371
|
}
|
|
9160
9372
|
async function ensureLocalConfig(dir = process.cwd()) {
|
|
9161
|
-
const { existsSync:
|
|
9162
|
-
const { join:
|
|
9163
|
-
const configPath =
|
|
9164
|
-
if (!
|
|
9373
|
+
const { existsSync: existsSync10 } = await import("fs");
|
|
9374
|
+
const { join: join9, resolve } = await import("path");
|
|
9375
|
+
const configPath = join9(dir, "protect-mcp.json");
|
|
9376
|
+
if (!existsSync10(configPath)) {
|
|
9165
9377
|
process.stderr.write(`${bold("protect-mcp wrap")}
|
|
9166
9378
|
|
|
9167
9379
|
No protect-mcp.json found; creating local shadow-mode config first.
|
|
@@ -9173,7 +9385,7 @@ No protect-mcp.json found; creating local shadow-mode config first.
|
|
|
9173
9385
|
}
|
|
9174
9386
|
function parseJsonlFile(pathValue) {
|
|
9175
9387
|
try {
|
|
9176
|
-
const raw = (0,
|
|
9388
|
+
const raw = (0, import_node_fs13.readFileSync)(pathValue, "utf-8");
|
|
9177
9389
|
return raw.split("\n").map((line) => line.trim()).filter(Boolean).flatMap((line) => {
|
|
9178
9390
|
try {
|
|
9179
9391
|
return [JSON.parse(line)];
|
|
@@ -9187,7 +9399,7 @@ function parseJsonlFile(pathValue) {
|
|
|
9187
9399
|
}
|
|
9188
9400
|
function parseJsonlRecords(pathValue) {
|
|
9189
9401
|
try {
|
|
9190
|
-
const raw = (0,
|
|
9402
|
+
const raw = (0, import_node_fs13.readFileSync)(pathValue, "utf-8");
|
|
9191
9403
|
return raw.split("\n").map((line) => line.trim()).filter(Boolean).flatMap((line) => {
|
|
9192
9404
|
try {
|
|
9193
9405
|
return [{
|
|
@@ -9205,8 +9417,8 @@ function parseJsonlRecords(pathValue) {
|
|
|
9205
9417
|
}
|
|
9206
9418
|
function loadPolicyJson(policyPath) {
|
|
9207
9419
|
try {
|
|
9208
|
-
if (!(0,
|
|
9209
|
-
return JSON.parse((0,
|
|
9420
|
+
if (!(0, import_node_fs13.existsSync)(policyPath)) return null;
|
|
9421
|
+
return JSON.parse((0, import_node_fs13.readFileSync)(policyPath, "utf-8"));
|
|
9210
9422
|
} catch {
|
|
9211
9423
|
return null;
|
|
9212
9424
|
}
|
|
@@ -9351,10 +9563,10 @@ function suggestedGuardrailFor(_tool, risk, reasons) {
|
|
|
9351
9563
|
policy: { rate_limit: "100/hour" }
|
|
9352
9564
|
};
|
|
9353
9565
|
}
|
|
9354
|
-
function buildDashboardSummary(dir, policyPath = (0,
|
|
9355
|
-
const logPath = (0,
|
|
9356
|
-
const receiptPath = (0,
|
|
9357
|
-
const keyPath = (0,
|
|
9566
|
+
function buildDashboardSummary(dir, policyPath = (0, import_node_path9.join)(dir, "protect-mcp.json")) {
|
|
9567
|
+
const logPath = (0, import_node_path9.join)(dir, ".protect-mcp-log.jsonl");
|
|
9568
|
+
const receiptPath = (0, import_node_path9.join)(dir, ".protect-mcp-receipts.jsonl");
|
|
9569
|
+
const keyPath = (0, import_node_path9.join)(dir, "keys", "gateway.json");
|
|
9358
9570
|
const entries = parseJsonlFile(logPath);
|
|
9359
9571
|
const receiptRecords = parseJsonlRecords(receiptPath);
|
|
9360
9572
|
const receipts = receiptRecords.map((record) => record.value);
|
|
@@ -9399,9 +9611,9 @@ function buildDashboardSummary(dir, policyPath = (0, import_node_path8.join)(dir
|
|
|
9399
9611
|
const pendingApprovals = entries.filter((e) => e.decision === "require_approval").slice(-25).reverse();
|
|
9400
9612
|
const chains = buildReceiptChains(entries, receiptRecords);
|
|
9401
9613
|
let key = null;
|
|
9402
|
-
if ((0,
|
|
9614
|
+
if ((0, import_node_fs13.existsSync)(keyPath)) {
|
|
9403
9615
|
try {
|
|
9404
|
-
const parsed = JSON.parse((0,
|
|
9616
|
+
const parsed = JSON.parse((0, import_node_fs13.readFileSync)(keyPath, "utf-8"));
|
|
9405
9617
|
key = {
|
|
9406
9618
|
kid: parsed.kid || null,
|
|
9407
9619
|
issuer: parsed.issuer || "protect-mcp",
|
|
@@ -9418,10 +9630,10 @@ function buildDashboardSummary(dir, policyPath = (0, import_node_path8.join)(dir
|
|
|
9418
9630
|
receipts: receiptPath,
|
|
9419
9631
|
key: keyPath,
|
|
9420
9632
|
policy: policyPath,
|
|
9421
|
-
log_exists: (0,
|
|
9422
|
-
receipts_exist: (0,
|
|
9423
|
-
key_exists: (0,
|
|
9424
|
-
policy_exists: (0,
|
|
9633
|
+
log_exists: (0, import_node_fs13.existsSync)(logPath),
|
|
9634
|
+
receipts_exist: (0, import_node_fs13.existsSync)(receiptPath),
|
|
9635
|
+
key_exists: (0, import_node_fs13.existsSync)(keyPath),
|
|
9636
|
+
policy_exists: (0, import_node_fs13.existsSync)(policyPath)
|
|
9425
9637
|
},
|
|
9426
9638
|
totals: {
|
|
9427
9639
|
decisions: entries.length,
|
|
@@ -9458,7 +9670,7 @@ function buildDashboardSummary(dir, policyPath = (0, import_node_path8.join)(dir
|
|
|
9458
9670
|
}))
|
|
9459
9671
|
},
|
|
9460
9672
|
connector_pilots: {
|
|
9461
|
-
directory: (0,
|
|
9673
|
+
directory: (0, import_node_path9.join)(dir, ".protect-mcp", "connectors"),
|
|
9462
9674
|
installed: readInstalledConnectorPilots(dir),
|
|
9463
9675
|
doctor: connectorDoctor(dir),
|
|
9464
9676
|
available: CONNECTOR_PILOTS.map((pilot) => ({
|
|
@@ -9836,7 +10048,7 @@ async function handleDashboard(argv) {
|
|
|
9836
10048
|
const { resolve } = await import("path");
|
|
9837
10049
|
const port = commandNeedsValue(argv, "--port") ? parseInt(flagValue(argv, "--port") || "9877", 10) : 9877;
|
|
9838
10050
|
const dir = resolve(commandNeedsValue(argv, "--dir") ? flagValue(argv, "--dir") || process.cwd() : process.cwd());
|
|
9839
|
-
const policyPath = resolve(flagValue(argv, "--policy") || (0,
|
|
10051
|
+
const policyPath = resolve(flagValue(argv, "--policy") || (0, import_node_path9.join)(dir, "protect-mcp.json"));
|
|
9840
10052
|
const approvalEndpoint = flagValue(argv, "--approval-endpoint");
|
|
9841
10053
|
const approvalNonce = flagValue(argv, "--approval-nonce");
|
|
9842
10054
|
const open = argv.includes("--open");
|
|
@@ -10041,32 +10253,32 @@ function writeToolPolicy(policyPath, tool, action) {
|
|
|
10041
10253
|
tools,
|
|
10042
10254
|
default_tier: existing.default_tier || "unknown"
|
|
10043
10255
|
};
|
|
10044
|
-
(0,
|
|
10256
|
+
(0, import_node_fs13.writeFileSync)(policyPath, JSON.stringify(next, null, 2) + "\n");
|
|
10045
10257
|
return next;
|
|
10046
10258
|
}
|
|
10047
10259
|
function policyPackDirectory(dir) {
|
|
10048
|
-
return (0,
|
|
10260
|
+
return (0, import_node_path9.join)(dir, "cedar");
|
|
10049
10261
|
}
|
|
10050
10262
|
function installedPolicyPackIds(dir) {
|
|
10051
10263
|
const cedarDir = policyPackDirectory(dir);
|
|
10052
10264
|
return POLICY_PACKS.filter(
|
|
10053
|
-
(pack) => pack.files.every((file) => (0,
|
|
10265
|
+
(pack) => pack.files.every((file) => (0, import_node_fs13.existsSync)((0, import_node_path9.join)(cedarDir, file.path)))
|
|
10054
10266
|
).map((pack) => pack.id);
|
|
10055
10267
|
}
|
|
10056
10268
|
function installPolicyPackToDir(dir, packId, force = false) {
|
|
10057
10269
|
const packs = packId === "all" ? POLICY_PACKS : [getPolicyPack(packId)].filter(Boolean);
|
|
10058
10270
|
if (packs.length === 0) throw new Error(`Unknown policy pack: ${packId}`);
|
|
10059
10271
|
const outDir = policyPackDirectory(dir);
|
|
10060
|
-
(0,
|
|
10272
|
+
(0, import_node_fs13.mkdirSync)(outDir, { recursive: true });
|
|
10061
10273
|
const written = [];
|
|
10062
10274
|
for (const pack of packs) {
|
|
10063
10275
|
for (const file of pack.files) {
|
|
10064
|
-
const outPath = (0,
|
|
10065
|
-
if ((0,
|
|
10276
|
+
const outPath = (0, import_node_path9.join)(outDir, file.path);
|
|
10277
|
+
if ((0, import_node_fs13.existsSync)(outPath) && !force) {
|
|
10066
10278
|
throw new Error(`Refusing to overwrite ${outPath}. Pass force=true if intentional.`);
|
|
10067
10279
|
}
|
|
10068
|
-
(0,
|
|
10069
|
-
(0,
|
|
10280
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path9.dirname)(outPath), { recursive: true });
|
|
10281
|
+
(0, import_node_fs13.writeFileSync)(outPath, file.contents.endsWith("\n") ? file.contents : `${file.contents}
|
|
10070
10282
|
`);
|
|
10071
10283
|
written.push(outPath);
|
|
10072
10284
|
}
|
|
@@ -10074,19 +10286,19 @@ function installPolicyPackToDir(dir, packId, force = false) {
|
|
|
10074
10286
|
return { dir: outDir, written, packs: packs.map((pack) => pack.id) };
|
|
10075
10287
|
}
|
|
10076
10288
|
function dashboardRegistryStatus(dir) {
|
|
10077
|
-
const identityPath = (0,
|
|
10078
|
-
const registryPath = (0,
|
|
10079
|
-
const verifierPath = (0,
|
|
10080
|
-
const identity = (0,
|
|
10289
|
+
const identityPath = (0, import_node_path9.join)(dir, ".protect-mcp-org.json");
|
|
10290
|
+
const registryPath = (0, import_node_path9.join)(dir, ".protect-mcp-registry.json");
|
|
10291
|
+
const verifierPath = (0, import_node_path9.join)(dir, "scopeblind-verifier.html");
|
|
10292
|
+
const identity = (0, import_node_fs13.existsSync)(identityPath) ? (() => {
|
|
10081
10293
|
try {
|
|
10082
|
-
return JSON.parse((0,
|
|
10294
|
+
return JSON.parse((0, import_node_fs13.readFileSync)(identityPath, "utf-8"));
|
|
10083
10295
|
} catch {
|
|
10084
10296
|
return null;
|
|
10085
10297
|
}
|
|
10086
10298
|
})() : null;
|
|
10087
|
-
const registry = (0,
|
|
10299
|
+
const registry = (0, import_node_fs13.existsSync)(registryPath) ? (() => {
|
|
10088
10300
|
try {
|
|
10089
|
-
return JSON.parse((0,
|
|
10301
|
+
return JSON.parse((0, import_node_fs13.readFileSync)(registryPath, "utf-8"));
|
|
10090
10302
|
} catch {
|
|
10091
10303
|
return null;
|
|
10092
10304
|
}
|
|
@@ -10094,9 +10306,9 @@ function dashboardRegistryStatus(dir) {
|
|
|
10094
10306
|
const anchors = Array.isArray(registry?.anchors) ? registry.anchors : [];
|
|
10095
10307
|
const hosted = anchors.some((anchor) => anchor.timestamp_source === "scopeblind-hosted");
|
|
10096
10308
|
return {
|
|
10097
|
-
identity_exists: (0,
|
|
10098
|
-
registry_exists: (0,
|
|
10099
|
-
verifier_exists: (0,
|
|
10309
|
+
identity_exists: (0, import_node_fs13.existsSync)(identityPath),
|
|
10310
|
+
registry_exists: (0, import_node_fs13.existsSync)(registryPath),
|
|
10311
|
+
verifier_exists: (0, import_node_fs13.existsSync)(verifierPath),
|
|
10100
10312
|
identity_path: identityPath,
|
|
10101
10313
|
registry_path: registryPath,
|
|
10102
10314
|
verifier_path: verifierPath,
|
|
@@ -10117,13 +10329,13 @@ async function readJsonBody(req) {
|
|
|
10117
10329
|
}
|
|
10118
10330
|
async function buildAuditBundleForDir(dir) {
|
|
10119
10331
|
const { createAuditBundle: createAuditBundle2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
|
|
10120
|
-
const receiptPath = (0,
|
|
10121
|
-
const keyPath = (0,
|
|
10122
|
-
if (!(0,
|
|
10123
|
-
if (!(0,
|
|
10332
|
+
const receiptPath = (0, import_node_path9.join)(dir, ".protect-mcp-receipts.jsonl");
|
|
10333
|
+
const keyPath = (0, import_node_path9.join)(dir, "keys", "gateway.json");
|
|
10334
|
+
if (!(0, import_node_fs13.existsSync)(receiptPath)) throw new Error("No receipt file found.");
|
|
10335
|
+
if (!(0, import_node_fs13.existsSync)(keyPath)) throw new Error("No signing key found.");
|
|
10124
10336
|
const receipts = parseJsonlFile(receiptPath);
|
|
10125
10337
|
if (receipts.length === 0) throw new Error("No signed receipts found.");
|
|
10126
|
-
const keyData = JSON.parse((0,
|
|
10338
|
+
const keyData = JSON.parse((0, import_node_fs13.readFileSync)(keyPath, "utf-8"));
|
|
10127
10339
|
return createAuditBundle2({
|
|
10128
10340
|
tenant: keyData.issuer || "protect-mcp",
|
|
10129
10341
|
receipts,
|
|
@@ -10141,17 +10353,17 @@ function collectSelectiveDisclosurePackages(dir) {
|
|
|
10141
10353
|
const out = [];
|
|
10142
10354
|
const seen = /* @__PURE__ */ new Set();
|
|
10143
10355
|
const candidates = [];
|
|
10144
|
-
const receiptsDir = (0,
|
|
10145
|
-
if ((0,
|
|
10146
|
-
for (const name of (0,
|
|
10356
|
+
const receiptsDir = (0, import_node_path9.join)(dir, "receipts");
|
|
10357
|
+
if ((0, import_node_fs13.existsSync)(receiptsDir)) {
|
|
10358
|
+
for (const name of (0, import_node_fs13.readdirSync)(receiptsDir)) {
|
|
10147
10359
|
if (name.includes("selective-disclosure") && name.endsWith(".json")) {
|
|
10148
|
-
candidates.push((0,
|
|
10360
|
+
candidates.push((0, import_node_path9.join)(receiptsDir, name));
|
|
10149
10361
|
}
|
|
10150
10362
|
}
|
|
10151
10363
|
}
|
|
10152
|
-
const jsonlPath = (0,
|
|
10153
|
-
if ((0,
|
|
10154
|
-
for (const line of (0,
|
|
10364
|
+
const jsonlPath = (0, import_node_path9.join)(dir, ".protect-mcp-selective-disclosures.jsonl");
|
|
10365
|
+
if ((0, import_node_fs13.existsSync)(jsonlPath)) {
|
|
10366
|
+
for (const line of (0, import_node_fs13.readFileSync)(jsonlPath, "utf-8").split("\n").map((s) => s.trim()).filter(Boolean)) {
|
|
10155
10367
|
try {
|
|
10156
10368
|
const parsed = JSON.parse(line);
|
|
10157
10369
|
addSelectiveDisclosure(out, seen, parsed);
|
|
@@ -10161,7 +10373,7 @@ function collectSelectiveDisclosurePackages(dir) {
|
|
|
10161
10373
|
}
|
|
10162
10374
|
for (const path of candidates) {
|
|
10163
10375
|
try {
|
|
10164
|
-
const parsed = JSON.parse((0,
|
|
10376
|
+
const parsed = JSON.parse((0, import_node_fs13.readFileSync)(path, "utf-8"));
|
|
10165
10377
|
addSelectiveDisclosure(out, seen, parsed);
|
|
10166
10378
|
} catch {
|
|
10167
10379
|
}
|
|
@@ -10194,7 +10406,7 @@ async function recordApprovalResolution(opts) {
|
|
|
10194
10406
|
takeover_note: opts.body.takeover_note || void 0,
|
|
10195
10407
|
payload_hash: opts.body.payload_hash || void 0
|
|
10196
10408
|
};
|
|
10197
|
-
(0,
|
|
10409
|
+
(0, import_node_fs13.appendFileSync)((0, import_node_path9.join)(opts.dir, ".protect-mcp-approval-resolutions.jsonl"), JSON.stringify(record) + "\n");
|
|
10198
10410
|
let forwarded = null;
|
|
10199
10411
|
if (resolution === "approve" && opts.approvalEndpoint && opts.approvalNonce) {
|
|
10200
10412
|
const endpoint = opts.approvalEndpoint.replace(/\/$/, "") + "/approve";
|
|
@@ -10217,7 +10429,7 @@ async function recordApprovalResolution(opts) {
|
|
|
10217
10429
|
return { recorded: true, resolution: record, forwarded };
|
|
10218
10430
|
}
|
|
10219
10431
|
async function handleRecommend(argv) {
|
|
10220
|
-
const { writeFileSync:
|
|
10432
|
+
const { writeFileSync: writeFileSync5 } = await import("fs");
|
|
10221
10433
|
const { resolve } = await import("path");
|
|
10222
10434
|
const dir = resolve(commandNeedsValue(argv, "--dir") ? flagValue(argv, "--dir") || process.cwd() : process.cwd());
|
|
10223
10435
|
const outputPath = resolve(flagValue(argv, "--output") || "protect-mcp.recommended.json");
|
|
@@ -10266,7 +10478,7 @@ Dry run only. Write the policy with:
|
|
|
10266
10478
|
process.stdout.write(dim(body));
|
|
10267
10479
|
return;
|
|
10268
10480
|
}
|
|
10269
|
-
|
|
10481
|
+
writeFileSync5(outputPath, body);
|
|
10270
10482
|
process.stdout.write(`
|
|
10271
10483
|
${green("\u2713 Wrote recommended policy")}
|
|
10272
10484
|
`);
|
|
@@ -10279,7 +10491,7 @@ ${green("\u2713 Wrote recommended policy")}
|
|
|
10279
10491
|
`);
|
|
10280
10492
|
}
|
|
10281
10493
|
async function handleWrap(argv) {
|
|
10282
|
-
const { existsSync:
|
|
10494
|
+
const { existsSync: existsSync10, readFileSync: readFileSync11, writeFileSync: writeFileSync5 } = await import("fs");
|
|
10283
10495
|
const { resolve } = await import("path");
|
|
10284
10496
|
const configFlag = flagValue(argv, "--config");
|
|
10285
10497
|
const cedarFlag = flagValue(argv, "--cedar");
|
|
@@ -10316,7 +10528,7 @@ ${bold("protect-mcp wrap")}
|
|
|
10316
10528
|
return;
|
|
10317
10529
|
}
|
|
10318
10530
|
const claudePath = resolve(flagValue(argv, "--path") || claudeDesktopConfigPath());
|
|
10319
|
-
if (!claudeDesktop && !
|
|
10531
|
+
if (!claudeDesktop && !existsSync10(claudePath)) {
|
|
10320
10532
|
process.stdout.write(`
|
|
10321
10533
|
${bold("protect-mcp wrap")}
|
|
10322
10534
|
|
|
@@ -10333,7 +10545,7 @@ ${bold("protect-mcp wrap")}
|
|
|
10333
10545
|
`);
|
|
10334
10546
|
return;
|
|
10335
10547
|
}
|
|
10336
|
-
if (!
|
|
10548
|
+
if (!existsSync10(claudePath)) {
|
|
10337
10549
|
process.stderr.write(`protect-mcp wrap: Claude Desktop config not found at ${claudePath}
|
|
10338
10550
|
`);
|
|
10339
10551
|
process.exit(1);
|
|
@@ -10404,8 +10616,8 @@ Dry run only. Apply with:
|
|
|
10404
10616
|
return;
|
|
10405
10617
|
}
|
|
10406
10618
|
const backupPath = `${claudePath}.bak.${Date.now()}`;
|
|
10407
|
-
|
|
10408
|
-
|
|
10619
|
+
writeFileSync5(backupPath, readFileSync11(claudePath, "utf-8"));
|
|
10620
|
+
writeFileSync5(claudePath, JSON.stringify(next, null, 2) + "\n");
|
|
10409
10621
|
process.stdout.write(`
|
|
10410
10622
|
${green("\u2713 Claude Desktop config updated")}
|
|
10411
10623
|
`);
|
|
@@ -10431,14 +10643,14 @@ function yellow(s) {
|
|
|
10431
10643
|
return process.env.NO_COLOR ? s : `\x1B[33m${s}\x1B[0m`;
|
|
10432
10644
|
}
|
|
10433
10645
|
async function handleDigest(argv) {
|
|
10434
|
-
const { readFileSync: readFileSync11, existsSync:
|
|
10435
|
-
const { join:
|
|
10646
|
+
const { readFileSync: readFileSync11, existsSync: existsSync10 } = await import("fs");
|
|
10647
|
+
const { join: join9 } = await import("path");
|
|
10436
10648
|
let dir = process.cwd();
|
|
10437
10649
|
const dirIdx = argv.indexOf("--dir");
|
|
10438
10650
|
if (dirIdx !== -1 && argv[dirIdx + 1]) dir = argv[dirIdx + 1];
|
|
10439
10651
|
const today = argv.includes("--today");
|
|
10440
|
-
const logPath =
|
|
10441
|
-
if (!
|
|
10652
|
+
const logPath = join9(dir, ".protect-mcp-log.jsonl");
|
|
10653
|
+
if (!existsSync10(logPath)) {
|
|
10442
10654
|
process.stderr.write(`${bold("protect-mcp digest")}
|
|
10443
10655
|
|
|
10444
10656
|
No log file found. Run protect-mcp first.
|
|
@@ -10522,15 +10734,15 @@ ${bold("\u{1F6E1}\uFE0F Agent Daily Digest")}
|
|
|
10522
10734
|
`);
|
|
10523
10735
|
}
|
|
10524
10736
|
async function handleReceipts2(argv) {
|
|
10525
|
-
const { readFileSync: readFileSync11, existsSync:
|
|
10526
|
-
const { join:
|
|
10737
|
+
const { readFileSync: readFileSync11, existsSync: existsSync10 } = await import("fs");
|
|
10738
|
+
const { join: join9 } = await import("path");
|
|
10527
10739
|
let dir = process.cwd();
|
|
10528
10740
|
const dirIdx = argv.indexOf("--dir");
|
|
10529
10741
|
if (dirIdx !== -1 && argv[dirIdx + 1]) dir = argv[dirIdx + 1];
|
|
10530
10742
|
const lastIdx = argv.indexOf("--last");
|
|
10531
10743
|
const count = lastIdx !== -1 && argv[lastIdx + 1] ? parseInt(argv[lastIdx + 1], 10) : 20;
|
|
10532
|
-
const receiptsPath =
|
|
10533
|
-
if (!
|
|
10744
|
+
const receiptsPath = join9(dir, ".protect-mcp-receipts.jsonl");
|
|
10745
|
+
if (!existsSync10(receiptsPath)) {
|
|
10534
10746
|
process.stderr.write(`${bold("protect-mcp receipts")}
|
|
10535
10747
|
|
|
10536
10748
|
No signed receipt file found. Run protect-mcp with signing enabled first.
|
|
@@ -10564,19 +10776,19 @@ async function pkgVersion() {
|
|
|
10564
10776
|
if (_pkgV) return _pkgV;
|
|
10565
10777
|
let v = "0.0.0";
|
|
10566
10778
|
try {
|
|
10567
|
-
const { readFileSync: readFileSync11, existsSync:
|
|
10568
|
-
const { dirname: dirname3, join:
|
|
10779
|
+
const { readFileSync: readFileSync11, existsSync: existsSync10, realpathSync } = await import("fs");
|
|
10780
|
+
const { dirname: dirname3, join: join9, resolve } = await import("path");
|
|
10569
10781
|
let base = "";
|
|
10570
10782
|
try {
|
|
10571
10783
|
base = dirname3(realpathSync(resolve(process.argv[1] || "")));
|
|
10572
10784
|
} catch {
|
|
10573
10785
|
}
|
|
10574
10786
|
const candidates = [
|
|
10575
|
-
base ?
|
|
10576
|
-
base ?
|
|
10787
|
+
base ? join9(base, "..", "package.json") : "",
|
|
10788
|
+
base ? join9(base, "package.json") : ""
|
|
10577
10789
|
].filter(Boolean);
|
|
10578
10790
|
for (const p of candidates) {
|
|
10579
|
-
if (
|
|
10791
|
+
if (existsSync10(p)) {
|
|
10580
10792
|
const parsed = JSON.parse(readFileSync11(p, "utf-8"));
|
|
10581
10793
|
if (parsed && parsed.name === "protect-mcp" && parsed.version) {
|
|
10582
10794
|
v = parsed.version;
|
|
@@ -10613,16 +10825,16 @@ function mapRecordEntry(e) {
|
|
|
10613
10825
|
return { ts, tool, verdict, reason, hook, signed, caps, agent, dur, id: String(e.request_id || p.request_id || ""), digest, raw: e };
|
|
10614
10826
|
}
|
|
10615
10827
|
async function handleRecord(argv) {
|
|
10616
|
-
const { readFileSync: readFileSync11, existsSync:
|
|
10617
|
-
const { join:
|
|
10828
|
+
const { readFileSync: readFileSync11, existsSync: existsSync10, writeFileSync: writeFileSync5 } = await import("fs");
|
|
10829
|
+
const { join: join9 } = await import("path");
|
|
10618
10830
|
const osMod = await import("os");
|
|
10619
10831
|
const cp = await import("child_process");
|
|
10620
10832
|
let dir = process.cwd();
|
|
10621
10833
|
const di = argv.indexOf("--dir");
|
|
10622
10834
|
if (di !== -1 && argv[di + 1]) dir = argv[di + 1];
|
|
10623
|
-
const recPath =
|
|
10624
|
-
const logPath =
|
|
10625
|
-
const pick = () =>
|
|
10835
|
+
const recPath = join9(dir, ".protect-mcp-receipts.jsonl");
|
|
10836
|
+
const logPath = join9(dir, ".protect-mcp-log.jsonl");
|
|
10837
|
+
const pick = () => existsSync10(recPath) ? recPath : existsSync10(logPath) ? logPath : null;
|
|
10626
10838
|
const chosen = pick();
|
|
10627
10839
|
if (!chosen) {
|
|
10628
10840
|
process.stderr.write(`
|
|
@@ -10647,7 +10859,7 @@ Start the gate with ${bold("npx protect-mcp serve")}, use your agent, then run t
|
|
|
10647
10859
|
let pinnedKey = "";
|
|
10648
10860
|
let pinnedKid = "";
|
|
10649
10861
|
try {
|
|
10650
|
-
const kd = JSON.parse(readFileSync11(
|
|
10862
|
+
const kd = JSON.parse(readFileSync11(join9(dir, "keys", "gateway.json"), "utf-8"));
|
|
10651
10863
|
if (kd && typeof kd.publicKey === "string" && /^[0-9a-f]{64}$/i.test(kd.publicKey)) {
|
|
10652
10864
|
pinnedKey = kd.publicKey;
|
|
10653
10865
|
pinnedKid = typeof kd.kid === "string" ? kd.kid : "";
|
|
@@ -10708,8 +10920,8 @@ ${bold("\u{1F6E1}\uFE0F Your record")} ${dim("\xB7")} live at ${url}
|
|
|
10708
10920
|
const recs = readRecs(chosen);
|
|
10709
10921
|
const meta = { file: chosen, signed: chosen === recPath, count: recs.length, live: false, pinned_key: pinnedKey, pinned_kid: pinnedKid };
|
|
10710
10922
|
const html = RECORD_HTML.replace("__DATA__", () => JSON.stringify(recs)).replace("__META__", () => JSON.stringify(meta));
|
|
10711
|
-
const out =
|
|
10712
|
-
|
|
10923
|
+
const out = join9(osMod.tmpdir(), "protect-mcp-record-" + Date.now() + ".html");
|
|
10924
|
+
writeFileSync5(out, html);
|
|
10713
10925
|
openTarget(out);
|
|
10714
10926
|
process.stdout.write(`
|
|
10715
10927
|
${bold("\u{1F6E1}\uFE0F Your record")} ${dim("\xB7")} ${recs.length} decision${recs.length === 1 ? "" : "s"}, all on this machine
|
|
@@ -10882,27 +11094,30 @@ render();kickVerify();
|
|
|
10882
11094
|
if(META.live){var poll=function(){fetch('/data',{cache:'no-store'}).then(function(r){return r.json()}).then(function(d){var nr=d.recs||[];var changed=nr.length!==RECORDS.length;RECORDS=nr;META.count=RECORDS.length;if(typeof d.signed==='boolean')META.signed=d.signed;if(changed){render();kickVerify()}}).catch(function(){})};poll();setInterval(poll,2000);}
|
|
10883
11095
|
</script></body></html>`;
|
|
10884
11096
|
async function handleClaim(argv) {
|
|
10885
|
-
const { readFileSync: readFileSync11, existsSync:
|
|
10886
|
-
const { join:
|
|
11097
|
+
const { readFileSync: readFileSync11, existsSync: existsSync10, writeFileSync: writeFileSync5 } = await import("fs");
|
|
11098
|
+
const { join: join9 } = await import("path");
|
|
10887
11099
|
const { buildClaim: buildClaim2 } = await Promise.resolve().then(() => (init_claim(), claim_exports));
|
|
10888
11100
|
let dir = process.cwd();
|
|
10889
11101
|
const di = argv.indexOf("--dir");
|
|
10890
11102
|
if (di !== -1 && argv[di + 1]) dir = argv[di + 1];
|
|
10891
11103
|
let predicate = null;
|
|
10892
|
-
const noIdx = argv.indexOf("--no"), onlyIdx = argv.indexOf("--only"), nvIdx = argv.indexOf("--no-verdict"), cvIdx = argv.indexOf("--count");
|
|
11104
|
+
const noIdx = argv.indexOf("--no"), onlyIdx = argv.indexOf("--only"), nvIdx = argv.indexOf("--no-verdict"), cvIdx = argv.indexOf("--count"), puIdx = argv.indexOf("--payment-under");
|
|
10893
11105
|
if (noIdx !== -1 && argv[noIdx + 1]) predicate = { kind: "no_capability", capability: argv[noIdx + 1] };
|
|
10894
11106
|
else if (onlyIdx !== -1 && argv[onlyIdx + 1]) predicate = { kind: "only_capabilities", capabilities: argv[onlyIdx + 1].split(",").map((s) => s.trim()).filter(Boolean) };
|
|
10895
11107
|
else if (nvIdx !== -1 && argv[nvIdx + 1]) predicate = { kind: "no_verdict", verdict: argv[nvIdx + 1] };
|
|
10896
11108
|
else if (cvIdx !== -1 && argv[cvIdx + 1]) predicate = { kind: "count_verdict", verdict: argv[cvIdx + 1] };
|
|
11109
|
+
else if (puIdx !== -1 && argv[puIdx + 1] && isFinite(parseFloat(argv[puIdx + 1]))) predicate = { kind: "payment_under", cap: parseFloat(argv[puIdx + 1]) };
|
|
10897
11110
|
if (!predicate) {
|
|
10898
11111
|
process.stderr.write(`
|
|
10899
11112
|
${bold("protect-mcp claim")}
|
|
10900
11113
|
|
|
10901
11114
|
Attest a signed, position-blind claim over your record:
|
|
10902
|
-
--no <capability> no action used it, e.g. ${dim("--no net.egress")}
|
|
11115
|
+
--no <capability> no action used it, e.g. ${dim("--no net.egress")} or ${dim("--no payment")}
|
|
10903
11116
|
--only <c1,c2,...> all actions confined to these capabilities
|
|
10904
11117
|
--no-verdict <verdict> e.g. ${dim("--no-verdict blocked")}
|
|
10905
11118
|
--count <verdict> how many, e.g. ${dim("--count blocked")}
|
|
11119
|
+
--payment-under <cap> every agent payment stayed under the cap (amounts the
|
|
11120
|
+
gate could not read count as OVER, so this cannot lie)
|
|
10906
11121
|
--anchor also record the claim digest in the public append-only
|
|
10907
11122
|
log so a counterparty can trust it is complete (only the
|
|
10908
11123
|
hash is sent; your record stays local)
|
|
@@ -10913,8 +11128,8 @@ Example: ${bold("npx protect-mcp claim --no net.egress --anchor")}
|
|
|
10913
11128
|
process.exit(0);
|
|
10914
11129
|
return;
|
|
10915
11130
|
}
|
|
10916
|
-
const keyPath =
|
|
10917
|
-
if (!
|
|
11131
|
+
const keyPath = join9(dir, "keys", "gateway.json");
|
|
11132
|
+
if (!existsSync10(keyPath)) {
|
|
10918
11133
|
process.stderr.write(`
|
|
10919
11134
|
${bold("protect-mcp claim")}
|
|
10920
11135
|
|
|
@@ -10943,8 +11158,8 @@ protect-mcp claim: ${keyPath} is missing privateKey/publicKey.
|
|
|
10943
11158
|
process.exit(1);
|
|
10944
11159
|
return;
|
|
10945
11160
|
}
|
|
10946
|
-
const recPath =
|
|
10947
|
-
if (!
|
|
11161
|
+
const recPath = join9(dir, ".protect-mcp-receipts.jsonl");
|
|
11162
|
+
if (!existsSync10(recPath)) {
|
|
10948
11163
|
process.stderr.write(`
|
|
10949
11164
|
${bold("protect-mcp claim")}
|
|
10950
11165
|
|
|
@@ -10971,8 +11186,8 @@ protect-mcp claim: no readable receipts in ${recPath}.
|
|
|
10971
11186
|
}
|
|
10972
11187
|
const pack = buildClaim2(receipts, predicate, { privateKey: key.privateKey, publicKey: key.publicKey, kid: key.kid || "gateway", issuer: "protect-mcp" }, (/* @__PURE__ */ new Date()).toISOString());
|
|
10973
11188
|
const oi = argv.indexOf("--output");
|
|
10974
|
-
const out = oi !== -1 && argv[oi + 1] ? argv[oi + 1] :
|
|
10975
|
-
|
|
11189
|
+
const out = oi !== -1 && argv[oi + 1] ? argv[oi + 1] : join9(dir, "claim-" + Date.now() + ".json");
|
|
11190
|
+
writeFileSync5(out, JSON.stringify(pack, null, 2) + "\n");
|
|
10976
11191
|
process.stdout.write(`
|
|
10977
11192
|
${bold("\u{1F6E1}\uFE0F Signed claim")}
|
|
10978
11193
|
`);
|
|
@@ -10998,15 +11213,25 @@ ${bold("\u{1F6E1}\uFE0F Signed claim")}
|
|
|
10998
11213
|
);
|
|
10999
11214
|
if (res.ok) {
|
|
11000
11215
|
const sidecar = out.replace(/\.json$/, "") + ".anchor.json";
|
|
11001
|
-
|
|
11216
|
+
writeFileSync5(sidecar, JSON.stringify({ log: logBase || "https://scopeblind.com", seq: res.seq, entry_url: res.entry_url, anchored_at: res.anchored_at, claim_digest: res.claim_digest, envelope: res.envelope }, null, 2) + "\n");
|
|
11002
11217
|
process.stdout.write(` ${green("Anchored")} as log entry ${bold("#" + res.seq)}${res.already_anchored ? dim(" (already present)") : ""} ${dim(res.entry_url || "")}
|
|
11003
11218
|
`);
|
|
11004
11219
|
process.stdout.write(` ${dim("A counterparty can now confirm this exact claim existed at " + (res.anchored_at || "this time") + " and cannot be quietly re-cut.")}
|
|
11005
11220
|
`);
|
|
11006
11221
|
process.stdout.write(` ${dim("Anchor record written to " + sidecar + ". Only the digest was sent; your record stayed local.")}
|
|
11007
11222
|
`);
|
|
11008
|
-
|
|
11223
|
+
const { lookupPinnedIdentity: lookupPinnedIdentity2 } = await Promise.resolve().then(() => (init_claim(), claim_exports));
|
|
11224
|
+
const who = await lookupPinnedIdentity2(key.publicKey, { log: logBase });
|
|
11225
|
+
if (who && who.found && !who.revoked) {
|
|
11226
|
+
process.stdout.write(` ${green("Identity:")} anchored as ${bold(who.name || who.slug || "enrolled org")} ${dim("(key pinned in the ScopeBlind directory" + (who.enrolled_at ? ", enrolled " + who.enrolled_at.slice(0, 10) : "") + ")")}
|
|
11227
|
+
`);
|
|
11228
|
+
} else if (who && who.found && who.revoked) {
|
|
11229
|
+
process.stdout.write(` ${red("Identity: this key is REVOKED in the ScopeBlind directory.")}
|
|
11230
|
+
`);
|
|
11231
|
+
} else {
|
|
11232
|
+
process.stdout.write(` ${dim("Identity: anonymous (key not enrolled). To anchor as a named org a counterparty can pin, see")} ${bold("scopeblind.com/enroll")}
|
|
11009
11233
|
`);
|
|
11234
|
+
}
|
|
11010
11235
|
} else {
|
|
11011
11236
|
process.stdout.write(` ${yellow("Anchor skipped")} ${dim("(" + (res.error || "unavailable") + "). The claim above is complete and verifiable offline without it.")}
|
|
11012
11237
|
`);
|
|
@@ -11016,11 +11241,137 @@ ${bold("\u{1F6E1}\uFE0F Signed claim")}
|
|
|
11016
11241
|
`);
|
|
11017
11242
|
process.exit(0);
|
|
11018
11243
|
}
|
|
11244
|
+
async function handleAnchorRecord(argv) {
|
|
11245
|
+
const { readFileSync: readFileSync11, existsSync: existsSync10, appendFileSync: appendFileSync3 } = await import("fs");
|
|
11246
|
+
const { join: join9 } = await import("path");
|
|
11247
|
+
const { anchorRecordCheckpoint: anchorRecordCheckpoint2, buildRecordCheckpoint: buildRecordCheckpoint2, lookupPinnedIdentity: lookupPinnedIdentity2 } = await Promise.resolve().then(() => (init_claim(), claim_exports));
|
|
11248
|
+
let dir = process.cwd();
|
|
11249
|
+
const di = argv.indexOf("--dir");
|
|
11250
|
+
if (di !== -1 && argv[di + 1]) dir = argv[di + 1];
|
|
11251
|
+
const li = argv.indexOf("--log");
|
|
11252
|
+
const logBase = li !== -1 && argv[li + 1] ? argv[li + 1] : void 0;
|
|
11253
|
+
const keyPath = join9(dir, "keys", "gateway.json");
|
|
11254
|
+
if (!existsSync10(keyPath)) {
|
|
11255
|
+
process.stderr.write(`
|
|
11256
|
+
${bold("protect-mcp anchor-record")}
|
|
11257
|
+
|
|
11258
|
+
No signing key at ${keyPath}. A checkpoint must be signed. Run ${bold("npx protect-mcp init")} first.
|
|
11259
|
+
|
|
11260
|
+
`);
|
|
11261
|
+
process.exit(1);
|
|
11262
|
+
return;
|
|
11263
|
+
}
|
|
11264
|
+
let key;
|
|
11265
|
+
try {
|
|
11266
|
+
key = JSON.parse(readFileSync11(keyPath, "utf-8"));
|
|
11267
|
+
} catch {
|
|
11268
|
+
process.stderr.write(`
|
|
11269
|
+
protect-mcp anchor-record: ${keyPath} is not valid JSON.
|
|
11270
|
+
|
|
11271
|
+
`);
|
|
11272
|
+
process.exit(1);
|
|
11273
|
+
return;
|
|
11274
|
+
}
|
|
11275
|
+
if (!key.privateKey || !key.publicKey) {
|
|
11276
|
+
process.stderr.write(`
|
|
11277
|
+
protect-mcp anchor-record: ${keyPath} is missing privateKey/publicKey.
|
|
11278
|
+
|
|
11279
|
+
`);
|
|
11280
|
+
process.exit(1);
|
|
11281
|
+
return;
|
|
11282
|
+
}
|
|
11283
|
+
const recPath = join9(dir, ".protect-mcp-receipts.jsonl");
|
|
11284
|
+
if (!existsSync10(recPath)) {
|
|
11285
|
+
process.stderr.write(`
|
|
11286
|
+
${bold("protect-mcp anchor-record")}
|
|
11287
|
+
|
|
11288
|
+
No signed receipts in ${dir}. Run the gate with signing on, then try again.
|
|
11289
|
+
|
|
11290
|
+
`);
|
|
11291
|
+
process.exit(0);
|
|
11292
|
+
return;
|
|
11293
|
+
}
|
|
11294
|
+
const receipts = readFileSync11(recPath, "utf-8").split(/\r?\n/).map((l) => l.trim()).filter(Boolean).map((l) => {
|
|
11295
|
+
try {
|
|
11296
|
+
return JSON.parse(l);
|
|
11297
|
+
} catch {
|
|
11298
|
+
return null;
|
|
11299
|
+
}
|
|
11300
|
+
}).filter((x) => x !== null);
|
|
11301
|
+
if (!receipts.length) {
|
|
11302
|
+
process.stderr.write(`
|
|
11303
|
+
protect-mcp anchor-record: no readable receipts in ${recPath}.
|
|
11304
|
+
|
|
11305
|
+
`);
|
|
11306
|
+
process.exit(0);
|
|
11307
|
+
return;
|
|
11308
|
+
}
|
|
11309
|
+
const claimKey = { privateKey: key.privateKey, publicKey: key.publicKey, kid: key.kid || "gateway", issuer: "protect-mcp" };
|
|
11310
|
+
const historyPath = join9(dir, ".protect-mcp-anchors.jsonl");
|
|
11311
|
+
const preview = buildRecordCheckpoint2(receipts, claimKey, "preview");
|
|
11312
|
+
if (!argv.includes("--force") && existsSync10(historyPath)) {
|
|
11313
|
+
const lines = readFileSync11(historyPath, "utf-8").split(/\r?\n/).filter(Boolean);
|
|
11314
|
+
const last = lines.length ? (() => {
|
|
11315
|
+
try {
|
|
11316
|
+
return JSON.parse(lines[lines.length - 1]);
|
|
11317
|
+
} catch {
|
|
11318
|
+
return null;
|
|
11319
|
+
}
|
|
11320
|
+
})() : null;
|
|
11321
|
+
if (last && last.record_root === preview.record_root && last.total === preview.total) {
|
|
11322
|
+
process.stdout.write(`
|
|
11323
|
+
${bold("\u{1F6E1}\uFE0F Record checkpoint")}
|
|
11324
|
+
`);
|
|
11325
|
+
process.stdout.write(` Unchanged since entry ${bold("#" + last.seq)} ${dim("(" + last.total + " receipts, anchored " + (last.anchored_at || "") + ")")}. Nothing new to anchor.
|
|
11326
|
+
`);
|
|
11327
|
+
process.stdout.write(` ${dim("Use --force to re-anchor anyway.")}
|
|
11328
|
+
|
|
11329
|
+
`);
|
|
11330
|
+
process.exit(0);
|
|
11331
|
+
return;
|
|
11332
|
+
}
|
|
11333
|
+
}
|
|
11334
|
+
const res = await anchorRecordCheckpoint2(receipts, claimKey, { log: logBase, issuedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
11335
|
+
process.stdout.write(`
|
|
11336
|
+
${bold("\u{1F6E1}\uFE0F Record checkpoint")}
|
|
11337
|
+
`);
|
|
11338
|
+
process.stdout.write(` ${res.total} receipts ${dim("\xB7")} root ${dim(res.record_root.slice(0, 16) + "\u2026")} ${dim("(" + res.checkpoint.from.slice(0, 10) + " \u2192 " + res.checkpoint.to.slice(0, 10) + ")")}
|
|
11339
|
+
`);
|
|
11340
|
+
if (!res.ok) {
|
|
11341
|
+
process.stdout.write(` ${yellow("Anchor failed")} ${dim("(" + (res.error || "unavailable") + "). Nothing was recorded; try again.")}
|
|
11342
|
+
|
|
11343
|
+
`);
|
|
11344
|
+
process.exit(1);
|
|
11345
|
+
return;
|
|
11346
|
+
}
|
|
11347
|
+
appendFileSync3(historyPath, JSON.stringify({ schema: res.checkpoint.schema, seq: res.seq, anchored_at: res.anchored_at, total: res.total, record_root: res.record_root, entry_url: res.entry_url, digest: res.checkpoint.digest }) + "\n");
|
|
11348
|
+
process.stdout.write(` ${green("Anchored")} as log entry ${bold("#" + res.seq)} ${dim(res.entry_url || "")}
|
|
11349
|
+
`);
|
|
11350
|
+
process.stdout.write(` ${dim("Only the root, count, and time range were sent. History: " + historyPath)}
|
|
11351
|
+
`);
|
|
11352
|
+
const who = await lookupPinnedIdentity2(claimKey.publicKey, { log: logBase });
|
|
11353
|
+
if (who && who.found && !who.revoked) {
|
|
11354
|
+
process.stdout.write(` ${green("Identity:")} anchored as ${bold(who.name || who.slug || "enrolled org")} ${dim("(key pinned in the ScopeBlind directory)")}
|
|
11355
|
+
`);
|
|
11356
|
+
} else if (who && who.found && who.revoked) {
|
|
11357
|
+
process.stdout.write(` ${red("Identity: this key is REVOKED in the ScopeBlind directory.")}
|
|
11358
|
+
`);
|
|
11359
|
+
} else {
|
|
11360
|
+
process.stdout.write(` ${dim("Identity: anonymous (key not enrolled). Named identity: scopeblind.com/enroll")}
|
|
11361
|
+
`);
|
|
11362
|
+
}
|
|
11363
|
+
process.stdout.write(` ${dim("A claim whose commitment matches this root is provably over the complete record as of")}
|
|
11364
|
+
`);
|
|
11365
|
+
process.stdout.write(` ${dim("this checkpoint. Run this on a heartbeat (e.g. cron) to keep the anchored history growing.")}
|
|
11366
|
+
|
|
11367
|
+
`);
|
|
11368
|
+
process.exit(0);
|
|
11369
|
+
}
|
|
11019
11370
|
async function handleVerifyClaim(argv) {
|
|
11020
|
-
const { readFileSync: readFileSync11, existsSync:
|
|
11371
|
+
const { readFileSync: readFileSync11, existsSync: existsSync10 } = await import("fs");
|
|
11021
11372
|
const { verifyClaim: verifyClaim2 } = await Promise.resolve().then(() => (init_claim(), claim_exports));
|
|
11022
11373
|
const file = argv.find((a) => !a.startsWith("--"));
|
|
11023
|
-
if (!file || !
|
|
11374
|
+
if (!file || !existsSync10(file)) {
|
|
11024
11375
|
process.stderr.write(`
|
|
11025
11376
|
${bold("protect-mcp verify-claim")} <claim.json> [--key <public-hex>]
|
|
11026
11377
|
|
|
@@ -11069,7 +11420,7 @@ ${bold("protect-mcp verify-claim")}
|
|
|
11069
11420
|
const sidecarPath = ai !== -1 && argv[ai + 1] ? argv[ai + 1] : file.replace(/\.json$/, "") + ".anchor.json";
|
|
11070
11421
|
const requireAnchor = argv.includes("--check-anchor");
|
|
11071
11422
|
let anchorOk = true;
|
|
11072
|
-
if (
|
|
11423
|
+
if (existsSync10(sidecarPath)) {
|
|
11073
11424
|
const { checkClaimAnchor: checkClaimAnchor2 } = await Promise.resolve().then(() => (init_claim(), claim_exports));
|
|
11074
11425
|
let sidecar = null;
|
|
11075
11426
|
try {
|
|
@@ -11102,6 +11453,21 @@ ${bold("protect-mcp verify-claim")}
|
|
|
11102
11453
|
process.stdout.write(` ${yellow("~")} log not checked ${dim(argv.includes("--offline") ? "(--offline)" : "(unreachable; local binding checks stand)")}
|
|
11103
11454
|
`);
|
|
11104
11455
|
}
|
|
11456
|
+
if (!argv.includes("--offline") && sidecar.envelope) {
|
|
11457
|
+
const { lookupPinnedIdentity: lookupPinnedIdentity2 } = await Promise.resolve().then(() => (init_claim(), claim_exports));
|
|
11458
|
+
const who = await lookupPinnedIdentity2(sidecar.envelope.verification_key, {});
|
|
11459
|
+
if (who && who.found && !who.revoked) {
|
|
11460
|
+
process.stdout.write(` ${green("\u2713")} issuer key pinned to ${bold(who.name || who.slug || "an enrolled org")} ${dim("(ScopeBlind key directory)")}
|
|
11461
|
+
`);
|
|
11462
|
+
} else if (who && who.found && who.revoked) {
|
|
11463
|
+
anchorOk = false;
|
|
11464
|
+
process.stdout.write(` ${red("\u2717")} issuer key is REVOKED in the ScopeBlind key directory
|
|
11465
|
+
`);
|
|
11466
|
+
} else if (who && !who.found) {
|
|
11467
|
+
process.stdout.write(` ${dim("issuer key not enrolled (anonymous issuer); named identities pin via scopeblind.com/enroll")}
|
|
11468
|
+
`);
|
|
11469
|
+
}
|
|
11470
|
+
}
|
|
11105
11471
|
}
|
|
11106
11472
|
} else if (requireAnchor) {
|
|
11107
11473
|
anchorOk = false;
|
|
@@ -11125,24 +11491,24 @@ ${bold("protect-mcp verify-claim")}
|
|
|
11125
11491
|
process.exit(finalValid ? 0 : 1);
|
|
11126
11492
|
}
|
|
11127
11493
|
async function handleBundle(argv) {
|
|
11128
|
-
const { readFileSync: readFileSync11, writeFileSync:
|
|
11129
|
-
const { join:
|
|
11494
|
+
const { readFileSync: readFileSync11, writeFileSync: writeFileSync5, existsSync: existsSync10 } = await import("fs");
|
|
11495
|
+
const { join: join9 } = await import("path");
|
|
11130
11496
|
const { createAuditBundle: createAuditBundle2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
|
|
11131
11497
|
let dir = process.cwd();
|
|
11132
11498
|
const dirIdx = argv.indexOf("--dir");
|
|
11133
11499
|
if (dirIdx !== -1 && argv[dirIdx + 1]) dir = argv[dirIdx + 1];
|
|
11134
11500
|
const outputIdx = argv.indexOf("--output");
|
|
11135
|
-
const outputPath = outputIdx !== -1 && argv[outputIdx + 1] ? argv[outputIdx + 1] :
|
|
11136
|
-
const receiptsPath =
|
|
11137
|
-
const keyPath =
|
|
11138
|
-
if (!
|
|
11501
|
+
const outputPath = outputIdx !== -1 && argv[outputIdx + 1] ? argv[outputIdx + 1] : join9(dir, "audit-bundle.json");
|
|
11502
|
+
const receiptsPath = join9(dir, ".protect-mcp-receipts.jsonl");
|
|
11503
|
+
const keyPath = join9(dir, "keys", "gateway.json");
|
|
11504
|
+
if (!existsSync10(receiptsPath)) {
|
|
11139
11505
|
process.stderr.write(`${bold("protect-mcp bundle")}
|
|
11140
11506
|
|
|
11141
11507
|
No signed receipt file found. Run protect-mcp with signing enabled first.
|
|
11142
11508
|
`);
|
|
11143
11509
|
process.exit(0);
|
|
11144
11510
|
}
|
|
11145
|
-
if (!
|
|
11511
|
+
if (!existsSync10(keyPath)) {
|
|
11146
11512
|
process.stderr.write(`${bold("protect-mcp bundle")}
|
|
11147
11513
|
|
|
11148
11514
|
No key file found at ${keyPath}
|
|
@@ -11163,7 +11529,7 @@ No key file found at ${keyPath}
|
|
|
11163
11529
|
use: "sig"
|
|
11164
11530
|
}]
|
|
11165
11531
|
});
|
|
11166
|
-
|
|
11532
|
+
writeFileSync5(outputPath, JSON.stringify(bundle, null, 2) + "\n");
|
|
11167
11533
|
process.stdout.write(`
|
|
11168
11534
|
${bold("protect-mcp bundle")}
|
|
11169
11535
|
|
|
@@ -11179,8 +11545,8 @@ ${bold("protect-mcp bundle")}
|
|
|
11179
11545
|
`);
|
|
11180
11546
|
}
|
|
11181
11547
|
async function createSandbox() {
|
|
11182
|
-
const { mkdirSync:
|
|
11183
|
-
const { join:
|
|
11548
|
+
const { mkdirSync: mkdirSync4, writeFileSync: writeFileSync5, existsSync: existsSync10, readFileSync: readFileSync11 } = await import("fs");
|
|
11549
|
+
const { join: join9 } = await import("path");
|
|
11184
11550
|
const { homedir } = await import("os");
|
|
11185
11551
|
let response;
|
|
11186
11552
|
try {
|
|
@@ -11210,19 +11576,19 @@ async function createSandbox() {
|
|
|
11210
11576
|
return null;
|
|
11211
11577
|
}
|
|
11212
11578
|
const dashboardUrl = `https://scopeblind.com/t/${data.slug}`;
|
|
11213
|
-
const configDir =
|
|
11214
|
-
if (!
|
|
11215
|
-
|
|
11579
|
+
const configDir = join9(homedir(), ".protect-mcp");
|
|
11580
|
+
if (!existsSync10(configDir)) {
|
|
11581
|
+
mkdirSync4(configDir, { recursive: true });
|
|
11216
11582
|
}
|
|
11217
|
-
const configPath =
|
|
11583
|
+
const configPath = join9(configDir, "config.json");
|
|
11218
11584
|
let existing = {};
|
|
11219
|
-
if (
|
|
11585
|
+
if (existsSync10(configPath)) {
|
|
11220
11586
|
try {
|
|
11221
11587
|
existing = JSON.parse(readFileSync11(configPath, "utf-8"));
|
|
11222
11588
|
} catch {
|
|
11223
11589
|
}
|
|
11224
11590
|
}
|
|
11225
|
-
|
|
11591
|
+
writeFileSync5(configPath, JSON.stringify({
|
|
11226
11592
|
...existing,
|
|
11227
11593
|
sandbox_slug: data.slug,
|
|
11228
11594
|
dashboard_url: dashboardUrl
|
|
@@ -11255,10 +11621,10 @@ ${"\u2500".repeat(50)}
|
|
|
11255
11621
|
}
|
|
11256
11622
|
async function handleQuickstart(argv) {
|
|
11257
11623
|
const connectFlag = argv.includes("--connect");
|
|
11258
|
-
const { mkdtempSync, writeFileSync:
|
|
11259
|
-
const { join:
|
|
11624
|
+
const { mkdtempSync, writeFileSync: writeFileSync5, existsSync: existsSync10, mkdirSync: mkdirSync4, readFileSync: readFileSync11 } = await import("fs");
|
|
11625
|
+
const { join: join9 } = await import("path");
|
|
11260
11626
|
const { tmpdir } = await import("os");
|
|
11261
|
-
const dir = mkdtempSync(
|
|
11627
|
+
const dir = mkdtempSync(join9(tmpdir(), "protect-mcp-quickstart-"));
|
|
11262
11628
|
process.stdout.write(`
|
|
11263
11629
|
${bold("protect-mcp quickstart")}
|
|
11264
11630
|
`);
|
|
@@ -11283,8 +11649,8 @@ ${bold("protect-mcp quickstart")}
|
|
|
11283
11649
|
Working dir: ${dir}
|
|
11284
11650
|
|
|
11285
11651
|
`);
|
|
11286
|
-
const keysDir =
|
|
11287
|
-
|
|
11652
|
+
const keysDir = join9(dir, "keys");
|
|
11653
|
+
mkdirSync4(keysDir, { recursive: true });
|
|
11288
11654
|
const { randomBytes: randomBytes4 } = await import("crypto");
|
|
11289
11655
|
let keypair;
|
|
11290
11656
|
try {
|
|
@@ -11304,13 +11670,13 @@ ${bold("protect-mcp quickstart")}
|
|
|
11304
11670
|
kid: `quickstart-${Date.now()}`
|
|
11305
11671
|
};
|
|
11306
11672
|
}
|
|
11307
|
-
|
|
11673
|
+
writeFileSync5(join9(keysDir, "gateway.json"), JSON.stringify({
|
|
11308
11674
|
privateKey: keypair.privateKey,
|
|
11309
11675
|
publicKey: keypair.publicKey,
|
|
11310
11676
|
kid: keypair.kid,
|
|
11311
11677
|
generated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
11312
11678
|
}, null, 2) + "\n");
|
|
11313
|
-
const configPath =
|
|
11679
|
+
const configPath = join9(dir, "protect-mcp.json");
|
|
11314
11680
|
const config = {
|
|
11315
11681
|
tools: {
|
|
11316
11682
|
"*": { rate_limit: "100/hour" },
|
|
@@ -11318,12 +11684,12 @@ ${bold("protect-mcp quickstart")}
|
|
|
11318
11684
|
},
|
|
11319
11685
|
default_tier: "unknown",
|
|
11320
11686
|
signing: {
|
|
11321
|
-
key_path:
|
|
11687
|
+
key_path: join9(keysDir, "gateway.json"),
|
|
11322
11688
|
issuer: "protect-mcp-quickstart",
|
|
11323
11689
|
enabled: true
|
|
11324
11690
|
}
|
|
11325
11691
|
};
|
|
11326
|
-
|
|
11692
|
+
writeFileSync5(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
11327
11693
|
process.stdout.write(` \u2713 Keypair generated (kid: ${keypair.kid})
|
|
11328
11694
|
`);
|
|
11329
11695
|
process.stdout.write(` \u2713 Policy created (shadow mode, all tools logged)
|
|
@@ -11338,7 +11704,7 @@ ${bold("protect-mcp quickstart")}
|
|
|
11338
11704
|
const dashboardUrl = await createSandbox();
|
|
11339
11705
|
if (dashboardUrl) {
|
|
11340
11706
|
const updatedConfig = { ...config, dashboard_url: dashboardUrl };
|
|
11341
|
-
|
|
11707
|
+
writeFileSync5(configPath, JSON.stringify(updatedConfig, null, 2) + "\n");
|
|
11342
11708
|
process.stdout.write(green(` \u2713 Dashboard created: ${dashboardUrl}
|
|
11343
11709
|
`));
|
|
11344
11710
|
process.stdout.write(` Receipts will be uploaded automatically.
|
|
@@ -11374,7 +11740,7 @@ ${bold("protect-mcp quickstart")}
|
|
|
11374
11740
|
}
|
|
11375
11741
|
async function handleRegistry(argv) {
|
|
11376
11742
|
const subcommand = argv[0] || "status";
|
|
11377
|
-
const dir = (0,
|
|
11743
|
+
const dir = (0, import_node_path9.resolve)(flagValue(argv, "--dir") || process.cwd());
|
|
11378
11744
|
const orgName = flagValue(argv, "--org") || process.env.SCOPEBLIND_ORG;
|
|
11379
11745
|
const orgId = flagValue(argv, "--org-id") || process.env.SCOPEBLIND_ORG_ID;
|
|
11380
11746
|
const billingAccountId = flagValue(argv, "--billing-account") || process.env.SCOPEBLIND_BILLING_ACCOUNT;
|
|
@@ -11456,14 +11822,14 @@ ${bold("protect-mcp registry anchor")}
|
|
|
11456
11822
|
return;
|
|
11457
11823
|
}
|
|
11458
11824
|
if (subcommand === "status") {
|
|
11459
|
-
const registryPath = (0,
|
|
11460
|
-
const identityPath = (0,
|
|
11825
|
+
const registryPath = (0, import_node_path9.join)(dir, registryMod.REGISTRY_FILE);
|
|
11826
|
+
const identityPath = (0, import_node_path9.join)(dir, registryMod.ORG_IDENTITY_FILE);
|
|
11461
11827
|
process.stdout.write(`
|
|
11462
11828
|
${bold("protect-mcp registry status")}
|
|
11463
11829
|
|
|
11464
11830
|
`);
|
|
11465
|
-
if ((0,
|
|
11466
|
-
const identity = JSON.parse((0,
|
|
11831
|
+
if ((0, import_node_fs13.existsSync)(identityPath)) {
|
|
11832
|
+
const identity = JSON.parse((0, import_node_fs13.readFileSync)(identityPath, "utf-8"));
|
|
11467
11833
|
process.stdout.write(` Org: ${identity.org_name || "unknown"}
|
|
11468
11834
|
`);
|
|
11469
11835
|
process.stdout.write(` Org ID: ${identity.org_id || "unknown"}
|
|
@@ -11474,8 +11840,8 @@ ${bold("protect-mcp registry status")}
|
|
|
11474
11840
|
process.stdout.write(` Org identity: ${yellow("missing")} (${identityPath})
|
|
11475
11841
|
`);
|
|
11476
11842
|
}
|
|
11477
|
-
if ((0,
|
|
11478
|
-
const registry = JSON.parse((0,
|
|
11843
|
+
if ((0, import_node_fs13.existsSync)(registryPath)) {
|
|
11844
|
+
const registry = JSON.parse((0, import_node_fs13.readFileSync)(registryPath, "utf-8"));
|
|
11479
11845
|
const hosted = Array.isArray(registry.anchors) && registry.anchors.some((a) => a.timestamp_source === "scopeblind-hosted");
|
|
11480
11846
|
process.stdout.write(` Registry: ${registryPath}
|
|
11481
11847
|
`);
|
|
@@ -11485,7 +11851,7 @@ ${bold("protect-mcp registry status")}
|
|
|
11485
11851
|
`);
|
|
11486
11852
|
process.stdout.write(` Boundary: ${hosted ? green("hosted digest anchor") : yellow("local preview only")}
|
|
11487
11853
|
`);
|
|
11488
|
-
process.stdout.write(` Verifier page: ${(0,
|
|
11854
|
+
process.stdout.write(` Verifier page: ${(0, import_node_path9.join)(dir, registryMod.VERIFIER_PAGE_FILE)}
|
|
11489
11855
|
`);
|
|
11490
11856
|
} else {
|
|
11491
11857
|
process.stdout.write(` Registry: ${yellow("missing")} (${registryPath})
|
|
@@ -11513,10 +11879,10 @@ async function handleKillerDemo(argv) {
|
|
|
11513
11879
|
verifySelectiveDisclosurePackage: verifySelectiveDisclosurePackage2
|
|
11514
11880
|
} = await Promise.resolve().then(() => (init_signing_committed(), signing_committed_exports));
|
|
11515
11881
|
const registryMod = await Promise.resolve().then(() => (init_receipt_registry(), receipt_registry_exports));
|
|
11516
|
-
const dir = (0,
|
|
11517
|
-
(0,
|
|
11518
|
-
(0,
|
|
11519
|
-
(0,
|
|
11882
|
+
const dir = (0, import_node_path9.resolve)(flagValue(argv, "--dir") || mkdtempSync((0, import_node_path9.join)(tmpdir(), "scopeblind-killer-demo-")));
|
|
11883
|
+
(0, import_node_fs13.mkdirSync)(dir, { recursive: true });
|
|
11884
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path9.join)(dir, "keys"), { recursive: true });
|
|
11885
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path9.join)(dir, "receipts"), { recursive: true });
|
|
11520
11886
|
const privateKeyBytes = randomBytes4(32);
|
|
11521
11887
|
const publicKeyBytes = ed255192.getPublicKey(privateKeyBytes);
|
|
11522
11888
|
const keypair = {
|
|
@@ -11525,14 +11891,14 @@ async function handleKillerDemo(argv) {
|
|
|
11525
11891
|
kid: `killer-demo-${Date.now()}`,
|
|
11526
11892
|
issuer: "scopeblind-killer-demo"
|
|
11527
11893
|
};
|
|
11528
|
-
const keyPath = (0,
|
|
11529
|
-
(0,
|
|
11894
|
+
const keyPath = (0, import_node_path9.join)(dir, "keys", "gateway.json");
|
|
11895
|
+
(0, import_node_fs13.writeFileSync)(keyPath, JSON.stringify({
|
|
11530
11896
|
...keypair,
|
|
11531
11897
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11532
11898
|
warning: "Demo key only. Do not use for production."
|
|
11533
11899
|
}, null, 2) + "\n");
|
|
11534
|
-
const shadowConfigPath = (0,
|
|
11535
|
-
const policyPackPath = (0,
|
|
11900
|
+
const shadowConfigPath = (0, import_node_path9.join)(dir, "protect-mcp.shadow.json");
|
|
11901
|
+
const policyPackPath = (0, import_node_path9.join)(dir, "protect-mcp.policy-pack.json");
|
|
11536
11902
|
const config = {
|
|
11537
11903
|
tools: { "*": { rate_limit: "100/hour" } },
|
|
11538
11904
|
default_tier: "signed-known",
|
|
@@ -11551,11 +11917,11 @@ async function handleKillerDemo(argv) {
|
|
|
11551
11917
|
signing: { key_path: keyPath, issuer: keypair.issuer, enabled: true },
|
|
11552
11918
|
notes: ["Demo policy pack: approvals for GitHub, email, and PMS booking; destructive tools blocked."]
|
|
11553
11919
|
};
|
|
11554
|
-
(0,
|
|
11555
|
-
(0,
|
|
11920
|
+
(0, import_node_fs13.writeFileSync)(shadowConfigPath, JSON.stringify(config, null, 2) + "\n");
|
|
11921
|
+
(0, import_node_fs13.writeFileSync)(policyPackPath, JSON.stringify(policyPack, null, 2) + "\n");
|
|
11556
11922
|
await initSigning({ enabled: true, key_path: keyPath, issuer: keypair.issuer });
|
|
11557
|
-
const logPath = (0,
|
|
11558
|
-
const receiptPath = (0,
|
|
11923
|
+
const logPath = (0, import_node_path9.join)(dir, ".protect-mcp-log.jsonl");
|
|
11924
|
+
const receiptPath = (0, import_node_path9.join)(dir, ".protect-mcp-receipts.jsonl");
|
|
11559
11925
|
const shadowCalls = [
|
|
11560
11926
|
{ tool: "read_file", input: { path: "/research/macro-notes.md" }, reason: "observe_mode" },
|
|
11561
11927
|
{ tool: "github_create_pr", input: { repo: "scopeblind/legate", branch: "agent/pms-adapter", title: "Wire mock PMS adapter" }, reason: "observe_mode" },
|
|
@@ -11564,7 +11930,7 @@ async function handleKillerDemo(argv) {
|
|
|
11564
11930
|
];
|
|
11565
11931
|
for (const [idx, call] of shadowCalls.entries()) {
|
|
11566
11932
|
const requestId2 = `demo-shadow-${idx + 1}`;
|
|
11567
|
-
(0,
|
|
11933
|
+
(0, import_node_fs13.appendFileSync)(logPath, JSON.stringify({
|
|
11568
11934
|
v: 2,
|
|
11569
11935
|
tool: call.tool,
|
|
11570
11936
|
decision: "allow",
|
|
@@ -11599,8 +11965,8 @@ async function handleKillerDemo(argv) {
|
|
|
11599
11965
|
policy_digest: (0, import_node_crypto7.createHash)("sha256").update(JSON.stringify(policyPack)).digest("hex").slice(0, 16),
|
|
11600
11966
|
action_readback: readback
|
|
11601
11967
|
};
|
|
11602
|
-
(0,
|
|
11603
|
-
(0,
|
|
11968
|
+
(0, import_node_fs13.appendFileSync)(logPath, JSON.stringify(requireApprovalEntry) + "\n");
|
|
11969
|
+
(0, import_node_fs13.appendFileSync)((0, import_node_path9.join)(dir, ".protect-mcp-approval-resolutions.jsonl"), JSON.stringify({
|
|
11604
11970
|
type: "scopeblind.approval_resolution.v1",
|
|
11605
11971
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11606
11972
|
request_id: requestId,
|
|
@@ -11620,11 +11986,11 @@ async function handleKillerDemo(argv) {
|
|
|
11620
11986
|
truncated: false
|
|
11621
11987
|
}
|
|
11622
11988
|
};
|
|
11623
|
-
(0,
|
|
11989
|
+
(0, import_node_fs13.appendFileSync)(logPath, JSON.stringify(executedEntry) + "\n");
|
|
11624
11990
|
const signed = signDecision(executedEntry);
|
|
11625
11991
|
if (!signed.signed) throw new Error(`demo signing failed: ${signed.warning || signed.error || "unknown"}`);
|
|
11626
|
-
(0,
|
|
11627
|
-
(0,
|
|
11992
|
+
(0, import_node_fs13.appendFileSync)(receiptPath, signed.signed + "\n");
|
|
11993
|
+
(0, import_node_fs13.writeFileSync)((0, import_node_path9.join)(dir, "receipts", "approved-pms-booking.receipt.json"), JSON.stringify(JSON.parse(signed.signed), null, 2) + "\n");
|
|
11628
11994
|
const receiptArtifact = JSON.parse(signed.signed);
|
|
11629
11995
|
const tamperedArtifact = JSON.parse(signed.signed);
|
|
11630
11996
|
if (tamperedArtifact.payload && typeof tamperedArtifact.payload === "object") {
|
|
@@ -11635,7 +12001,7 @@ async function handleKillerDemo(argv) {
|
|
|
11635
12001
|
}
|
|
11636
12002
|
const validOriginal = artifacts.verifyArtifact(receiptArtifact, keypair.publicKey);
|
|
11637
12003
|
const validTampered = artifacts.verifyArtifact(tamperedArtifact, keypair.publicKey);
|
|
11638
|
-
(0,
|
|
12004
|
+
(0, import_node_fs13.writeFileSync)((0, import_node_path9.join)(dir, "receipts", "tampered.receipt.json"), JSON.stringify(tamperedArtifact, null, 2) + "\n");
|
|
11639
12005
|
const committed = signCommittedDecision2(
|
|
11640
12006
|
executedEntry,
|
|
11641
12007
|
["tool", "payload_digest", "swarm"],
|
|
@@ -11647,11 +12013,11 @@ async function handleKillerDemo(argv) {
|
|
|
11647
12013
|
const committedReceipt = JSON.parse(committed.signed);
|
|
11648
12014
|
const disclosurePackage = createSelectiveDisclosurePackage2(committedReceipt, ["tool"], committed.openings);
|
|
11649
12015
|
const disclosureVerification = verifySelectiveDisclosurePackage2(committedReceipt, disclosurePackage);
|
|
11650
|
-
(0,
|
|
11651
|
-
(0,
|
|
11652
|
-
(0,
|
|
11653
|
-
(0,
|
|
11654
|
-
(0,
|
|
12016
|
+
(0, import_node_fs13.appendFileSync)(receiptPath, committed.signed + "\n");
|
|
12017
|
+
(0, import_node_fs13.writeFileSync)((0, import_node_path9.join)(dir, "receipts", "selective-disclosure.receipt.json"), JSON.stringify(committedReceipt, null, 2) + "\n");
|
|
12018
|
+
(0, import_node_fs13.writeFileSync)((0, import_node_path9.join)(dir, "receipts", "selective-disclosure.package.json"), JSON.stringify(disclosurePackage, null, 2) + "\n");
|
|
12019
|
+
(0, import_node_fs13.writeFileSync)((0, import_node_path9.join)(dir, "receipts", "selective-disclosure.tool-only.json"), JSON.stringify(disclosurePackage, null, 2) + "\n");
|
|
12020
|
+
(0, import_node_fs13.writeFileSync)((0, import_node_path9.join)(dir, "verification-results.json"), JSON.stringify({
|
|
11655
12021
|
original_receipt_valid: validOriginal,
|
|
11656
12022
|
tampered_receipt_valid: validTampered,
|
|
11657
12023
|
selective_disclosure_valid: disclosureVerification.valid,
|
|
@@ -11734,19 +12100,19 @@ async function handleKillerDemo(argv) {
|
|
|
11734
12100
|
"No raw prompt, payload, output, private key, or raw receipt is uploaded by the registry flow. Hosted mode submits receipt digests, request ids, org public keys, and billing account metadata only.",
|
|
11735
12101
|
""
|
|
11736
12102
|
].join("\n");
|
|
11737
|
-
(0,
|
|
11738
|
-
(0,
|
|
12103
|
+
(0, import_node_fs13.writeFileSync)((0, import_node_path9.join)(dir, "DEMO-RUNBOOK.md"), runbook);
|
|
12104
|
+
(0, import_node_fs13.writeFileSync)((0, import_node_path9.join)(dir, "demo-summary.json"), JSON.stringify({
|
|
11739
12105
|
dir,
|
|
11740
12106
|
dashboard_command: `npx protect-mcp dashboard --dir ${dir} --policy ${policyPackPath} --open`,
|
|
11741
12107
|
policy_pack: policyPackPath,
|
|
11742
|
-
receipt: (0,
|
|
11743
|
-
tampered_receipt: (0,
|
|
11744
|
-
selective_disclosure_receipt: (0,
|
|
11745
|
-
selective_disclosure_package: (0,
|
|
11746
|
-
verification_results: (0,
|
|
12108
|
+
receipt: (0, import_node_path9.join)(dir, "receipts", "approved-pms-booking.receipt.json"),
|
|
12109
|
+
tampered_receipt: (0, import_node_path9.join)(dir, "receipts", "tampered.receipt.json"),
|
|
12110
|
+
selective_disclosure_receipt: (0, import_node_path9.join)(dir, "receipts", "selective-disclosure.receipt.json"),
|
|
12111
|
+
selective_disclosure_package: (0, import_node_path9.join)(dir, "receipts", "selective-disclosure.tool-only.json"),
|
|
12112
|
+
verification_results: (0, import_node_path9.join)(dir, "verification-results.json"),
|
|
11747
12113
|
registry: registry.registryPath,
|
|
11748
12114
|
verifier_page: registry.verifierPath,
|
|
11749
|
-
runbook: (0,
|
|
12115
|
+
runbook: (0, import_node_path9.join)(dir, "DEMO-RUNBOOK.md"),
|
|
11750
12116
|
original_valid: validOriginal.valid,
|
|
11751
12117
|
tampered_valid: validTampered.valid,
|
|
11752
12118
|
selective_disclosure_valid: disclosureVerification.valid
|
|
@@ -11759,9 +12125,9 @@ ${bold("protect-mcp killer-demo")}
|
|
|
11759
12125
|
`);
|
|
11760
12126
|
process.stdout.write(` Dashboard: ${dim(`npx protect-mcp dashboard --dir ${dir} --policy ${policyPackPath} --open`)}
|
|
11761
12127
|
`);
|
|
11762
|
-
process.stdout.write(` Runbook: ${(0,
|
|
12128
|
+
process.stdout.write(` Runbook: ${(0, import_node_path9.join)(dir, "DEMO-RUNBOOK.md")}
|
|
11763
12129
|
`);
|
|
11764
|
-
process.stdout.write(` Signed receipt: ${(0,
|
|
12130
|
+
process.stdout.write(` Signed receipt: ${(0, import_node_path9.join)(dir, "receipts", "approved-pms-booking.receipt.json")}
|
|
11765
12131
|
`);
|
|
11766
12132
|
process.stdout.write(` Tamper check: original=${validOriginal.valid ? green("valid") : red("invalid")} tampered=${validTampered.valid ? red("valid") : green("invalid")}
|
|
11767
12133
|
`);
|
|
@@ -11781,8 +12147,8 @@ async function handleVerifyDisclosure(argv) {
|
|
|
11781
12147
|
process.exit(1);
|
|
11782
12148
|
}
|
|
11783
12149
|
const { verifySelectiveDisclosurePackage: verifySelectiveDisclosurePackage2 } = await Promise.resolve().then(() => (init_signing_committed(), signing_committed_exports));
|
|
11784
|
-
const receipt = JSON.parse((0,
|
|
11785
|
-
const disclosure = JSON.parse((0,
|
|
12150
|
+
const receipt = JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path9.resolve)(receiptPath), "utf-8"));
|
|
12151
|
+
const disclosure = JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path9.resolve)(disclosurePath), "utf-8"));
|
|
11786
12152
|
const result = verifySelectiveDisclosurePackage2(receipt, disclosure);
|
|
11787
12153
|
process.stdout.write(`
|
|
11788
12154
|
${bold("protect-mcp verify-disclosure")}
|
|
@@ -11818,7 +12184,7 @@ ${red("Errors:")}
|
|
|
11818
12184
|
async function handlePolicyPacks(argv) {
|
|
11819
12185
|
const subcommand = argv[0] || "list";
|
|
11820
12186
|
const packArg = argv[1];
|
|
11821
|
-
const dir = (0,
|
|
12187
|
+
const dir = (0, import_node_path9.resolve)(flagValue(argv, "--dir") || "./cedar");
|
|
11822
12188
|
const force = argv.includes("--force");
|
|
11823
12189
|
if (subcommand === "list") {
|
|
11824
12190
|
process.stdout.write(`
|
|
@@ -11873,17 +12239,17 @@ ${bold(pack.name)} (${pack.id})
|
|
|
11873
12239
|
`);
|
|
11874
12240
|
process.exit(1);
|
|
11875
12241
|
}
|
|
11876
|
-
(0,
|
|
12242
|
+
(0, import_node_fs13.mkdirSync)(dir, { recursive: true });
|
|
11877
12243
|
const written = [];
|
|
11878
12244
|
for (const pack of packs) {
|
|
11879
12245
|
for (const file of pack.files) {
|
|
11880
|
-
const outPath = (0,
|
|
11881
|
-
if ((0,
|
|
12246
|
+
const outPath = (0, import_node_path9.join)(dir, file.path);
|
|
12247
|
+
if ((0, import_node_fs13.existsSync)(outPath) && !force) {
|
|
11882
12248
|
process.stderr.write(`Refusing to overwrite ${outPath}. Re-run with --force if intentional.
|
|
11883
12249
|
`);
|
|
11884
12250
|
process.exit(1);
|
|
11885
12251
|
}
|
|
11886
|
-
(0,
|
|
12252
|
+
(0, import_node_fs13.writeFileSync)(outPath, file.contents.endsWith("\n") ? file.contents : `${file.contents}
|
|
11887
12253
|
`);
|
|
11888
12254
|
written.push(outPath);
|
|
11889
12255
|
}
|
|
@@ -11908,7 +12274,7 @@ Next: ${dim(`protect-mcp serve --cedar ${dir}`)} for shadow mode, then add ${dim
|
|
|
11908
12274
|
async function handleConnectors(argv) {
|
|
11909
12275
|
const subcommand = argv[0] || "list";
|
|
11910
12276
|
const pilotArg = argv[1];
|
|
11911
|
-
const dir = (0,
|
|
12277
|
+
const dir = (0, import_node_path9.resolve)(flagValue(argv, "--dir") || process.cwd());
|
|
11912
12278
|
const force = argv.includes("--force");
|
|
11913
12279
|
if (subcommand === "list") {
|
|
11914
12280
|
process.stdout.write(`
|
|
@@ -12150,11 +12516,11 @@ ${"\u2500".repeat(60)}
|
|
|
12150
12516
|
`);
|
|
12151
12517
|
}
|
|
12152
12518
|
async function traceLocal(receiptId) {
|
|
12153
|
-
const { readFileSync: readFileSync11, existsSync:
|
|
12154
|
-
const { join:
|
|
12519
|
+
const { readFileSync: readFileSync11, existsSync: existsSync10 } = await import("fs");
|
|
12520
|
+
const { join: join9 } = await import("path");
|
|
12155
12521
|
const dir = process.cwd();
|
|
12156
|
-
const receiptsDir =
|
|
12157
|
-
if (!
|
|
12522
|
+
const receiptsDir = join9(dir, ".protect-mcp", "receipts");
|
|
12523
|
+
if (!existsSync10(receiptsDir)) {
|
|
12158
12524
|
process.stdout.write(` No local receipts found in ${receiptsDir}
|
|
12159
12525
|
|
|
12160
12526
|
`);
|
|
@@ -12168,7 +12534,7 @@ async function traceLocal(receiptId) {
|
|
|
12168
12534
|
const receipts = [];
|
|
12169
12535
|
for (const file of files) {
|
|
12170
12536
|
try {
|
|
12171
|
-
const content = readFileSync11(
|
|
12537
|
+
const content = readFileSync11(join9(receiptsDir, file), "utf-8");
|
|
12172
12538
|
const receipt = JSON.parse(content);
|
|
12173
12539
|
receipts.push(receipt);
|
|
12174
12540
|
} catch {
|
|
@@ -12225,8 +12591,8 @@ function getTypeEmoji(type) {
|
|
|
12225
12591
|
}
|
|
12226
12592
|
}
|
|
12227
12593
|
async function handleInitHooks(argv) {
|
|
12228
|
-
const { writeFileSync:
|
|
12229
|
-
const { join:
|
|
12594
|
+
const { writeFileSync: writeFileSync5, existsSync: existsSync10, mkdirSync: mkdirSync4, readFileSync: readFileSync11 } = await import("fs");
|
|
12595
|
+
const { join: join9 } = await import("path");
|
|
12230
12596
|
const { generateHookSettings: generateHookSettings2, generateSampleCedarPolicy: generateSampleCedarPolicy2, generateVerifyReceiptSkill: generateVerifyReceiptSkill2 } = await Promise.resolve().then(() => (init_hook_patterns(), hook_patterns_exports));
|
|
12231
12597
|
let dir = process.cwd();
|
|
12232
12598
|
const dirIdx = argv.indexOf("--dir");
|
|
@@ -12240,13 +12606,13 @@ ${bold("protect-mcp init-hooks")}
|
|
|
12240
12606
|
process.stdout.write(`${"\u2500".repeat(55)}
|
|
12241
12607
|
|
|
12242
12608
|
`);
|
|
12243
|
-
const claudeDir =
|
|
12244
|
-
const settingsPath =
|
|
12609
|
+
const claudeDir = join9(dir, ".claude");
|
|
12610
|
+
const settingsPath = join9(claudeDir, "settings.json");
|
|
12245
12611
|
let existingSettings = {};
|
|
12246
|
-
if (!
|
|
12247
|
-
|
|
12612
|
+
if (!existsSync10(claudeDir)) {
|
|
12613
|
+
mkdirSync4(claudeDir, { recursive: true });
|
|
12248
12614
|
}
|
|
12249
|
-
if (
|
|
12615
|
+
if (existsSync10(settingsPath)) {
|
|
12250
12616
|
try {
|
|
12251
12617
|
existingSettings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
|
|
12252
12618
|
} catch {
|
|
@@ -12262,7 +12628,7 @@ ${bold("protect-mcp init-hooks")}
|
|
|
12262
12628
|
...hookSettings.hooks
|
|
12263
12629
|
}
|
|
12264
12630
|
};
|
|
12265
|
-
|
|
12631
|
+
writeFileSync5(settingsPath, JSON.stringify(mergedSettings, null, 2) + "\n");
|
|
12266
12632
|
process.stdout.write(` ${green("\u2713")} ${settingsPath}
|
|
12267
12633
|
`);
|
|
12268
12634
|
process.stdout.write(` Hook URL: ${dim(hookUrl)}
|
|
@@ -12270,26 +12636,26 @@ ${bold("protect-mcp init-hooks")}
|
|
|
12270
12636
|
process.stdout.write(` Events: PreToolUse, PostToolUse, SubagentStart/Stop, Task, Session, Config, Stop
|
|
12271
12637
|
|
|
12272
12638
|
`);
|
|
12273
|
-
const keysDir =
|
|
12274
|
-
const keyPath =
|
|
12275
|
-
if (!
|
|
12276
|
-
if (!
|
|
12639
|
+
const keysDir = join9(dir, "keys");
|
|
12640
|
+
const keyPath = join9(keysDir, "gateway.json");
|
|
12641
|
+
if (!existsSync10(keyPath)) {
|
|
12642
|
+
if (!existsSync10(keysDir)) mkdirSync4(keysDir, { recursive: true });
|
|
12277
12643
|
const { randomBytes: rb } = await import("crypto");
|
|
12278
12644
|
try {
|
|
12279
12645
|
const { ed25519: ed255192 } = await Promise.resolve().then(() => (init_ed25519(), ed25519_exports));
|
|
12280
12646
|
const { bytesToHex: bytesToHex2 } = await Promise.resolve().then(() => (init_utils(), utils_exports));
|
|
12281
12647
|
const privateKey = rb(32);
|
|
12282
12648
|
const publicKey = ed255192.getPublicKey(privateKey);
|
|
12283
|
-
|
|
12649
|
+
writeFileSync5(keyPath, JSON.stringify({
|
|
12284
12650
|
privateKey: bytesToHex2(privateKey),
|
|
12285
12651
|
publicKey: bytesToHex2(publicKey),
|
|
12286
12652
|
kid: `hook-${Date.now()}`,
|
|
12287
12653
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12288
12654
|
warning: "KEEP THIS FILE SECRET. Never commit to version control."
|
|
12289
12655
|
}, null, 2) + "\n");
|
|
12290
|
-
const gitignorePath =
|
|
12291
|
-
if (!
|
|
12292
|
-
|
|
12656
|
+
const gitignorePath = join9(keysDir, ".gitignore");
|
|
12657
|
+
if (!existsSync10(gitignorePath)) {
|
|
12658
|
+
writeFileSync5(gitignorePath, "# Never commit signing keys\n*.json\n");
|
|
12293
12659
|
}
|
|
12294
12660
|
process.stdout.write(` ${green("\u2713")} ${keyPath} (Ed25519 keypair)
|
|
12295
12661
|
|
|
@@ -12304,11 +12670,11 @@ ${bold("protect-mcp init-hooks")}
|
|
|
12304
12670
|
|
|
12305
12671
|
`);
|
|
12306
12672
|
}
|
|
12307
|
-
const policiesDir =
|
|
12308
|
-
const cedarPath =
|
|
12309
|
-
if (!
|
|
12310
|
-
if (!
|
|
12311
|
-
|
|
12673
|
+
const policiesDir = join9(dir, "policies");
|
|
12674
|
+
const cedarPath = join9(policiesDir, "agent.cedar");
|
|
12675
|
+
if (!existsSync10(cedarPath)) {
|
|
12676
|
+
if (!existsSync10(policiesDir)) mkdirSync4(policiesDir, { recursive: true });
|
|
12677
|
+
writeFileSync5(cedarPath, generateSampleCedarPolicy2());
|
|
12312
12678
|
process.stdout.write(` ${green("\u2713")} ${cedarPath}
|
|
12313
12679
|
`);
|
|
12314
12680
|
process.stdout.write(` Edit to customize tool permissions. Cedar deny is AUTHORITATIVE.
|
|
@@ -12319,8 +12685,8 @@ ${bold("protect-mcp init-hooks")}
|
|
|
12319
12685
|
|
|
12320
12686
|
`);
|
|
12321
12687
|
}
|
|
12322
|
-
const configPath =
|
|
12323
|
-
if (!
|
|
12688
|
+
const configPath = join9(dir, "protect-mcp.json");
|
|
12689
|
+
if (!existsSync10(configPath)) {
|
|
12324
12690
|
const config = {
|
|
12325
12691
|
tools: { "*": { rate_limit: "100/hour" } },
|
|
12326
12692
|
default_tier: "unknown",
|
|
@@ -12330,16 +12696,16 @@ ${bold("protect-mcp init-hooks")}
|
|
|
12330
12696
|
enabled: true
|
|
12331
12697
|
}
|
|
12332
12698
|
};
|
|
12333
|
-
|
|
12699
|
+
writeFileSync5(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
12334
12700
|
process.stdout.write(` ${green("\u2713")} ${configPath}
|
|
12335
12701
|
|
|
12336
12702
|
`);
|
|
12337
12703
|
}
|
|
12338
|
-
const skillsDir =
|
|
12339
|
-
const skillPath =
|
|
12340
|
-
if (!
|
|
12341
|
-
|
|
12342
|
-
|
|
12704
|
+
const skillsDir = join9(dir, ".claude", "skills", "verify-receipt");
|
|
12705
|
+
const skillPath = join9(skillsDir, "SKILL.md");
|
|
12706
|
+
if (!existsSync10(skillPath)) {
|
|
12707
|
+
mkdirSync4(skillsDir, { recursive: true });
|
|
12708
|
+
writeFileSync5(skillPath, generateVerifyReceiptSkill2());
|
|
12343
12709
|
process.stdout.write(` ${green("\u2713")} ${skillPath}
|
|
12344
12710
|
`);
|
|
12345
12711
|
process.stdout.write(` Use ${dim("/verify-receipt")} in Claude Code to check audit trails.
|
|
@@ -12395,13 +12761,13 @@ ${bold("protect-mcp init-hooks")}
|
|
|
12395
12761
|
}
|
|
12396
12762
|
async function sendInstallTelemetry() {
|
|
12397
12763
|
try {
|
|
12398
|
-
const { existsSync:
|
|
12399
|
-
const { join:
|
|
12764
|
+
const { existsSync: existsSync10, mkdirSync: mkdirSync4, writeFileSync: writeFileSync5, readFileSync: readFileSync11 } = await import("fs");
|
|
12765
|
+
const { join: join9, dirname: dirname3 } = await import("path");
|
|
12400
12766
|
const { homedir } = await import("os");
|
|
12401
12767
|
const { fileURLToPath } = await import("url");
|
|
12402
|
-
const markerDir =
|
|
12403
|
-
const markerFile =
|
|
12404
|
-
if (
|
|
12768
|
+
const markerDir = join9(homedir(), ".protect-mcp");
|
|
12769
|
+
const markerFile = join9(markerDir, ".telemetry-sent");
|
|
12770
|
+
if (existsSync10(markerFile) || process.env.PROTECT_MCP_TELEMETRY === "off") {
|
|
12405
12771
|
return;
|
|
12406
12772
|
}
|
|
12407
12773
|
const version = await pkgVersion();
|
|
@@ -12421,10 +12787,10 @@ async function sendInstallTelemetry() {
|
|
|
12421
12787
|
signal: controller.signal
|
|
12422
12788
|
}).catch(() => {
|
|
12423
12789
|
}).finally(() => clearTimeout(timeout));
|
|
12424
|
-
if (!
|
|
12425
|
-
|
|
12790
|
+
if (!existsSync10(markerDir)) {
|
|
12791
|
+
mkdirSync4(markerDir, { recursive: true });
|
|
12426
12792
|
}
|
|
12427
|
-
|
|
12793
|
+
writeFileSync5(markerFile, String(Date.now()), "utf-8");
|
|
12428
12794
|
process.stderr.write(
|
|
12429
12795
|
"[protect-mcp] Thanks for installing! Anonymous telemetry sent (disable: PROTECT_MCP_TELEMETRY=off)\n[protect-mcp] Free dashboard: npx protect-mcp connect | https://scopeblind.com\n"
|
|
12430
12796
|
);
|
|
@@ -12440,8 +12806,8 @@ function loadPolicyArg(argv) {
|
|
|
12440
12806
|
const policyFile = flagValue(argv, "--policy");
|
|
12441
12807
|
try {
|
|
12442
12808
|
if (cedarDir) return loadCedarPolicies(cedarDir);
|
|
12443
|
-
if (policyFile && (0,
|
|
12444
|
-
return policySetFromSource((0,
|
|
12809
|
+
if (policyFile && (0, import_node_fs13.existsSync)(policyFile)) {
|
|
12810
|
+
return policySetFromSource((0, import_node_fs13.readFileSync)(policyFile, "utf-8"), (0, import_node_path9.basename)(policyFile));
|
|
12445
12811
|
}
|
|
12446
12812
|
} catch {
|
|
12447
12813
|
}
|
|
@@ -12542,7 +12908,7 @@ async function handleSign(argv) {
|
|
|
12542
12908
|
if (m.tool) tool = m.tool;
|
|
12543
12909
|
}
|
|
12544
12910
|
}
|
|
12545
|
-
if (keyPath && (0,
|
|
12911
|
+
if (keyPath && (0, import_node_fs13.existsSync)(keyPath)) {
|
|
12546
12912
|
try {
|
|
12547
12913
|
await initSigning({ enabled: true, key_path: keyPath });
|
|
12548
12914
|
} catch {
|
|
@@ -12559,12 +12925,12 @@ async function handleSign(argv) {
|
|
|
12559
12925
|
timestamp: Date.now()
|
|
12560
12926
|
});
|
|
12561
12927
|
try {
|
|
12562
|
-
(0,
|
|
12928
|
+
(0, import_node_fs13.mkdirSync)(receiptsDir, { recursive: true });
|
|
12563
12929
|
} catch {
|
|
12564
12930
|
}
|
|
12565
12931
|
const line = signed.signed ?? JSON.stringify({ tool, request_id: requestId, signed: false, note: signed.warning || "no signer configured" });
|
|
12566
12932
|
try {
|
|
12567
|
-
(0,
|
|
12933
|
+
(0, import_node_fs13.appendFileSync)((0, import_node_path9.join)(receiptsDir, "receipts.jsonl"), line + "\n");
|
|
12568
12934
|
} catch {
|
|
12569
12935
|
}
|
|
12570
12936
|
if (format === "hermes") {
|
|
@@ -12574,12 +12940,55 @@ async function handleSign(argv) {
|
|
|
12574
12940
|
process.stdout.write(JSON.stringify({ signed: Boolean(signed.signed), artifact_type: signed.artifact_type, request_id: requestId }) + "\n");
|
|
12575
12941
|
process.exit(0);
|
|
12576
12942
|
}
|
|
12943
|
+
async function handleSample(argv) {
|
|
12944
|
+
const dir = flagValue(argv, "--dir") || process.cwd();
|
|
12945
|
+
const { buildSampleKit: buildSampleKit2 } = await Promise.resolve().then(() => (init_sample(), sample_exports));
|
|
12946
|
+
let kit;
|
|
12947
|
+
try {
|
|
12948
|
+
kit = buildSampleKit2(dir, { force: argv.includes("--force") });
|
|
12949
|
+
} catch (err) {
|
|
12950
|
+
if (err?.code === "SAMPLE_EXISTS") {
|
|
12951
|
+
process.stderr.write(
|
|
12952
|
+
"\nprotect-mcp sample: this folder already has a record or signing key.\nThis command seeds a LABELED SAMPLE record and will not touch a real one.\nRun it in an empty folder, or pass --force to overwrite.\n\n"
|
|
12953
|
+
);
|
|
12954
|
+
process.exit(1);
|
|
12955
|
+
}
|
|
12956
|
+
throw err;
|
|
12957
|
+
}
|
|
12958
|
+
process.stdout.write(`
|
|
12959
|
+
${bold("\u{1F6E1} Sample record seeded")} \xB7 8 decisions (1 blocked, 2 payments), signed with a fresh key ${dim(`(kid ${kit.kid})`)}
|
|
12960
|
+
|
|
12961
|
+
`);
|
|
12962
|
+
process.stdout.write(" .protect-mcp-receipts.jsonl the signed sample record\n");
|
|
12963
|
+
process.stdout.write(" demo-tampered.jsonl the same record with ONE decision edited after signing\n");
|
|
12964
|
+
process.stdout.write(` keys/gateway.json sample keypair ${dim("(never commit)")}
|
|
12965
|
+
|
|
12966
|
+
`);
|
|
12967
|
+
process.stdout.write(`${bold("Replay the demo")} ${dim("(the film: legate.scopeblind.com/record)")}
|
|
12968
|
+
`);
|
|
12969
|
+
process.stdout.write(" npx protect-mcp record\n");
|
|
12970
|
+
process.stdout.write(" npx protect-mcp claim --payment-under 100 --anchor --output payments-under-100.json\n");
|
|
12971
|
+
process.stdout.write(" npx protect-mcp verify-claim payments-under-100.json\n");
|
|
12972
|
+
process.stdout.write(" npx protect-mcp anchor-record\n\n");
|
|
12973
|
+
process.stdout.write(`${dim("Drop demo-tampered.jsonl into the record page to watch tampering get caught.")}
|
|
12974
|
+
`);
|
|
12975
|
+
process.stdout.write(`${dim("Everything runs locally; --anchor publishes only a digest to the public log.")}
|
|
12976
|
+
|
|
12977
|
+
`);
|
|
12978
|
+
process.exit(0);
|
|
12979
|
+
}
|
|
12577
12980
|
async function main() {
|
|
12578
12981
|
sendInstallTelemetry().catch(() => {
|
|
12579
12982
|
});
|
|
12580
12983
|
const args = process.argv.slice(2);
|
|
12581
12984
|
process.env.PROTECT_MCP_VERSION = process.env.PROTECT_MCP_VERSION || await pkgVersion();
|
|
12582
|
-
|
|
12985
|
+
const preSep = args.includes("--") ? args.slice(0, args.indexOf("--")) : args;
|
|
12986
|
+
if (args[0] === "version" || preSep.includes("--version") || preSep.includes("-V")) {
|
|
12987
|
+
process.stdout.write(`${process.env.PROTECT_MCP_VERSION || "unknown"}
|
|
12988
|
+
`);
|
|
12989
|
+
process.exit(0);
|
|
12990
|
+
}
|
|
12991
|
+
if (args.length === 0 || args[0] === "help" || preSep.includes("--help") || preSep.includes("-h")) {
|
|
12583
12992
|
printHelp();
|
|
12584
12993
|
process.exit(0);
|
|
12585
12994
|
}
|
|
@@ -12629,6 +13038,14 @@ async function main() {
|
|
|
12629
13038
|
await handleVerifyClaim(args.slice(1));
|
|
12630
13039
|
return;
|
|
12631
13040
|
}
|
|
13041
|
+
if (args[0] === "anchor-record") {
|
|
13042
|
+
await handleAnchorRecord(args.slice(1));
|
|
13043
|
+
return;
|
|
13044
|
+
}
|
|
13045
|
+
if (args[0] === "sample") {
|
|
13046
|
+
await handleSample(args.slice(1));
|
|
13047
|
+
return;
|
|
13048
|
+
}
|
|
12632
13049
|
if (args[0] === "init-hooks") {
|
|
12633
13050
|
await handleInitHooks(args.slice(1));
|
|
12634
13051
|
process.exit(0);
|
|
@@ -12733,10 +13150,10 @@ async function main() {
|
|
|
12733
13150
|
let cedarPolicySet = null;
|
|
12734
13151
|
let effectiveCedarDir = cedarDir;
|
|
12735
13152
|
if (!effectiveCedarDir && !policyPath) {
|
|
12736
|
-
const { existsSync:
|
|
13153
|
+
const { existsSync: existsSync10, readdirSync: readdirSync4 } = await import("fs");
|
|
12737
13154
|
for (const candidate of ["cedar", "policies", "."]) {
|
|
12738
13155
|
try {
|
|
12739
|
-
if (
|
|
13156
|
+
if (existsSync10(candidate) && readdirSync4(candidate).some((f) => f.endsWith(".cedar"))) {
|
|
12740
13157
|
effectiveCedarDir = candidate;
|
|
12741
13158
|
process.stderr.write(`[PROTECT_MCP] Auto-detected Cedar policies in ./${candidate}/
|
|
12742
13159
|
`);
|
|
@@ -12847,8 +13264,8 @@ async function handleSimulate(args) {
|
|
|
12847
13264
|
process.stderr.write("Usage: protect-mcp simulate --policy <path> [--log <path>] [--tier <tier>] [--json]\n");
|
|
12848
13265
|
process.exit(1);
|
|
12849
13266
|
}
|
|
12850
|
-
const { existsSync:
|
|
12851
|
-
if (!
|
|
13267
|
+
const { existsSync: existsSync10 } = await import("fs");
|
|
13268
|
+
if (!existsSync10(logPath)) {
|
|
12852
13269
|
process.stderr.write(`Log file not found: ${logPath}
|
|
12853
13270
|
`);
|
|
12854
13271
|
process.stderr.write("Run protect-mcp in shadow mode first to generate a log file.\n");
|
|
@@ -12870,8 +13287,8 @@ async function handleSimulate(args) {
|
|
|
12870
13287
|
}
|
|
12871
13288
|
}
|
|
12872
13289
|
async function handleDoctor() {
|
|
12873
|
-
const { existsSync:
|
|
12874
|
-
const { join:
|
|
13290
|
+
const { existsSync: existsSync10, readFileSync: readFileSync11, readdirSync: readdirSync4 } = await import("fs");
|
|
13291
|
+
const { join: join9 } = await import("path");
|
|
12875
13292
|
const { execSync } = await import("child_process");
|
|
12876
13293
|
const green2 = (s) => `\x1B[32m\u2713\x1B[0m ${s}`;
|
|
12877
13294
|
const red2 = (s) => `\x1B[31m\u2717\x1B[0m ${s}`;
|
|
@@ -12890,8 +13307,8 @@ async function handleDoctor() {
|
|
|
12890
13307
|
`));
|
|
12891
13308
|
issues++;
|
|
12892
13309
|
}
|
|
12893
|
-
const configPath =
|
|
12894
|
-
if (
|
|
13310
|
+
const configPath = join9(process.cwd(), "scopeblind.config.json");
|
|
13311
|
+
if (existsSync10(configPath)) {
|
|
12895
13312
|
try {
|
|
12896
13313
|
const config = JSON.parse(readFileSync11(configPath, "utf-8"));
|
|
12897
13314
|
if (config.signing?.private_key || config.signing?.key_file) {
|
|
@@ -12910,7 +13327,7 @@ async function handleDoctor() {
|
|
|
12910
13327
|
let policyFound = false;
|
|
12911
13328
|
for (const dir of ["cedar", "policies", "."]) {
|
|
12912
13329
|
try {
|
|
12913
|
-
if (
|
|
13330
|
+
if (existsSync10(dir) && readdirSync4(dir).some((f) => f.endsWith(".cedar"))) {
|
|
12914
13331
|
process.stdout.write(green2(`Cedar policies found in ./${dir}/
|
|
12915
13332
|
`));
|
|
12916
13333
|
policyFound = true;
|
|
@@ -12921,7 +13338,7 @@ async function handleDoctor() {
|
|
|
12921
13338
|
}
|
|
12922
13339
|
if (!policyFound) {
|
|
12923
13340
|
for (const name of ["policy.json", "protect-mcp.policy.json", "scopeblind-policy.json"]) {
|
|
12924
|
-
if (
|
|
13341
|
+
if (existsSync10(name)) {
|
|
12925
13342
|
process.stdout.write(green2(`JSON policy found: ${name}
|
|
12926
13343
|
`));
|
|
12927
13344
|
policyFound = true;
|
|
@@ -12942,9 +13359,9 @@ async function handleDoctor() {
|
|
|
12942
13359
|
} catch {
|
|
12943
13360
|
process.stdout.write(dim2(" Cedar WASM not installed\n"));
|
|
12944
13361
|
}
|
|
12945
|
-
const logFile =
|
|
12946
|
-
const receiptFile =
|
|
12947
|
-
if (
|
|
13362
|
+
const logFile = join9(process.cwd(), "protect-mcp-decisions.jsonl");
|
|
13363
|
+
const receiptFile = join9(process.cwd(), "protect-mcp-receipts.jsonl");
|
|
13364
|
+
if (existsSync10(logFile)) {
|
|
12948
13365
|
try {
|
|
12949
13366
|
const lines = readFileSync11(logFile, "utf-8").trim().split("\n").length;
|
|
12950
13367
|
process.stdout.write(green2(`Decision log: ${lines} entries
|
|
@@ -12955,7 +13372,7 @@ async function handleDoctor() {
|
|
|
12955
13372
|
} else {
|
|
12956
13373
|
process.stdout.write(dim2(" No decision log yet, will be created on first tool call\n"));
|
|
12957
13374
|
}
|
|
12958
|
-
if (
|
|
13375
|
+
if (existsSync10(receiptFile)) {
|
|
12959
13376
|
try {
|
|
12960
13377
|
const lines = readFileSync11(receiptFile, "utf-8").trim().split("\n").length;
|
|
12961
13378
|
process.stdout.write(green2(`Receipt file: ${lines} signed receipts
|
|
@@ -13026,9 +13443,9 @@ async function handleReport(args) {
|
|
|
13026
13443
|
}
|
|
13027
13444
|
}
|
|
13028
13445
|
const { generateReport: generateReport2, formatReportMarkdown: formatReportMarkdown2 } = await Promise.resolve().then(() => (init_report(), report_exports));
|
|
13029
|
-
const { join:
|
|
13030
|
-
const logPath =
|
|
13031
|
-
const receiptPath =
|
|
13446
|
+
const { join: join9 } = await import("path");
|
|
13447
|
+
const logPath = join9(dir, ".protect-mcp-log.jsonl");
|
|
13448
|
+
const receiptPath = join9(dir, ".protect-mcp-receipts.jsonl");
|
|
13032
13449
|
const report = generateReport2(logPath, receiptPath, period);
|
|
13033
13450
|
let output;
|
|
13034
13451
|
if (format === "md") {
|
|
@@ -13037,8 +13454,8 @@ async function handleReport(args) {
|
|
|
13037
13454
|
output = JSON.stringify(report, null, 2);
|
|
13038
13455
|
}
|
|
13039
13456
|
if (outputPath) {
|
|
13040
|
-
const { writeFileSync:
|
|
13041
|
-
|
|
13457
|
+
const { writeFileSync: writeFileSync5 } = await import("fs");
|
|
13458
|
+
writeFileSync5(outputPath, output, "utf-8");
|
|
13042
13459
|
process.stderr.write(`Report written to ${outputPath}
|
|
13043
13460
|
`);
|
|
13044
13461
|
} else {
|