protect-mcp 0.9.4 → 0.9.6
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 +43 -0
- package/README.md +22 -0
- package/dist/{chunk-H332ZNJ6.mjs → chunk-WCZJGEBO.mjs} +45 -4
- package/dist/cli.js +636 -300
- package/dist/cli.mjs +203 -3
- package/dist/hook-server.js +44 -3
- package/dist/hook-server.mjs +1 -1
- package/dist/index.js +44 -3
- package/dist/index.mjs +1 -1
- package/dist/sample-6LRP73ZD.mjs +99 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6319,6 +6319,101 @@ var init_hook_patterns = __esm({
|
|
|
6319
6319
|
}
|
|
6320
6320
|
});
|
|
6321
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
|
+
|
|
6322
6417
|
// src/scopeblind-bridge.ts
|
|
6323
6418
|
function getScopeBlindBridge() {
|
|
6324
6419
|
if (!singleton) singleton = new ScopeBlindBridge();
|
|
@@ -6527,7 +6622,7 @@ function detectSandboxState() {
|
|
|
6527
6622
|
}
|
|
6528
6623
|
if (process.platform === "linux") {
|
|
6529
6624
|
try {
|
|
6530
|
-
const procStatus = (0,
|
|
6625
|
+
const procStatus = (0, import_node_fs11.readFileSync)("/proc/self/status", "utf-8");
|
|
6531
6626
|
if (procStatus.includes("Seccomp: 2")) return "enabled";
|
|
6532
6627
|
} catch {
|
|
6533
6628
|
}
|
|
@@ -6555,6 +6650,7 @@ async function handlePreToolUse(input, state) {
|
|
|
6555
6650
|
...input.teamName && { team_name: input.teamName },
|
|
6556
6651
|
...input.agentType && { agent_type: input.agentType }
|
|
6557
6652
|
};
|
|
6653
|
+
maybeReloadCedar(state);
|
|
6558
6654
|
if (state.cedarPolicies) {
|
|
6559
6655
|
try {
|
|
6560
6656
|
const cedarDecision = await evaluateCedar(state.cedarPolicies, {
|
|
@@ -6592,10 +6688,13 @@ async function handlePreToolUse(input, state) {
|
|
|
6592
6688
|
sandbox_state: detectSandboxState(),
|
|
6593
6689
|
plan_receipt_id: state.activePlanReceiptId || void 0
|
|
6594
6690
|
});
|
|
6691
|
+
const isDefaultDeny = !reason || reason === "cedar_deny" || /reason":\[\]/.test(reason);
|
|
6692
|
+
const policyRef = state.cedarDir ? ` Policy: ${state.cedarDir}.` : "";
|
|
6693
|
+
const howTo = isDefaultDeny ? ` No permit matched (default-deny, fail-closed).${policyRef} Allow it: npx protect-mcp policy allow ${toolName}` : ` Blocked by an explicit forbid rule.${policyRef} Review: npx protect-mcp policy show`;
|
|
6595
6694
|
if (denyCount === 1) {
|
|
6596
6695
|
process.stderr.write(
|
|
6597
|
-
`[PROTECT_MCP]
|
|
6598
|
-
${
|
|
6696
|
+
`[PROTECT_MCP] Denied "${toolName}" (${isDefaultDeny ? "default-deny" : "forbid"}).
|
|
6697
|
+
Allow with: npx protect-mcp policy allow ${toolName}
|
|
6599
6698
|
`
|
|
6600
6699
|
);
|
|
6601
6700
|
}
|
|
@@ -6603,7 +6702,7 @@ async function handlePreToolUse(input, state) {
|
|
|
6603
6702
|
hookSpecificOutput: {
|
|
6604
6703
|
hookEventName: "PreToolUse",
|
|
6605
6704
|
permissionDecision: "deny",
|
|
6606
|
-
permissionDecisionReason: `[ScopeBlind] Denied
|
|
6705
|
+
permissionDecisionReason: `[ScopeBlind] Denied "${toolName}".${howTo}` + (denyCount > 1 ? ` (attempt ${denyCount})` : "")
|
|
6607
6706
|
}
|
|
6608
6707
|
};
|
|
6609
6708
|
}
|
|
@@ -6972,14 +7071,14 @@ function emitDecisionLog(state, entry) {
|
|
|
6972
7071
|
process.stderr.write(`[PROTECT_MCP] ${JSON.stringify(log)}
|
|
6973
7072
|
`);
|
|
6974
7073
|
try {
|
|
6975
|
-
(0,
|
|
7074
|
+
(0, import_node_fs11.appendFileSync)(state.logFilePath, JSON.stringify(log) + "\n");
|
|
6976
7075
|
} catch {
|
|
6977
7076
|
}
|
|
6978
7077
|
if (isSigningEnabled()) {
|
|
6979
7078
|
const signed = signDecision(log);
|
|
6980
7079
|
if (signed.signed) {
|
|
6981
7080
|
try {
|
|
6982
|
-
(0,
|
|
7081
|
+
(0, import_node_fs11.appendFileSync)(state.receiptFilePath, signed.signed + "\n");
|
|
6983
7082
|
} catch {
|
|
6984
7083
|
}
|
|
6985
7084
|
state.receiptBuffer.add(log.request_id, signed.signed);
|
|
@@ -7003,7 +7102,7 @@ function emitDecisionLog(state, entry) {
|
|
|
7003
7102
|
at: new Date(log.timestamp).toISOString()
|
|
7004
7103
|
});
|
|
7005
7104
|
try {
|
|
7006
|
-
(0,
|
|
7105
|
+
(0, import_node_fs11.appendFileSync)(state.receiptFilePath, tombstone + "\n");
|
|
7007
7106
|
} catch {
|
|
7008
7107
|
}
|
|
7009
7108
|
process.stderr.write(`[PROTECT_MCP_SIGNING_FAILURE] ${tombstone}
|
|
@@ -7091,8 +7190,8 @@ async function startHookServer(options = {}) {
|
|
|
7091
7190
|
}
|
|
7092
7191
|
}
|
|
7093
7192
|
if (!jsonPolicy?.signing) {
|
|
7094
|
-
const keyPath = (0,
|
|
7095
|
-
if ((0,
|
|
7193
|
+
const keyPath = (0, import_node_path8.join)(process.cwd(), "keys", "gateway.json");
|
|
7194
|
+
if ((0, import_node_fs11.existsSync)(keyPath)) {
|
|
7096
7195
|
const warnings = await initSigning({ key_path: keyPath, issuer: "protect-mcp", enabled: true });
|
|
7097
7196
|
for (const w of warnings) {
|
|
7098
7197
|
process.stderr.write(`[PROTECT_MCP] Warning: ${w}
|
|
@@ -7102,6 +7201,9 @@ async function startHookServer(options = {}) {
|
|
|
7102
7201
|
}
|
|
7103
7202
|
const state = {
|
|
7104
7203
|
cedarPolicies,
|
|
7204
|
+
cedarDir: cedarDir || null,
|
|
7205
|
+
cedarMtimeMs: cedarDir ? newestCedarMtime(cedarDir) : 0,
|
|
7206
|
+
cedarCheckedMs: 0,
|
|
7105
7207
|
jsonPolicy,
|
|
7106
7208
|
rateLimitStore: /* @__PURE__ */ new Map(),
|
|
7107
7209
|
receiptBuffer: new ReceiptBuffer(),
|
|
@@ -7114,8 +7216,8 @@ async function startHookServer(options = {}) {
|
|
|
7114
7216
|
verbose,
|
|
7115
7217
|
enforce,
|
|
7116
7218
|
policyDigest,
|
|
7117
|
-
logFilePath: (0,
|
|
7118
|
-
receiptFilePath: (0,
|
|
7219
|
+
logFilePath: (0, import_node_path8.join)(process.cwd(), LOG_FILE3),
|
|
7220
|
+
receiptFilePath: (0, import_node_path8.join)(process.cwd(), RECEIPTS_FILE2),
|
|
7119
7221
|
permissionSuggestions: /* @__PURE__ */ new Map(),
|
|
7120
7222
|
configAlerts: []
|
|
7121
7223
|
};
|
|
@@ -7264,7 +7366,7 @@ async function startHookServer(options = {}) {
|
|
|
7264
7366
|
`);
|
|
7265
7367
|
w(`
|
|
7266
7368
|
`);
|
|
7267
|
-
const hasSlug = process.env.SCOPEBLIND_SLUG || (0,
|
|
7369
|
+
const hasSlug = process.env.SCOPEBLIND_SLUG || (0, import_node_fs11.existsSync)((0, import_node_path8.join)(process.cwd(), ".scopeblind"));
|
|
7268
7370
|
if (!hasSlug) {
|
|
7269
7371
|
w(` Dashboard npx protect-mcp connect
|
|
7270
7372
|
`);
|
|
@@ -7292,11 +7394,44 @@ async function startHookServer(options = {}) {
|
|
|
7292
7394
|
process.on("SIGTERM", shutdown);
|
|
7293
7395
|
return server;
|
|
7294
7396
|
}
|
|
7397
|
+
function newestCedarMtime(dir) {
|
|
7398
|
+
try {
|
|
7399
|
+
let newest = 0;
|
|
7400
|
+
for (const f of (0, import_node_fs11.readdirSync)(dir)) {
|
|
7401
|
+
if (!f.endsWith(".cedar")) continue;
|
|
7402
|
+
const m = (0, import_node_fs11.statSync)((0, import_node_path8.join)(dir, f)).mtimeMs;
|
|
7403
|
+
if (m > newest) newest = m;
|
|
7404
|
+
}
|
|
7405
|
+
return newest;
|
|
7406
|
+
} catch {
|
|
7407
|
+
return 0;
|
|
7408
|
+
}
|
|
7409
|
+
}
|
|
7410
|
+
function maybeReloadCedar(state) {
|
|
7411
|
+
if (!state.cedarDir) return;
|
|
7412
|
+
const nowMs = Date.now();
|
|
7413
|
+
if (nowMs - state.cedarCheckedMs < CEDAR_CHECK_THROTTLE_MS) return;
|
|
7414
|
+
state.cedarCheckedMs = nowMs;
|
|
7415
|
+
const m = newestCedarMtime(state.cedarDir);
|
|
7416
|
+
if (m <= state.cedarMtimeMs) return;
|
|
7417
|
+
try {
|
|
7418
|
+
const reloaded = loadCedarPolicies(state.cedarDir);
|
|
7419
|
+
state.cedarPolicies = reloaded;
|
|
7420
|
+
state.policyDigest = reloaded.digest;
|
|
7421
|
+
state.cedarMtimeMs = m;
|
|
7422
|
+
process.stderr.write(`[PROTECT_MCP] Cedar policy reloaded (digest: ${reloaded.digest}) after on-disk change.
|
|
7423
|
+
`);
|
|
7424
|
+
} catch (err) {
|
|
7425
|
+
process.stderr.write(`[PROTECT_MCP] Cedar reload failed, keeping the previous policy: ${err instanceof Error ? err.message : err}
|
|
7426
|
+
`);
|
|
7427
|
+
state.cedarMtimeMs = m;
|
|
7428
|
+
}
|
|
7429
|
+
}
|
|
7295
7430
|
function findCedarDir() {
|
|
7296
7431
|
for (const candidate of ["cedar", "policies", "."]) {
|
|
7297
7432
|
try {
|
|
7298
|
-
if ((0,
|
|
7299
|
-
const files = (0,
|
|
7433
|
+
if ((0, import_node_fs11.existsSync)(candidate)) {
|
|
7434
|
+
const files = (0, import_node_fs11.readdirSync)(candidate, { encoding: "utf-8" });
|
|
7300
7435
|
if (files.some((f) => f.endsWith(".cedar"))) {
|
|
7301
7436
|
return candidate;
|
|
7302
7437
|
}
|
|
@@ -7317,14 +7452,14 @@ function normalizeHookInput(raw) {
|
|
|
7317
7452
|
}
|
|
7318
7453
|
return result;
|
|
7319
7454
|
}
|
|
7320
|
-
var import_node_http2, import_node_crypto6,
|
|
7455
|
+
var import_node_http2, import_node_crypto6, import_node_fs11, import_node_path8, DEFAULT_PORT, LOG_FILE3, RECEIPTS_FILE2, PAYLOAD_HASH_THRESHOLD, CEDAR_CHECK_THROTTLE_MS, SNAKE_TO_CAMEL_MAP;
|
|
7321
7456
|
var init_hook_server = __esm({
|
|
7322
7457
|
"src/hook-server.ts"() {
|
|
7323
7458
|
"use strict";
|
|
7324
7459
|
import_node_http2 = require("http");
|
|
7325
7460
|
import_node_crypto6 = require("crypto");
|
|
7326
|
-
|
|
7327
|
-
|
|
7461
|
+
import_node_fs11 = require("fs");
|
|
7462
|
+
import_node_path8 = require("path");
|
|
7328
7463
|
init_cedar_evaluator();
|
|
7329
7464
|
init_signing();
|
|
7330
7465
|
init_policy();
|
|
@@ -7336,6 +7471,7 @@ var init_hook_server = __esm({
|
|
|
7336
7471
|
LOG_FILE3 = ".protect-mcp-log.jsonl";
|
|
7337
7472
|
RECEIPTS_FILE2 = ".protect-mcp-receipts.jsonl";
|
|
7338
7473
|
PAYLOAD_HASH_THRESHOLD = 1024;
|
|
7474
|
+
CEDAR_CHECK_THROTTLE_MS = 2e3;
|
|
7339
7475
|
SNAKE_TO_CAMEL_MAP = {
|
|
7340
7476
|
hook_event_name: "hookEventName",
|
|
7341
7477
|
session_id: "sessionId",
|
|
@@ -7550,8 +7686,8 @@ function generateReport(logPath, receiptPath, periodDays) {
|
|
|
7550
7686
|
const now = /* @__PURE__ */ new Date();
|
|
7551
7687
|
const from = new Date(now.getTime() - periodDays * 864e5);
|
|
7552
7688
|
const entries = [];
|
|
7553
|
-
if ((0,
|
|
7554
|
-
const raw = (0,
|
|
7689
|
+
if ((0, import_node_fs12.existsSync)(logPath)) {
|
|
7690
|
+
const raw = (0, import_node_fs12.readFileSync)(logPath, "utf-8");
|
|
7555
7691
|
for (const line of raw.split("\n")) {
|
|
7556
7692
|
const trimmed = line.trim();
|
|
7557
7693
|
if (!trimmed) continue;
|
|
@@ -7571,8 +7707,8 @@ function generateReport(logPath, receiptPath, periodDays) {
|
|
|
7571
7707
|
let receiptsSigned = 0;
|
|
7572
7708
|
let signerKid = "";
|
|
7573
7709
|
let signerIssuer = "";
|
|
7574
|
-
if ((0,
|
|
7575
|
-
const raw = (0,
|
|
7710
|
+
if ((0, import_node_fs12.existsSync)(receiptPath)) {
|
|
7711
|
+
const raw = (0, import_node_fs12.readFileSync)(receiptPath, "utf-8");
|
|
7576
7712
|
for (const line of raw.split("\n")) {
|
|
7577
7713
|
const trimmed = line.trim();
|
|
7578
7714
|
if (!trimmed) continue;
|
|
@@ -7704,11 +7840,11 @@ function formatReportMarkdown(report) {
|
|
|
7704
7840
|
lines.push("*Generated by protect-mcp \xB7 scopeblind.com*");
|
|
7705
7841
|
return lines.join("\n");
|
|
7706
7842
|
}
|
|
7707
|
-
var
|
|
7843
|
+
var import_node_fs12;
|
|
7708
7844
|
var init_report = __esm({
|
|
7709
7845
|
"src/report.ts"() {
|
|
7710
7846
|
"use strict";
|
|
7711
|
-
|
|
7847
|
+
import_node_fs12 = require("fs");
|
|
7712
7848
|
}
|
|
7713
7849
|
});
|
|
7714
7850
|
|
|
@@ -8752,8 +8888,8 @@ Next: run \`npx protect-mcp dashboard --open\` and review tool inventory, policy
|
|
|
8752
8888
|
|
|
8753
8889
|
// src/cli.ts
|
|
8754
8890
|
var import_node_crypto7 = require("crypto");
|
|
8755
|
-
var
|
|
8756
|
-
var
|
|
8891
|
+
var import_node_fs13 = require("fs");
|
|
8892
|
+
var import_node_path9 = require("path");
|
|
8757
8893
|
var import_node_os = require("os");
|
|
8758
8894
|
function printHelp() {
|
|
8759
8895
|
process.stderr.write(`
|
|
@@ -8775,14 +8911,17 @@ Usage:
|
|
|
8775
8911
|
protect-mcp policy-packs list|show|install [pack] [--dir ./cedar] [--force]
|
|
8776
8912
|
protect-mcp connect
|
|
8777
8913
|
protect-mcp init [--dir <path>]
|
|
8914
|
+
protect-mcp sample [--dir <path>] [--force]
|
|
8915
|
+
protect-mcp policy list|show|allow <tool>|deny <tool>|path
|
|
8778
8916
|
protect-mcp demo
|
|
8779
8917
|
protect-mcp trace <receipt_id> [--endpoint <url>] [--depth <n>]
|
|
8780
8918
|
protect-mcp status [--dir <path>]
|
|
8781
8919
|
protect-mcp digest [--today] [--dir <path>]
|
|
8782
8920
|
protect-mcp receipts [--last <n>] [--dir <path>]
|
|
8783
8921
|
protect-mcp record [--dir <path>] [--live] [--no-open]
|
|
8784
|
-
protect-mcp claim [--no <cap>] [--only <c,c>] [--count <verdict>] [--dir <path>] [--output <path>]
|
|
8785
|
-
protect-mcp verify-claim <claim.json> [--key <public-hex>]
|
|
8922
|
+
protect-mcp claim [--no <cap>] [--only <c,c>] [--count <verdict>] [--payment-under <amount>] [--anchor] [--dir <path>] [--output <path>]
|
|
8923
|
+
protect-mcp verify-claim <claim.json> [--key <public-hex>] [--check-anchor] [--offline]
|
|
8924
|
+
protect-mcp anchor-record [--dir <path>] [--force]
|
|
8786
8925
|
protect-mcp bundle [--output <path>] [--dir <path>]
|
|
8787
8926
|
protect-mcp simulate --policy <path> [--log <path>] [--tier <tier>] [--json]
|
|
8788
8927
|
protect-mcp report [--period <days>d] [--format md|json] [--output <path>] [--dir <path>]
|
|
@@ -8796,6 +8935,7 @@ Options:
|
|
|
8796
8935
|
--port <port> HTTP server port (default: 3000 for --http, 9377 for serve)
|
|
8797
8936
|
--verbose Enable debug logging to stderr
|
|
8798
8937
|
--help Show this help
|
|
8938
|
+
--version Print the installed version
|
|
8799
8939
|
|
|
8800
8940
|
Commands:
|
|
8801
8941
|
serve Start HTTP hook server for Claude Code integration (port 9377)
|
|
@@ -8906,17 +9046,17 @@ function parseArgs(argv) {
|
|
|
8906
9046
|
return { policyPath, cedarDir, slug, enforce, verbose, childCommand };
|
|
8907
9047
|
}
|
|
8908
9048
|
async function handleInit(argv) {
|
|
8909
|
-
const { writeFileSync:
|
|
8910
|
-
const { join:
|
|
9049
|
+
const { writeFileSync: writeFileSync5, existsSync: existsSync10, mkdirSync: mkdirSync4 } = await import("fs");
|
|
9050
|
+
const { join: join9 } = await import("path");
|
|
8911
9051
|
let dir = process.cwd();
|
|
8912
9052
|
const dirIdx = argv.indexOf("--dir");
|
|
8913
9053
|
if (dirIdx !== -1 && argv[dirIdx + 1]) {
|
|
8914
9054
|
dir = argv[dirIdx + 1];
|
|
8915
9055
|
}
|
|
8916
|
-
const configPath =
|
|
8917
|
-
const keysDir =
|
|
8918
|
-
const keyPath =
|
|
8919
|
-
if (
|
|
9056
|
+
const configPath = join9(dir, "protect-mcp.json");
|
|
9057
|
+
const keysDir = join9(dir, "keys");
|
|
9058
|
+
const keyPath = join9(keysDir, "gateway.json");
|
|
9059
|
+
if (existsSync10(configPath)) {
|
|
8920
9060
|
process.stderr.write(`[PROTECT_MCP] Config already exists at ${configPath}
|
|
8921
9061
|
`);
|
|
8922
9062
|
process.stderr.write("[PROTECT_MCP] Delete it first if you want to regenerate.\n");
|
|
@@ -8935,19 +9075,19 @@ async function handleInit(argv) {
|
|
|
8935
9075
|
kid: "generated"
|
|
8936
9076
|
};
|
|
8937
9077
|
}
|
|
8938
|
-
if (!
|
|
8939
|
-
|
|
9078
|
+
if (!existsSync10(keysDir)) {
|
|
9079
|
+
mkdirSync4(keysDir, { recursive: true });
|
|
8940
9080
|
}
|
|
8941
|
-
|
|
9081
|
+
writeFileSync5(keyPath, JSON.stringify({
|
|
8942
9082
|
privateKey: keypair.privateKey,
|
|
8943
9083
|
publicKey: keypair.publicKey,
|
|
8944
9084
|
kid: keypair.kid,
|
|
8945
9085
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8946
9086
|
warning: "KEEP THIS FILE SECRET. Never commit to version control."
|
|
8947
9087
|
}, null, 2) + "\n");
|
|
8948
|
-
const gitignorePath =
|
|
8949
|
-
if (!
|
|
8950
|
-
|
|
9088
|
+
const gitignorePath = join9(keysDir, ".gitignore");
|
|
9089
|
+
if (!existsSync10(gitignorePath)) {
|
|
9090
|
+
writeFileSync5(gitignorePath, "# Never commit signing keys\n*.json\n");
|
|
8951
9091
|
}
|
|
8952
9092
|
const config = {
|
|
8953
9093
|
tools: {
|
|
@@ -8981,7 +9121,7 @@ async function handleInit(argv) {
|
|
|
8981
9121
|
}
|
|
8982
9122
|
}
|
|
8983
9123
|
};
|
|
8984
|
-
|
|
9124
|
+
writeFileSync5(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
8985
9125
|
const claudeConfig = {
|
|
8986
9126
|
"mcpServers": {
|
|
8987
9127
|
"my-server": {
|
|
@@ -9019,8 +9159,8 @@ Add --enforce when ready to block policy violations.
|
|
|
9019
9159
|
`);
|
|
9020
9160
|
}
|
|
9021
9161
|
async function handleDemo() {
|
|
9022
|
-
const { existsSync:
|
|
9023
|
-
const { join:
|
|
9162
|
+
const { existsSync: existsSync10 } = await import("fs");
|
|
9163
|
+
const { join: join9, dirname: dirname3, resolve } = await import("path");
|
|
9024
9164
|
const { realpathSync } = await import("fs");
|
|
9025
9165
|
const cliPath = resolve(process.argv[1] || "dist/cli.js");
|
|
9026
9166
|
let cliDir;
|
|
@@ -9029,9 +9169,9 @@ async function handleDemo() {
|
|
|
9029
9169
|
} catch {
|
|
9030
9170
|
cliDir = dirname3(cliPath);
|
|
9031
9171
|
}
|
|
9032
|
-
const demoServerPath =
|
|
9033
|
-
const configPath =
|
|
9034
|
-
const hasConfig =
|
|
9172
|
+
const demoServerPath = join9(cliDir, "demo-server.js");
|
|
9173
|
+
const configPath = join9(process.cwd(), "protect-mcp.json");
|
|
9174
|
+
const hasConfig = existsSync10(configPath);
|
|
9035
9175
|
if (!hasConfig) {
|
|
9036
9176
|
process.stderr.write(`
|
|
9037
9177
|
${bold("protect-mcp demo")}
|
|
@@ -9105,15 +9245,15 @@ Starting demo server with 5 tools...
|
|
|
9105
9245
|
await gateway.start();
|
|
9106
9246
|
}
|
|
9107
9247
|
async function handleStatus2(argv) {
|
|
9108
|
-
const { readFileSync: readFileSync11, existsSync:
|
|
9109
|
-
const { join:
|
|
9248
|
+
const { readFileSync: readFileSync11, existsSync: existsSync10 } = await import("fs");
|
|
9249
|
+
const { join: join9 } = await import("path");
|
|
9110
9250
|
let dir = process.cwd();
|
|
9111
9251
|
const dirIdx = argv.indexOf("--dir");
|
|
9112
9252
|
if (dirIdx !== -1 && argv[dirIdx + 1]) {
|
|
9113
9253
|
dir = argv[dirIdx + 1];
|
|
9114
9254
|
}
|
|
9115
|
-
const logPath =
|
|
9116
|
-
if (!
|
|
9255
|
+
const logPath = join9(dir, ".protect-mcp-log.jsonl");
|
|
9256
|
+
if (!existsSync10(logPath)) {
|
|
9117
9257
|
process.stderr.write(`${bold("protect-mcp status")}
|
|
9118
9258
|
|
|
9119
9259
|
`);
|
|
@@ -9202,8 +9342,8 @@ ${bold("protect-mcp status")}
|
|
|
9202
9342
|
process.stdout.write(` ${reason.padEnd(25)} ${count}
|
|
9203
9343
|
`);
|
|
9204
9344
|
}
|
|
9205
|
-
const evidencePath =
|
|
9206
|
-
if (
|
|
9345
|
+
const evidencePath = join9(dir, ".protect-mcp-evidence.json");
|
|
9346
|
+
if (existsSync10(evidencePath)) {
|
|
9207
9347
|
try {
|
|
9208
9348
|
const evidenceRaw = readFileSync11(evidencePath, "utf-8");
|
|
9209
9349
|
const evidence = JSON.parse(evidenceRaw);
|
|
@@ -9214,8 +9354,8 @@ ${bold("protect-mcp status")}
|
|
|
9214
9354
|
} catch {
|
|
9215
9355
|
}
|
|
9216
9356
|
}
|
|
9217
|
-
const keyPath =
|
|
9218
|
-
if (
|
|
9357
|
+
const keyPath = join9(dir, "keys", "gateway.json");
|
|
9358
|
+
if (existsSync10(keyPath)) {
|
|
9219
9359
|
try {
|
|
9220
9360
|
const keyData = JSON.parse(readFileSync11(keyPath, "utf-8"));
|
|
9221
9361
|
if (keyData.publicKey) {
|
|
@@ -9245,7 +9385,7 @@ function commandNeedsValue(argv, flag) {
|
|
|
9245
9385
|
return Boolean(value && !value.startsWith("--"));
|
|
9246
9386
|
}
|
|
9247
9387
|
function absoluteOrCwd(pathValue) {
|
|
9248
|
-
return (0,
|
|
9388
|
+
return (0, import_node_path9.resolve)(process.cwd(), pathValue);
|
|
9249
9389
|
}
|
|
9250
9390
|
function shellQuoteArg(arg) {
|
|
9251
9391
|
if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(arg)) return arg;
|
|
@@ -9264,18 +9404,18 @@ function wrapperArgsFor(command, opts) {
|
|
|
9264
9404
|
}
|
|
9265
9405
|
function claudeDesktopConfigPath() {
|
|
9266
9406
|
if (process.platform === "darwin") {
|
|
9267
|
-
return (0,
|
|
9407
|
+
return (0, import_node_path9.join)((0, import_node_os.homedir)(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
9268
9408
|
}
|
|
9269
9409
|
if (process.platform === "win32") {
|
|
9270
|
-
return (0,
|
|
9410
|
+
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");
|
|
9271
9411
|
}
|
|
9272
|
-
return (0,
|
|
9412
|
+
return (0, import_node_path9.join)((0, import_node_os.homedir)(), ".config", "Claude", "claude_desktop_config.json");
|
|
9273
9413
|
}
|
|
9274
9414
|
async function ensureLocalConfig(dir = process.cwd()) {
|
|
9275
|
-
const { existsSync:
|
|
9276
|
-
const { join:
|
|
9277
|
-
const configPath =
|
|
9278
|
-
if (!
|
|
9415
|
+
const { existsSync: existsSync10 } = await import("fs");
|
|
9416
|
+
const { join: join9, resolve } = await import("path");
|
|
9417
|
+
const configPath = join9(dir, "protect-mcp.json");
|
|
9418
|
+
if (!existsSync10(configPath)) {
|
|
9279
9419
|
process.stderr.write(`${bold("protect-mcp wrap")}
|
|
9280
9420
|
|
|
9281
9421
|
No protect-mcp.json found; creating local shadow-mode config first.
|
|
@@ -9287,7 +9427,7 @@ No protect-mcp.json found; creating local shadow-mode config first.
|
|
|
9287
9427
|
}
|
|
9288
9428
|
function parseJsonlFile(pathValue) {
|
|
9289
9429
|
try {
|
|
9290
|
-
const raw = (0,
|
|
9430
|
+
const raw = (0, import_node_fs13.readFileSync)(pathValue, "utf-8");
|
|
9291
9431
|
return raw.split("\n").map((line) => line.trim()).filter(Boolean).flatMap((line) => {
|
|
9292
9432
|
try {
|
|
9293
9433
|
return [JSON.parse(line)];
|
|
@@ -9301,7 +9441,7 @@ function parseJsonlFile(pathValue) {
|
|
|
9301
9441
|
}
|
|
9302
9442
|
function parseJsonlRecords(pathValue) {
|
|
9303
9443
|
try {
|
|
9304
|
-
const raw = (0,
|
|
9444
|
+
const raw = (0, import_node_fs13.readFileSync)(pathValue, "utf-8");
|
|
9305
9445
|
return raw.split("\n").map((line) => line.trim()).filter(Boolean).flatMap((line) => {
|
|
9306
9446
|
try {
|
|
9307
9447
|
return [{
|
|
@@ -9319,8 +9459,8 @@ function parseJsonlRecords(pathValue) {
|
|
|
9319
9459
|
}
|
|
9320
9460
|
function loadPolicyJson(policyPath) {
|
|
9321
9461
|
try {
|
|
9322
|
-
if (!(0,
|
|
9323
|
-
return JSON.parse((0,
|
|
9462
|
+
if (!(0, import_node_fs13.existsSync)(policyPath)) return null;
|
|
9463
|
+
return JSON.parse((0, import_node_fs13.readFileSync)(policyPath, "utf-8"));
|
|
9324
9464
|
} catch {
|
|
9325
9465
|
return null;
|
|
9326
9466
|
}
|
|
@@ -9465,10 +9605,10 @@ function suggestedGuardrailFor(_tool, risk, reasons) {
|
|
|
9465
9605
|
policy: { rate_limit: "100/hour" }
|
|
9466
9606
|
};
|
|
9467
9607
|
}
|
|
9468
|
-
function buildDashboardSummary(dir, policyPath = (0,
|
|
9469
|
-
const logPath = (0,
|
|
9470
|
-
const receiptPath = (0,
|
|
9471
|
-
const keyPath = (0,
|
|
9608
|
+
function buildDashboardSummary(dir, policyPath = (0, import_node_path9.join)(dir, "protect-mcp.json")) {
|
|
9609
|
+
const logPath = (0, import_node_path9.join)(dir, ".protect-mcp-log.jsonl");
|
|
9610
|
+
const receiptPath = (0, import_node_path9.join)(dir, ".protect-mcp-receipts.jsonl");
|
|
9611
|
+
const keyPath = (0, import_node_path9.join)(dir, "keys", "gateway.json");
|
|
9472
9612
|
const entries = parseJsonlFile(logPath);
|
|
9473
9613
|
const receiptRecords = parseJsonlRecords(receiptPath);
|
|
9474
9614
|
const receipts = receiptRecords.map((record) => record.value);
|
|
@@ -9513,9 +9653,9 @@ function buildDashboardSummary(dir, policyPath = (0, import_node_path8.join)(dir
|
|
|
9513
9653
|
const pendingApprovals = entries.filter((e) => e.decision === "require_approval").slice(-25).reverse();
|
|
9514
9654
|
const chains = buildReceiptChains(entries, receiptRecords);
|
|
9515
9655
|
let key = null;
|
|
9516
|
-
if ((0,
|
|
9656
|
+
if ((0, import_node_fs13.existsSync)(keyPath)) {
|
|
9517
9657
|
try {
|
|
9518
|
-
const parsed = JSON.parse((0,
|
|
9658
|
+
const parsed = JSON.parse((0, import_node_fs13.readFileSync)(keyPath, "utf-8"));
|
|
9519
9659
|
key = {
|
|
9520
9660
|
kid: parsed.kid || null,
|
|
9521
9661
|
issuer: parsed.issuer || "protect-mcp",
|
|
@@ -9532,10 +9672,10 @@ function buildDashboardSummary(dir, policyPath = (0, import_node_path8.join)(dir
|
|
|
9532
9672
|
receipts: receiptPath,
|
|
9533
9673
|
key: keyPath,
|
|
9534
9674
|
policy: policyPath,
|
|
9535
|
-
log_exists: (0,
|
|
9536
|
-
receipts_exist: (0,
|
|
9537
|
-
key_exists: (0,
|
|
9538
|
-
policy_exists: (0,
|
|
9675
|
+
log_exists: (0, import_node_fs13.existsSync)(logPath),
|
|
9676
|
+
receipts_exist: (0, import_node_fs13.existsSync)(receiptPath),
|
|
9677
|
+
key_exists: (0, import_node_fs13.existsSync)(keyPath),
|
|
9678
|
+
policy_exists: (0, import_node_fs13.existsSync)(policyPath)
|
|
9539
9679
|
},
|
|
9540
9680
|
totals: {
|
|
9541
9681
|
decisions: entries.length,
|
|
@@ -9572,7 +9712,7 @@ function buildDashboardSummary(dir, policyPath = (0, import_node_path8.join)(dir
|
|
|
9572
9712
|
}))
|
|
9573
9713
|
},
|
|
9574
9714
|
connector_pilots: {
|
|
9575
|
-
directory: (0,
|
|
9715
|
+
directory: (0, import_node_path9.join)(dir, ".protect-mcp", "connectors"),
|
|
9576
9716
|
installed: readInstalledConnectorPilots(dir),
|
|
9577
9717
|
doctor: connectorDoctor(dir),
|
|
9578
9718
|
available: CONNECTOR_PILOTS.map((pilot) => ({
|
|
@@ -9950,7 +10090,7 @@ async function handleDashboard(argv) {
|
|
|
9950
10090
|
const { resolve } = await import("path");
|
|
9951
10091
|
const port = commandNeedsValue(argv, "--port") ? parseInt(flagValue(argv, "--port") || "9877", 10) : 9877;
|
|
9952
10092
|
const dir = resolve(commandNeedsValue(argv, "--dir") ? flagValue(argv, "--dir") || process.cwd() : process.cwd());
|
|
9953
|
-
const policyPath = resolve(flagValue(argv, "--policy") || (0,
|
|
10093
|
+
const policyPath = resolve(flagValue(argv, "--policy") || (0, import_node_path9.join)(dir, "protect-mcp.json"));
|
|
9954
10094
|
const approvalEndpoint = flagValue(argv, "--approval-endpoint");
|
|
9955
10095
|
const approvalNonce = flagValue(argv, "--approval-nonce");
|
|
9956
10096
|
const open = argv.includes("--open");
|
|
@@ -10155,32 +10295,32 @@ function writeToolPolicy(policyPath, tool, action) {
|
|
|
10155
10295
|
tools,
|
|
10156
10296
|
default_tier: existing.default_tier || "unknown"
|
|
10157
10297
|
};
|
|
10158
|
-
(0,
|
|
10298
|
+
(0, import_node_fs13.writeFileSync)(policyPath, JSON.stringify(next, null, 2) + "\n");
|
|
10159
10299
|
return next;
|
|
10160
10300
|
}
|
|
10161
10301
|
function policyPackDirectory(dir) {
|
|
10162
|
-
return (0,
|
|
10302
|
+
return (0, import_node_path9.join)(dir, "cedar");
|
|
10163
10303
|
}
|
|
10164
10304
|
function installedPolicyPackIds(dir) {
|
|
10165
10305
|
const cedarDir = policyPackDirectory(dir);
|
|
10166
10306
|
return POLICY_PACKS.filter(
|
|
10167
|
-
(pack) => pack.files.every((file) => (0,
|
|
10307
|
+
(pack) => pack.files.every((file) => (0, import_node_fs13.existsSync)((0, import_node_path9.join)(cedarDir, file.path)))
|
|
10168
10308
|
).map((pack) => pack.id);
|
|
10169
10309
|
}
|
|
10170
10310
|
function installPolicyPackToDir(dir, packId, force = false) {
|
|
10171
10311
|
const packs = packId === "all" ? POLICY_PACKS : [getPolicyPack(packId)].filter(Boolean);
|
|
10172
10312
|
if (packs.length === 0) throw new Error(`Unknown policy pack: ${packId}`);
|
|
10173
10313
|
const outDir = policyPackDirectory(dir);
|
|
10174
|
-
(0,
|
|
10314
|
+
(0, import_node_fs13.mkdirSync)(outDir, { recursive: true });
|
|
10175
10315
|
const written = [];
|
|
10176
10316
|
for (const pack of packs) {
|
|
10177
10317
|
for (const file of pack.files) {
|
|
10178
|
-
const outPath = (0,
|
|
10179
|
-
if ((0,
|
|
10318
|
+
const outPath = (0, import_node_path9.join)(outDir, file.path);
|
|
10319
|
+
if ((0, import_node_fs13.existsSync)(outPath) && !force) {
|
|
10180
10320
|
throw new Error(`Refusing to overwrite ${outPath}. Pass force=true if intentional.`);
|
|
10181
10321
|
}
|
|
10182
|
-
(0,
|
|
10183
|
-
(0,
|
|
10322
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path9.dirname)(outPath), { recursive: true });
|
|
10323
|
+
(0, import_node_fs13.writeFileSync)(outPath, file.contents.endsWith("\n") ? file.contents : `${file.contents}
|
|
10184
10324
|
`);
|
|
10185
10325
|
written.push(outPath);
|
|
10186
10326
|
}
|
|
@@ -10188,19 +10328,19 @@ function installPolicyPackToDir(dir, packId, force = false) {
|
|
|
10188
10328
|
return { dir: outDir, written, packs: packs.map((pack) => pack.id) };
|
|
10189
10329
|
}
|
|
10190
10330
|
function dashboardRegistryStatus(dir) {
|
|
10191
|
-
const identityPath = (0,
|
|
10192
|
-
const registryPath = (0,
|
|
10193
|
-
const verifierPath = (0,
|
|
10194
|
-
const identity = (0,
|
|
10331
|
+
const identityPath = (0, import_node_path9.join)(dir, ".protect-mcp-org.json");
|
|
10332
|
+
const registryPath = (0, import_node_path9.join)(dir, ".protect-mcp-registry.json");
|
|
10333
|
+
const verifierPath = (0, import_node_path9.join)(dir, "scopeblind-verifier.html");
|
|
10334
|
+
const identity = (0, import_node_fs13.existsSync)(identityPath) ? (() => {
|
|
10195
10335
|
try {
|
|
10196
|
-
return JSON.parse((0,
|
|
10336
|
+
return JSON.parse((0, import_node_fs13.readFileSync)(identityPath, "utf-8"));
|
|
10197
10337
|
} catch {
|
|
10198
10338
|
return null;
|
|
10199
10339
|
}
|
|
10200
10340
|
})() : null;
|
|
10201
|
-
const registry = (0,
|
|
10341
|
+
const registry = (0, import_node_fs13.existsSync)(registryPath) ? (() => {
|
|
10202
10342
|
try {
|
|
10203
|
-
return JSON.parse((0,
|
|
10343
|
+
return JSON.parse((0, import_node_fs13.readFileSync)(registryPath, "utf-8"));
|
|
10204
10344
|
} catch {
|
|
10205
10345
|
return null;
|
|
10206
10346
|
}
|
|
@@ -10208,9 +10348,9 @@ function dashboardRegistryStatus(dir) {
|
|
|
10208
10348
|
const anchors = Array.isArray(registry?.anchors) ? registry.anchors : [];
|
|
10209
10349
|
const hosted = anchors.some((anchor) => anchor.timestamp_source === "scopeblind-hosted");
|
|
10210
10350
|
return {
|
|
10211
|
-
identity_exists: (0,
|
|
10212
|
-
registry_exists: (0,
|
|
10213
|
-
verifier_exists: (0,
|
|
10351
|
+
identity_exists: (0, import_node_fs13.existsSync)(identityPath),
|
|
10352
|
+
registry_exists: (0, import_node_fs13.existsSync)(registryPath),
|
|
10353
|
+
verifier_exists: (0, import_node_fs13.existsSync)(verifierPath),
|
|
10214
10354
|
identity_path: identityPath,
|
|
10215
10355
|
registry_path: registryPath,
|
|
10216
10356
|
verifier_path: verifierPath,
|
|
@@ -10231,13 +10371,13 @@ async function readJsonBody(req) {
|
|
|
10231
10371
|
}
|
|
10232
10372
|
async function buildAuditBundleForDir(dir) {
|
|
10233
10373
|
const { createAuditBundle: createAuditBundle2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
|
|
10234
|
-
const receiptPath = (0,
|
|
10235
|
-
const keyPath = (0,
|
|
10236
|
-
if (!(0,
|
|
10237
|
-
if (!(0,
|
|
10374
|
+
const receiptPath = (0, import_node_path9.join)(dir, ".protect-mcp-receipts.jsonl");
|
|
10375
|
+
const keyPath = (0, import_node_path9.join)(dir, "keys", "gateway.json");
|
|
10376
|
+
if (!(0, import_node_fs13.existsSync)(receiptPath)) throw new Error("No receipt file found.");
|
|
10377
|
+
if (!(0, import_node_fs13.existsSync)(keyPath)) throw new Error("No signing key found.");
|
|
10238
10378
|
const receipts = parseJsonlFile(receiptPath);
|
|
10239
10379
|
if (receipts.length === 0) throw new Error("No signed receipts found.");
|
|
10240
|
-
const keyData = JSON.parse((0,
|
|
10380
|
+
const keyData = JSON.parse((0, import_node_fs13.readFileSync)(keyPath, "utf-8"));
|
|
10241
10381
|
return createAuditBundle2({
|
|
10242
10382
|
tenant: keyData.issuer || "protect-mcp",
|
|
10243
10383
|
receipts,
|
|
@@ -10255,17 +10395,17 @@ function collectSelectiveDisclosurePackages(dir) {
|
|
|
10255
10395
|
const out = [];
|
|
10256
10396
|
const seen = /* @__PURE__ */ new Set();
|
|
10257
10397
|
const candidates = [];
|
|
10258
|
-
const receiptsDir = (0,
|
|
10259
|
-
if ((0,
|
|
10260
|
-
for (const name of (0,
|
|
10398
|
+
const receiptsDir = (0, import_node_path9.join)(dir, "receipts");
|
|
10399
|
+
if ((0, import_node_fs13.existsSync)(receiptsDir)) {
|
|
10400
|
+
for (const name of (0, import_node_fs13.readdirSync)(receiptsDir)) {
|
|
10261
10401
|
if (name.includes("selective-disclosure") && name.endsWith(".json")) {
|
|
10262
|
-
candidates.push((0,
|
|
10402
|
+
candidates.push((0, import_node_path9.join)(receiptsDir, name));
|
|
10263
10403
|
}
|
|
10264
10404
|
}
|
|
10265
10405
|
}
|
|
10266
|
-
const jsonlPath = (0,
|
|
10267
|
-
if ((0,
|
|
10268
|
-
for (const line of (0,
|
|
10406
|
+
const jsonlPath = (0, import_node_path9.join)(dir, ".protect-mcp-selective-disclosures.jsonl");
|
|
10407
|
+
if ((0, import_node_fs13.existsSync)(jsonlPath)) {
|
|
10408
|
+
for (const line of (0, import_node_fs13.readFileSync)(jsonlPath, "utf-8").split("\n").map((s) => s.trim()).filter(Boolean)) {
|
|
10269
10409
|
try {
|
|
10270
10410
|
const parsed = JSON.parse(line);
|
|
10271
10411
|
addSelectiveDisclosure(out, seen, parsed);
|
|
@@ -10275,7 +10415,7 @@ function collectSelectiveDisclosurePackages(dir) {
|
|
|
10275
10415
|
}
|
|
10276
10416
|
for (const path of candidates) {
|
|
10277
10417
|
try {
|
|
10278
|
-
const parsed = JSON.parse((0,
|
|
10418
|
+
const parsed = JSON.parse((0, import_node_fs13.readFileSync)(path, "utf-8"));
|
|
10279
10419
|
addSelectiveDisclosure(out, seen, parsed);
|
|
10280
10420
|
} catch {
|
|
10281
10421
|
}
|
|
@@ -10308,7 +10448,7 @@ async function recordApprovalResolution(opts) {
|
|
|
10308
10448
|
takeover_note: opts.body.takeover_note || void 0,
|
|
10309
10449
|
payload_hash: opts.body.payload_hash || void 0
|
|
10310
10450
|
};
|
|
10311
|
-
(0,
|
|
10451
|
+
(0, import_node_fs13.appendFileSync)((0, import_node_path9.join)(opts.dir, ".protect-mcp-approval-resolutions.jsonl"), JSON.stringify(record) + "\n");
|
|
10312
10452
|
let forwarded = null;
|
|
10313
10453
|
if (resolution === "approve" && opts.approvalEndpoint && opts.approvalNonce) {
|
|
10314
10454
|
const endpoint = opts.approvalEndpoint.replace(/\/$/, "") + "/approve";
|
|
@@ -10331,7 +10471,7 @@ async function recordApprovalResolution(opts) {
|
|
|
10331
10471
|
return { recorded: true, resolution: record, forwarded };
|
|
10332
10472
|
}
|
|
10333
10473
|
async function handleRecommend(argv) {
|
|
10334
|
-
const { writeFileSync:
|
|
10474
|
+
const { writeFileSync: writeFileSync5 } = await import("fs");
|
|
10335
10475
|
const { resolve } = await import("path");
|
|
10336
10476
|
const dir = resolve(commandNeedsValue(argv, "--dir") ? flagValue(argv, "--dir") || process.cwd() : process.cwd());
|
|
10337
10477
|
const outputPath = resolve(flagValue(argv, "--output") || "protect-mcp.recommended.json");
|
|
@@ -10380,7 +10520,7 @@ Dry run only. Write the policy with:
|
|
|
10380
10520
|
process.stdout.write(dim(body));
|
|
10381
10521
|
return;
|
|
10382
10522
|
}
|
|
10383
|
-
|
|
10523
|
+
writeFileSync5(outputPath, body);
|
|
10384
10524
|
process.stdout.write(`
|
|
10385
10525
|
${green("\u2713 Wrote recommended policy")}
|
|
10386
10526
|
`);
|
|
@@ -10393,7 +10533,7 @@ ${green("\u2713 Wrote recommended policy")}
|
|
|
10393
10533
|
`);
|
|
10394
10534
|
}
|
|
10395
10535
|
async function handleWrap(argv) {
|
|
10396
|
-
const { existsSync:
|
|
10536
|
+
const { existsSync: existsSync10, readFileSync: readFileSync11, writeFileSync: writeFileSync5 } = await import("fs");
|
|
10397
10537
|
const { resolve } = await import("path");
|
|
10398
10538
|
const configFlag = flagValue(argv, "--config");
|
|
10399
10539
|
const cedarFlag = flagValue(argv, "--cedar");
|
|
@@ -10430,7 +10570,7 @@ ${bold("protect-mcp wrap")}
|
|
|
10430
10570
|
return;
|
|
10431
10571
|
}
|
|
10432
10572
|
const claudePath = resolve(flagValue(argv, "--path") || claudeDesktopConfigPath());
|
|
10433
|
-
if (!claudeDesktop && !
|
|
10573
|
+
if (!claudeDesktop && !existsSync10(claudePath)) {
|
|
10434
10574
|
process.stdout.write(`
|
|
10435
10575
|
${bold("protect-mcp wrap")}
|
|
10436
10576
|
|
|
@@ -10447,7 +10587,7 @@ ${bold("protect-mcp wrap")}
|
|
|
10447
10587
|
`);
|
|
10448
10588
|
return;
|
|
10449
10589
|
}
|
|
10450
|
-
if (!
|
|
10590
|
+
if (!existsSync10(claudePath)) {
|
|
10451
10591
|
process.stderr.write(`protect-mcp wrap: Claude Desktop config not found at ${claudePath}
|
|
10452
10592
|
`);
|
|
10453
10593
|
process.exit(1);
|
|
@@ -10518,8 +10658,8 @@ Dry run only. Apply with:
|
|
|
10518
10658
|
return;
|
|
10519
10659
|
}
|
|
10520
10660
|
const backupPath = `${claudePath}.bak.${Date.now()}`;
|
|
10521
|
-
|
|
10522
|
-
|
|
10661
|
+
writeFileSync5(backupPath, readFileSync11(claudePath, "utf-8"));
|
|
10662
|
+
writeFileSync5(claudePath, JSON.stringify(next, null, 2) + "\n");
|
|
10523
10663
|
process.stdout.write(`
|
|
10524
10664
|
${green("\u2713 Claude Desktop config updated")}
|
|
10525
10665
|
`);
|
|
@@ -10545,14 +10685,14 @@ function yellow(s) {
|
|
|
10545
10685
|
return process.env.NO_COLOR ? s : `\x1B[33m${s}\x1B[0m`;
|
|
10546
10686
|
}
|
|
10547
10687
|
async function handleDigest(argv) {
|
|
10548
|
-
const { readFileSync: readFileSync11, existsSync:
|
|
10549
|
-
const { join:
|
|
10688
|
+
const { readFileSync: readFileSync11, existsSync: existsSync10 } = await import("fs");
|
|
10689
|
+
const { join: join9 } = await import("path");
|
|
10550
10690
|
let dir = process.cwd();
|
|
10551
10691
|
const dirIdx = argv.indexOf("--dir");
|
|
10552
10692
|
if (dirIdx !== -1 && argv[dirIdx + 1]) dir = argv[dirIdx + 1];
|
|
10553
10693
|
const today = argv.includes("--today");
|
|
10554
|
-
const logPath =
|
|
10555
|
-
if (!
|
|
10694
|
+
const logPath = join9(dir, ".protect-mcp-log.jsonl");
|
|
10695
|
+
if (!existsSync10(logPath)) {
|
|
10556
10696
|
process.stderr.write(`${bold("protect-mcp digest")}
|
|
10557
10697
|
|
|
10558
10698
|
No log file found. Run protect-mcp first.
|
|
@@ -10636,15 +10776,15 @@ ${bold("\u{1F6E1}\uFE0F Agent Daily Digest")}
|
|
|
10636
10776
|
`);
|
|
10637
10777
|
}
|
|
10638
10778
|
async function handleReceipts2(argv) {
|
|
10639
|
-
const { readFileSync: readFileSync11, existsSync:
|
|
10640
|
-
const { join:
|
|
10779
|
+
const { readFileSync: readFileSync11, existsSync: existsSync10 } = await import("fs");
|
|
10780
|
+
const { join: join9 } = await import("path");
|
|
10641
10781
|
let dir = process.cwd();
|
|
10642
10782
|
const dirIdx = argv.indexOf("--dir");
|
|
10643
10783
|
if (dirIdx !== -1 && argv[dirIdx + 1]) dir = argv[dirIdx + 1];
|
|
10644
10784
|
const lastIdx = argv.indexOf("--last");
|
|
10645
10785
|
const count = lastIdx !== -1 && argv[lastIdx + 1] ? parseInt(argv[lastIdx + 1], 10) : 20;
|
|
10646
|
-
const receiptsPath =
|
|
10647
|
-
if (!
|
|
10786
|
+
const receiptsPath = join9(dir, ".protect-mcp-receipts.jsonl");
|
|
10787
|
+
if (!existsSync10(receiptsPath)) {
|
|
10648
10788
|
process.stderr.write(`${bold("protect-mcp receipts")}
|
|
10649
10789
|
|
|
10650
10790
|
No signed receipt file found. Run protect-mcp with signing enabled first.
|
|
@@ -10678,19 +10818,19 @@ async function pkgVersion() {
|
|
|
10678
10818
|
if (_pkgV) return _pkgV;
|
|
10679
10819
|
let v = "0.0.0";
|
|
10680
10820
|
try {
|
|
10681
|
-
const { readFileSync: readFileSync11, existsSync:
|
|
10682
|
-
const { dirname: dirname3, join:
|
|
10821
|
+
const { readFileSync: readFileSync11, existsSync: existsSync10, realpathSync } = await import("fs");
|
|
10822
|
+
const { dirname: dirname3, join: join9, resolve } = await import("path");
|
|
10683
10823
|
let base = "";
|
|
10684
10824
|
try {
|
|
10685
10825
|
base = dirname3(realpathSync(resolve(process.argv[1] || "")));
|
|
10686
10826
|
} catch {
|
|
10687
10827
|
}
|
|
10688
10828
|
const candidates = [
|
|
10689
|
-
base ?
|
|
10690
|
-
base ?
|
|
10829
|
+
base ? join9(base, "..", "package.json") : "",
|
|
10830
|
+
base ? join9(base, "package.json") : ""
|
|
10691
10831
|
].filter(Boolean);
|
|
10692
10832
|
for (const p of candidates) {
|
|
10693
|
-
if (
|
|
10833
|
+
if (existsSync10(p)) {
|
|
10694
10834
|
const parsed = JSON.parse(readFileSync11(p, "utf-8"));
|
|
10695
10835
|
if (parsed && parsed.name === "protect-mcp" && parsed.version) {
|
|
10696
10836
|
v = parsed.version;
|
|
@@ -10727,16 +10867,16 @@ function mapRecordEntry(e) {
|
|
|
10727
10867
|
return { ts, tool, verdict, reason, hook, signed, caps, agent, dur, id: String(e.request_id || p.request_id || ""), digest, raw: e };
|
|
10728
10868
|
}
|
|
10729
10869
|
async function handleRecord(argv) {
|
|
10730
|
-
const { readFileSync: readFileSync11, existsSync:
|
|
10731
|
-
const { join:
|
|
10870
|
+
const { readFileSync: readFileSync11, existsSync: existsSync10, writeFileSync: writeFileSync5 } = await import("fs");
|
|
10871
|
+
const { join: join9 } = await import("path");
|
|
10732
10872
|
const osMod = await import("os");
|
|
10733
10873
|
const cp = await import("child_process");
|
|
10734
10874
|
let dir = process.cwd();
|
|
10735
10875
|
const di = argv.indexOf("--dir");
|
|
10736
10876
|
if (di !== -1 && argv[di + 1]) dir = argv[di + 1];
|
|
10737
|
-
const recPath =
|
|
10738
|
-
const logPath =
|
|
10739
|
-
const pick = () =>
|
|
10877
|
+
const recPath = join9(dir, ".protect-mcp-receipts.jsonl");
|
|
10878
|
+
const logPath = join9(dir, ".protect-mcp-log.jsonl");
|
|
10879
|
+
const pick = () => existsSync10(recPath) ? recPath : existsSync10(logPath) ? logPath : null;
|
|
10740
10880
|
const chosen = pick();
|
|
10741
10881
|
if (!chosen) {
|
|
10742
10882
|
process.stderr.write(`
|
|
@@ -10761,7 +10901,7 @@ Start the gate with ${bold("npx protect-mcp serve")}, use your agent, then run t
|
|
|
10761
10901
|
let pinnedKey = "";
|
|
10762
10902
|
let pinnedKid = "";
|
|
10763
10903
|
try {
|
|
10764
|
-
const kd = JSON.parse(readFileSync11(
|
|
10904
|
+
const kd = JSON.parse(readFileSync11(join9(dir, "keys", "gateway.json"), "utf-8"));
|
|
10765
10905
|
if (kd && typeof kd.publicKey === "string" && /^[0-9a-f]{64}$/i.test(kd.publicKey)) {
|
|
10766
10906
|
pinnedKey = kd.publicKey;
|
|
10767
10907
|
pinnedKid = typeof kd.kid === "string" ? kd.kid : "";
|
|
@@ -10822,8 +10962,8 @@ ${bold("\u{1F6E1}\uFE0F Your record")} ${dim("\xB7")} live at ${url}
|
|
|
10822
10962
|
const recs = readRecs(chosen);
|
|
10823
10963
|
const meta = { file: chosen, signed: chosen === recPath, count: recs.length, live: false, pinned_key: pinnedKey, pinned_kid: pinnedKid };
|
|
10824
10964
|
const html = RECORD_HTML.replace("__DATA__", () => JSON.stringify(recs)).replace("__META__", () => JSON.stringify(meta));
|
|
10825
|
-
const out =
|
|
10826
|
-
|
|
10965
|
+
const out = join9(osMod.tmpdir(), "protect-mcp-record-" + Date.now() + ".html");
|
|
10966
|
+
writeFileSync5(out, html);
|
|
10827
10967
|
openTarget(out);
|
|
10828
10968
|
process.stdout.write(`
|
|
10829
10969
|
${bold("\u{1F6E1}\uFE0F Your record")} ${dim("\xB7")} ${recs.length} decision${recs.length === 1 ? "" : "s"}, all on this machine
|
|
@@ -10996,8 +11136,8 @@ render();kickVerify();
|
|
|
10996
11136
|
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);}
|
|
10997
11137
|
</script></body></html>`;
|
|
10998
11138
|
async function handleClaim(argv) {
|
|
10999
|
-
const { readFileSync: readFileSync11, existsSync:
|
|
11000
|
-
const { join:
|
|
11139
|
+
const { readFileSync: readFileSync11, existsSync: existsSync10, writeFileSync: writeFileSync5 } = await import("fs");
|
|
11140
|
+
const { join: join9 } = await import("path");
|
|
11001
11141
|
const { buildClaim: buildClaim2 } = await Promise.resolve().then(() => (init_claim(), claim_exports));
|
|
11002
11142
|
let dir = process.cwd();
|
|
11003
11143
|
const di = argv.indexOf("--dir");
|
|
@@ -11030,8 +11170,8 @@ Example: ${bold("npx protect-mcp claim --no net.egress --anchor")}
|
|
|
11030
11170
|
process.exit(0);
|
|
11031
11171
|
return;
|
|
11032
11172
|
}
|
|
11033
|
-
const keyPath =
|
|
11034
|
-
if (!
|
|
11173
|
+
const keyPath = join9(dir, "keys", "gateway.json");
|
|
11174
|
+
if (!existsSync10(keyPath)) {
|
|
11035
11175
|
process.stderr.write(`
|
|
11036
11176
|
${bold("protect-mcp claim")}
|
|
11037
11177
|
|
|
@@ -11060,8 +11200,8 @@ protect-mcp claim: ${keyPath} is missing privateKey/publicKey.
|
|
|
11060
11200
|
process.exit(1);
|
|
11061
11201
|
return;
|
|
11062
11202
|
}
|
|
11063
|
-
const recPath =
|
|
11064
|
-
if (!
|
|
11203
|
+
const recPath = join9(dir, ".protect-mcp-receipts.jsonl");
|
|
11204
|
+
if (!existsSync10(recPath)) {
|
|
11065
11205
|
process.stderr.write(`
|
|
11066
11206
|
${bold("protect-mcp claim")}
|
|
11067
11207
|
|
|
@@ -11088,8 +11228,8 @@ protect-mcp claim: no readable receipts in ${recPath}.
|
|
|
11088
11228
|
}
|
|
11089
11229
|
const pack = buildClaim2(receipts, predicate, { privateKey: key.privateKey, publicKey: key.publicKey, kid: key.kid || "gateway", issuer: "protect-mcp" }, (/* @__PURE__ */ new Date()).toISOString());
|
|
11090
11230
|
const oi = argv.indexOf("--output");
|
|
11091
|
-
const out = oi !== -1 && argv[oi + 1] ? argv[oi + 1] :
|
|
11092
|
-
|
|
11231
|
+
const out = oi !== -1 && argv[oi + 1] ? argv[oi + 1] : join9(dir, "claim-" + Date.now() + ".json");
|
|
11232
|
+
writeFileSync5(out, JSON.stringify(pack, null, 2) + "\n");
|
|
11093
11233
|
process.stdout.write(`
|
|
11094
11234
|
${bold("\u{1F6E1}\uFE0F Signed claim")}
|
|
11095
11235
|
`);
|
|
@@ -11115,7 +11255,7 @@ ${bold("\u{1F6E1}\uFE0F Signed claim")}
|
|
|
11115
11255
|
);
|
|
11116
11256
|
if (res.ok) {
|
|
11117
11257
|
const sidecar = out.replace(/\.json$/, "") + ".anchor.json";
|
|
11118
|
-
|
|
11258
|
+
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");
|
|
11119
11259
|
process.stdout.write(` ${green("Anchored")} as log entry ${bold("#" + res.seq)}${res.already_anchored ? dim(" (already present)") : ""} ${dim(res.entry_url || "")}
|
|
11120
11260
|
`);
|
|
11121
11261
|
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.")}
|
|
@@ -11144,16 +11284,16 @@ ${bold("\u{1F6E1}\uFE0F Signed claim")}
|
|
|
11144
11284
|
process.exit(0);
|
|
11145
11285
|
}
|
|
11146
11286
|
async function handleAnchorRecord(argv) {
|
|
11147
|
-
const { readFileSync: readFileSync11, existsSync:
|
|
11148
|
-
const { join:
|
|
11287
|
+
const { readFileSync: readFileSync11, existsSync: existsSync10, appendFileSync: appendFileSync3 } = await import("fs");
|
|
11288
|
+
const { join: join9 } = await import("path");
|
|
11149
11289
|
const { anchorRecordCheckpoint: anchorRecordCheckpoint2, buildRecordCheckpoint: buildRecordCheckpoint2, lookupPinnedIdentity: lookupPinnedIdentity2 } = await Promise.resolve().then(() => (init_claim(), claim_exports));
|
|
11150
11290
|
let dir = process.cwd();
|
|
11151
11291
|
const di = argv.indexOf("--dir");
|
|
11152
11292
|
if (di !== -1 && argv[di + 1]) dir = argv[di + 1];
|
|
11153
11293
|
const li = argv.indexOf("--log");
|
|
11154
11294
|
const logBase = li !== -1 && argv[li + 1] ? argv[li + 1] : void 0;
|
|
11155
|
-
const keyPath =
|
|
11156
|
-
if (!
|
|
11295
|
+
const keyPath = join9(dir, "keys", "gateway.json");
|
|
11296
|
+
if (!existsSync10(keyPath)) {
|
|
11157
11297
|
process.stderr.write(`
|
|
11158
11298
|
${bold("protect-mcp anchor-record")}
|
|
11159
11299
|
|
|
@@ -11182,8 +11322,8 @@ protect-mcp anchor-record: ${keyPath} is missing privateKey/publicKey.
|
|
|
11182
11322
|
process.exit(1);
|
|
11183
11323
|
return;
|
|
11184
11324
|
}
|
|
11185
|
-
const recPath =
|
|
11186
|
-
if (!
|
|
11325
|
+
const recPath = join9(dir, ".protect-mcp-receipts.jsonl");
|
|
11326
|
+
if (!existsSync10(recPath)) {
|
|
11187
11327
|
process.stderr.write(`
|
|
11188
11328
|
${bold("protect-mcp anchor-record")}
|
|
11189
11329
|
|
|
@@ -11209,9 +11349,9 @@ protect-mcp anchor-record: no readable receipts in ${recPath}.
|
|
|
11209
11349
|
return;
|
|
11210
11350
|
}
|
|
11211
11351
|
const claimKey = { privateKey: key.privateKey, publicKey: key.publicKey, kid: key.kid || "gateway", issuer: "protect-mcp" };
|
|
11212
|
-
const historyPath =
|
|
11352
|
+
const historyPath = join9(dir, ".protect-mcp-anchors.jsonl");
|
|
11213
11353
|
const preview = buildRecordCheckpoint2(receipts, claimKey, "preview");
|
|
11214
|
-
if (!argv.includes("--force") &&
|
|
11354
|
+
if (!argv.includes("--force") && existsSync10(historyPath)) {
|
|
11215
11355
|
const lines = readFileSync11(historyPath, "utf-8").split(/\r?\n/).filter(Boolean);
|
|
11216
11356
|
const last = lines.length ? (() => {
|
|
11217
11357
|
try {
|
|
@@ -11270,10 +11410,10 @@ ${bold("\u{1F6E1}\uFE0F Record checkpoint")}
|
|
|
11270
11410
|
process.exit(0);
|
|
11271
11411
|
}
|
|
11272
11412
|
async function handleVerifyClaim(argv) {
|
|
11273
|
-
const { readFileSync: readFileSync11, existsSync:
|
|
11413
|
+
const { readFileSync: readFileSync11, existsSync: existsSync10 } = await import("fs");
|
|
11274
11414
|
const { verifyClaim: verifyClaim2 } = await Promise.resolve().then(() => (init_claim(), claim_exports));
|
|
11275
11415
|
const file = argv.find((a) => !a.startsWith("--"));
|
|
11276
|
-
if (!file || !
|
|
11416
|
+
if (!file || !existsSync10(file)) {
|
|
11277
11417
|
process.stderr.write(`
|
|
11278
11418
|
${bold("protect-mcp verify-claim")} <claim.json> [--key <public-hex>]
|
|
11279
11419
|
|
|
@@ -11322,7 +11462,7 @@ ${bold("protect-mcp verify-claim")}
|
|
|
11322
11462
|
const sidecarPath = ai !== -1 && argv[ai + 1] ? argv[ai + 1] : file.replace(/\.json$/, "") + ".anchor.json";
|
|
11323
11463
|
const requireAnchor = argv.includes("--check-anchor");
|
|
11324
11464
|
let anchorOk = true;
|
|
11325
|
-
if (
|
|
11465
|
+
if (existsSync10(sidecarPath)) {
|
|
11326
11466
|
const { checkClaimAnchor: checkClaimAnchor2 } = await Promise.resolve().then(() => (init_claim(), claim_exports));
|
|
11327
11467
|
let sidecar = null;
|
|
11328
11468
|
try {
|
|
@@ -11393,24 +11533,24 @@ ${bold("protect-mcp verify-claim")}
|
|
|
11393
11533
|
process.exit(finalValid ? 0 : 1);
|
|
11394
11534
|
}
|
|
11395
11535
|
async function handleBundle(argv) {
|
|
11396
|
-
const { readFileSync: readFileSync11, writeFileSync:
|
|
11397
|
-
const { join:
|
|
11536
|
+
const { readFileSync: readFileSync11, writeFileSync: writeFileSync5, existsSync: existsSync10 } = await import("fs");
|
|
11537
|
+
const { join: join9 } = await import("path");
|
|
11398
11538
|
const { createAuditBundle: createAuditBundle2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
|
|
11399
11539
|
let dir = process.cwd();
|
|
11400
11540
|
const dirIdx = argv.indexOf("--dir");
|
|
11401
11541
|
if (dirIdx !== -1 && argv[dirIdx + 1]) dir = argv[dirIdx + 1];
|
|
11402
11542
|
const outputIdx = argv.indexOf("--output");
|
|
11403
|
-
const outputPath = outputIdx !== -1 && argv[outputIdx + 1] ? argv[outputIdx + 1] :
|
|
11404
|
-
const receiptsPath =
|
|
11405
|
-
const keyPath =
|
|
11406
|
-
if (!
|
|
11543
|
+
const outputPath = outputIdx !== -1 && argv[outputIdx + 1] ? argv[outputIdx + 1] : join9(dir, "audit-bundle.json");
|
|
11544
|
+
const receiptsPath = join9(dir, ".protect-mcp-receipts.jsonl");
|
|
11545
|
+
const keyPath = join9(dir, "keys", "gateway.json");
|
|
11546
|
+
if (!existsSync10(receiptsPath)) {
|
|
11407
11547
|
process.stderr.write(`${bold("protect-mcp bundle")}
|
|
11408
11548
|
|
|
11409
11549
|
No signed receipt file found. Run protect-mcp with signing enabled first.
|
|
11410
11550
|
`);
|
|
11411
11551
|
process.exit(0);
|
|
11412
11552
|
}
|
|
11413
|
-
if (!
|
|
11553
|
+
if (!existsSync10(keyPath)) {
|
|
11414
11554
|
process.stderr.write(`${bold("protect-mcp bundle")}
|
|
11415
11555
|
|
|
11416
11556
|
No key file found at ${keyPath}
|
|
@@ -11431,7 +11571,7 @@ No key file found at ${keyPath}
|
|
|
11431
11571
|
use: "sig"
|
|
11432
11572
|
}]
|
|
11433
11573
|
});
|
|
11434
|
-
|
|
11574
|
+
writeFileSync5(outputPath, JSON.stringify(bundle, null, 2) + "\n");
|
|
11435
11575
|
process.stdout.write(`
|
|
11436
11576
|
${bold("protect-mcp bundle")}
|
|
11437
11577
|
|
|
@@ -11447,8 +11587,8 @@ ${bold("protect-mcp bundle")}
|
|
|
11447
11587
|
`);
|
|
11448
11588
|
}
|
|
11449
11589
|
async function createSandbox() {
|
|
11450
|
-
const { mkdirSync:
|
|
11451
|
-
const { join:
|
|
11590
|
+
const { mkdirSync: mkdirSync4, writeFileSync: writeFileSync5, existsSync: existsSync10, readFileSync: readFileSync11 } = await import("fs");
|
|
11591
|
+
const { join: join9 } = await import("path");
|
|
11452
11592
|
const { homedir } = await import("os");
|
|
11453
11593
|
let response;
|
|
11454
11594
|
try {
|
|
@@ -11478,19 +11618,19 @@ async function createSandbox() {
|
|
|
11478
11618
|
return null;
|
|
11479
11619
|
}
|
|
11480
11620
|
const dashboardUrl = `https://scopeblind.com/t/${data.slug}`;
|
|
11481
|
-
const configDir =
|
|
11482
|
-
if (!
|
|
11483
|
-
|
|
11621
|
+
const configDir = join9(homedir(), ".protect-mcp");
|
|
11622
|
+
if (!existsSync10(configDir)) {
|
|
11623
|
+
mkdirSync4(configDir, { recursive: true });
|
|
11484
11624
|
}
|
|
11485
|
-
const configPath =
|
|
11625
|
+
const configPath = join9(configDir, "config.json");
|
|
11486
11626
|
let existing = {};
|
|
11487
|
-
if (
|
|
11627
|
+
if (existsSync10(configPath)) {
|
|
11488
11628
|
try {
|
|
11489
11629
|
existing = JSON.parse(readFileSync11(configPath, "utf-8"));
|
|
11490
11630
|
} catch {
|
|
11491
11631
|
}
|
|
11492
11632
|
}
|
|
11493
|
-
|
|
11633
|
+
writeFileSync5(configPath, JSON.stringify({
|
|
11494
11634
|
...existing,
|
|
11495
11635
|
sandbox_slug: data.slug,
|
|
11496
11636
|
dashboard_url: dashboardUrl
|
|
@@ -11523,10 +11663,10 @@ ${"\u2500".repeat(50)}
|
|
|
11523
11663
|
}
|
|
11524
11664
|
async function handleQuickstart(argv) {
|
|
11525
11665
|
const connectFlag = argv.includes("--connect");
|
|
11526
|
-
const { mkdtempSync, writeFileSync:
|
|
11527
|
-
const { join:
|
|
11666
|
+
const { mkdtempSync, writeFileSync: writeFileSync5, existsSync: existsSync10, mkdirSync: mkdirSync4, readFileSync: readFileSync11 } = await import("fs");
|
|
11667
|
+
const { join: join9 } = await import("path");
|
|
11528
11668
|
const { tmpdir } = await import("os");
|
|
11529
|
-
const dir = mkdtempSync(
|
|
11669
|
+
const dir = mkdtempSync(join9(tmpdir(), "protect-mcp-quickstart-"));
|
|
11530
11670
|
process.stdout.write(`
|
|
11531
11671
|
${bold("protect-mcp quickstart")}
|
|
11532
11672
|
`);
|
|
@@ -11551,8 +11691,8 @@ ${bold("protect-mcp quickstart")}
|
|
|
11551
11691
|
Working dir: ${dir}
|
|
11552
11692
|
|
|
11553
11693
|
`);
|
|
11554
|
-
const keysDir =
|
|
11555
|
-
|
|
11694
|
+
const keysDir = join9(dir, "keys");
|
|
11695
|
+
mkdirSync4(keysDir, { recursive: true });
|
|
11556
11696
|
const { randomBytes: randomBytes4 } = await import("crypto");
|
|
11557
11697
|
let keypair;
|
|
11558
11698
|
try {
|
|
@@ -11572,13 +11712,13 @@ ${bold("protect-mcp quickstart")}
|
|
|
11572
11712
|
kid: `quickstart-${Date.now()}`
|
|
11573
11713
|
};
|
|
11574
11714
|
}
|
|
11575
|
-
|
|
11715
|
+
writeFileSync5(join9(keysDir, "gateway.json"), JSON.stringify({
|
|
11576
11716
|
privateKey: keypair.privateKey,
|
|
11577
11717
|
publicKey: keypair.publicKey,
|
|
11578
11718
|
kid: keypair.kid,
|
|
11579
11719
|
generated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
11580
11720
|
}, null, 2) + "\n");
|
|
11581
|
-
const configPath =
|
|
11721
|
+
const configPath = join9(dir, "protect-mcp.json");
|
|
11582
11722
|
const config = {
|
|
11583
11723
|
tools: {
|
|
11584
11724
|
"*": { rate_limit: "100/hour" },
|
|
@@ -11586,12 +11726,12 @@ ${bold("protect-mcp quickstart")}
|
|
|
11586
11726
|
},
|
|
11587
11727
|
default_tier: "unknown",
|
|
11588
11728
|
signing: {
|
|
11589
|
-
key_path:
|
|
11729
|
+
key_path: join9(keysDir, "gateway.json"),
|
|
11590
11730
|
issuer: "protect-mcp-quickstart",
|
|
11591
11731
|
enabled: true
|
|
11592
11732
|
}
|
|
11593
11733
|
};
|
|
11594
|
-
|
|
11734
|
+
writeFileSync5(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
11595
11735
|
process.stdout.write(` \u2713 Keypair generated (kid: ${keypair.kid})
|
|
11596
11736
|
`);
|
|
11597
11737
|
process.stdout.write(` \u2713 Policy created (shadow mode, all tools logged)
|
|
@@ -11606,7 +11746,7 @@ ${bold("protect-mcp quickstart")}
|
|
|
11606
11746
|
const dashboardUrl = await createSandbox();
|
|
11607
11747
|
if (dashboardUrl) {
|
|
11608
11748
|
const updatedConfig = { ...config, dashboard_url: dashboardUrl };
|
|
11609
|
-
|
|
11749
|
+
writeFileSync5(configPath, JSON.stringify(updatedConfig, null, 2) + "\n");
|
|
11610
11750
|
process.stdout.write(green(` \u2713 Dashboard created: ${dashboardUrl}
|
|
11611
11751
|
`));
|
|
11612
11752
|
process.stdout.write(` Receipts will be uploaded automatically.
|
|
@@ -11642,7 +11782,7 @@ ${bold("protect-mcp quickstart")}
|
|
|
11642
11782
|
}
|
|
11643
11783
|
async function handleRegistry(argv) {
|
|
11644
11784
|
const subcommand = argv[0] || "status";
|
|
11645
|
-
const dir = (0,
|
|
11785
|
+
const dir = (0, import_node_path9.resolve)(flagValue(argv, "--dir") || process.cwd());
|
|
11646
11786
|
const orgName = flagValue(argv, "--org") || process.env.SCOPEBLIND_ORG;
|
|
11647
11787
|
const orgId = flagValue(argv, "--org-id") || process.env.SCOPEBLIND_ORG_ID;
|
|
11648
11788
|
const billingAccountId = flagValue(argv, "--billing-account") || process.env.SCOPEBLIND_BILLING_ACCOUNT;
|
|
@@ -11724,14 +11864,14 @@ ${bold("protect-mcp registry anchor")}
|
|
|
11724
11864
|
return;
|
|
11725
11865
|
}
|
|
11726
11866
|
if (subcommand === "status") {
|
|
11727
|
-
const registryPath = (0,
|
|
11728
|
-
const identityPath = (0,
|
|
11867
|
+
const registryPath = (0, import_node_path9.join)(dir, registryMod.REGISTRY_FILE);
|
|
11868
|
+
const identityPath = (0, import_node_path9.join)(dir, registryMod.ORG_IDENTITY_FILE);
|
|
11729
11869
|
process.stdout.write(`
|
|
11730
11870
|
${bold("protect-mcp registry status")}
|
|
11731
11871
|
|
|
11732
11872
|
`);
|
|
11733
|
-
if ((0,
|
|
11734
|
-
const identity = JSON.parse((0,
|
|
11873
|
+
if ((0, import_node_fs13.existsSync)(identityPath)) {
|
|
11874
|
+
const identity = JSON.parse((0, import_node_fs13.readFileSync)(identityPath, "utf-8"));
|
|
11735
11875
|
process.stdout.write(` Org: ${identity.org_name || "unknown"}
|
|
11736
11876
|
`);
|
|
11737
11877
|
process.stdout.write(` Org ID: ${identity.org_id || "unknown"}
|
|
@@ -11742,8 +11882,8 @@ ${bold("protect-mcp registry status")}
|
|
|
11742
11882
|
process.stdout.write(` Org identity: ${yellow("missing")} (${identityPath})
|
|
11743
11883
|
`);
|
|
11744
11884
|
}
|
|
11745
|
-
if ((0,
|
|
11746
|
-
const registry = JSON.parse((0,
|
|
11885
|
+
if ((0, import_node_fs13.existsSync)(registryPath)) {
|
|
11886
|
+
const registry = JSON.parse((0, import_node_fs13.readFileSync)(registryPath, "utf-8"));
|
|
11747
11887
|
const hosted = Array.isArray(registry.anchors) && registry.anchors.some((a) => a.timestamp_source === "scopeblind-hosted");
|
|
11748
11888
|
process.stdout.write(` Registry: ${registryPath}
|
|
11749
11889
|
`);
|
|
@@ -11753,7 +11893,7 @@ ${bold("protect-mcp registry status")}
|
|
|
11753
11893
|
`);
|
|
11754
11894
|
process.stdout.write(` Boundary: ${hosted ? green("hosted digest anchor") : yellow("local preview only")}
|
|
11755
11895
|
`);
|
|
11756
|
-
process.stdout.write(` Verifier page: ${(0,
|
|
11896
|
+
process.stdout.write(` Verifier page: ${(0, import_node_path9.join)(dir, registryMod.VERIFIER_PAGE_FILE)}
|
|
11757
11897
|
`);
|
|
11758
11898
|
} else {
|
|
11759
11899
|
process.stdout.write(` Registry: ${yellow("missing")} (${registryPath})
|
|
@@ -11781,10 +11921,10 @@ async function handleKillerDemo(argv) {
|
|
|
11781
11921
|
verifySelectiveDisclosurePackage: verifySelectiveDisclosurePackage2
|
|
11782
11922
|
} = await Promise.resolve().then(() => (init_signing_committed(), signing_committed_exports));
|
|
11783
11923
|
const registryMod = await Promise.resolve().then(() => (init_receipt_registry(), receipt_registry_exports));
|
|
11784
|
-
const dir = (0,
|
|
11785
|
-
(0,
|
|
11786
|
-
(0,
|
|
11787
|
-
(0,
|
|
11924
|
+
const dir = (0, import_node_path9.resolve)(flagValue(argv, "--dir") || mkdtempSync((0, import_node_path9.join)(tmpdir(), "scopeblind-killer-demo-")));
|
|
11925
|
+
(0, import_node_fs13.mkdirSync)(dir, { recursive: true });
|
|
11926
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path9.join)(dir, "keys"), { recursive: true });
|
|
11927
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path9.join)(dir, "receipts"), { recursive: true });
|
|
11788
11928
|
const privateKeyBytes = randomBytes4(32);
|
|
11789
11929
|
const publicKeyBytes = ed255192.getPublicKey(privateKeyBytes);
|
|
11790
11930
|
const keypair = {
|
|
@@ -11793,14 +11933,14 @@ async function handleKillerDemo(argv) {
|
|
|
11793
11933
|
kid: `killer-demo-${Date.now()}`,
|
|
11794
11934
|
issuer: "scopeblind-killer-demo"
|
|
11795
11935
|
};
|
|
11796
|
-
const keyPath = (0,
|
|
11797
|
-
(0,
|
|
11936
|
+
const keyPath = (0, import_node_path9.join)(dir, "keys", "gateway.json");
|
|
11937
|
+
(0, import_node_fs13.writeFileSync)(keyPath, JSON.stringify({
|
|
11798
11938
|
...keypair,
|
|
11799
11939
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11800
11940
|
warning: "Demo key only. Do not use for production."
|
|
11801
11941
|
}, null, 2) + "\n");
|
|
11802
|
-
const shadowConfigPath = (0,
|
|
11803
|
-
const policyPackPath = (0,
|
|
11942
|
+
const shadowConfigPath = (0, import_node_path9.join)(dir, "protect-mcp.shadow.json");
|
|
11943
|
+
const policyPackPath = (0, import_node_path9.join)(dir, "protect-mcp.policy-pack.json");
|
|
11804
11944
|
const config = {
|
|
11805
11945
|
tools: { "*": { rate_limit: "100/hour" } },
|
|
11806
11946
|
default_tier: "signed-known",
|
|
@@ -11819,11 +11959,11 @@ async function handleKillerDemo(argv) {
|
|
|
11819
11959
|
signing: { key_path: keyPath, issuer: keypair.issuer, enabled: true },
|
|
11820
11960
|
notes: ["Demo policy pack: approvals for GitHub, email, and PMS booking; destructive tools blocked."]
|
|
11821
11961
|
};
|
|
11822
|
-
(0,
|
|
11823
|
-
(0,
|
|
11962
|
+
(0, import_node_fs13.writeFileSync)(shadowConfigPath, JSON.stringify(config, null, 2) + "\n");
|
|
11963
|
+
(0, import_node_fs13.writeFileSync)(policyPackPath, JSON.stringify(policyPack, null, 2) + "\n");
|
|
11824
11964
|
await initSigning({ enabled: true, key_path: keyPath, issuer: keypair.issuer });
|
|
11825
|
-
const logPath = (0,
|
|
11826
|
-
const receiptPath = (0,
|
|
11965
|
+
const logPath = (0, import_node_path9.join)(dir, ".protect-mcp-log.jsonl");
|
|
11966
|
+
const receiptPath = (0, import_node_path9.join)(dir, ".protect-mcp-receipts.jsonl");
|
|
11827
11967
|
const shadowCalls = [
|
|
11828
11968
|
{ tool: "read_file", input: { path: "/research/macro-notes.md" }, reason: "observe_mode" },
|
|
11829
11969
|
{ tool: "github_create_pr", input: { repo: "scopeblind/legate", branch: "agent/pms-adapter", title: "Wire mock PMS adapter" }, reason: "observe_mode" },
|
|
@@ -11832,7 +11972,7 @@ async function handleKillerDemo(argv) {
|
|
|
11832
11972
|
];
|
|
11833
11973
|
for (const [idx, call] of shadowCalls.entries()) {
|
|
11834
11974
|
const requestId2 = `demo-shadow-${idx + 1}`;
|
|
11835
|
-
(0,
|
|
11975
|
+
(0, import_node_fs13.appendFileSync)(logPath, JSON.stringify({
|
|
11836
11976
|
v: 2,
|
|
11837
11977
|
tool: call.tool,
|
|
11838
11978
|
decision: "allow",
|
|
@@ -11867,8 +12007,8 @@ async function handleKillerDemo(argv) {
|
|
|
11867
12007
|
policy_digest: (0, import_node_crypto7.createHash)("sha256").update(JSON.stringify(policyPack)).digest("hex").slice(0, 16),
|
|
11868
12008
|
action_readback: readback
|
|
11869
12009
|
};
|
|
11870
|
-
(0,
|
|
11871
|
-
(0,
|
|
12010
|
+
(0, import_node_fs13.appendFileSync)(logPath, JSON.stringify(requireApprovalEntry) + "\n");
|
|
12011
|
+
(0, import_node_fs13.appendFileSync)((0, import_node_path9.join)(dir, ".protect-mcp-approval-resolutions.jsonl"), JSON.stringify({
|
|
11872
12012
|
type: "scopeblind.approval_resolution.v1",
|
|
11873
12013
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11874
12014
|
request_id: requestId,
|
|
@@ -11888,11 +12028,11 @@ async function handleKillerDemo(argv) {
|
|
|
11888
12028
|
truncated: false
|
|
11889
12029
|
}
|
|
11890
12030
|
};
|
|
11891
|
-
(0,
|
|
12031
|
+
(0, import_node_fs13.appendFileSync)(logPath, JSON.stringify(executedEntry) + "\n");
|
|
11892
12032
|
const signed = signDecision(executedEntry);
|
|
11893
12033
|
if (!signed.signed) throw new Error(`demo signing failed: ${signed.warning || signed.error || "unknown"}`);
|
|
11894
|
-
(0,
|
|
11895
|
-
(0,
|
|
12034
|
+
(0, import_node_fs13.appendFileSync)(receiptPath, signed.signed + "\n");
|
|
12035
|
+
(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");
|
|
11896
12036
|
const receiptArtifact = JSON.parse(signed.signed);
|
|
11897
12037
|
const tamperedArtifact = JSON.parse(signed.signed);
|
|
11898
12038
|
if (tamperedArtifact.payload && typeof tamperedArtifact.payload === "object") {
|
|
@@ -11903,7 +12043,7 @@ async function handleKillerDemo(argv) {
|
|
|
11903
12043
|
}
|
|
11904
12044
|
const validOriginal = artifacts.verifyArtifact(receiptArtifact, keypair.publicKey);
|
|
11905
12045
|
const validTampered = artifacts.verifyArtifact(tamperedArtifact, keypair.publicKey);
|
|
11906
|
-
(0,
|
|
12046
|
+
(0, import_node_fs13.writeFileSync)((0, import_node_path9.join)(dir, "receipts", "tampered.receipt.json"), JSON.stringify(tamperedArtifact, null, 2) + "\n");
|
|
11907
12047
|
const committed = signCommittedDecision2(
|
|
11908
12048
|
executedEntry,
|
|
11909
12049
|
["tool", "payload_digest", "swarm"],
|
|
@@ -11915,11 +12055,11 @@ async function handleKillerDemo(argv) {
|
|
|
11915
12055
|
const committedReceipt = JSON.parse(committed.signed);
|
|
11916
12056
|
const disclosurePackage = createSelectiveDisclosurePackage2(committedReceipt, ["tool"], committed.openings);
|
|
11917
12057
|
const disclosureVerification = verifySelectiveDisclosurePackage2(committedReceipt, disclosurePackage);
|
|
11918
|
-
(0,
|
|
11919
|
-
(0,
|
|
11920
|
-
(0,
|
|
11921
|
-
(0,
|
|
11922
|
-
(0,
|
|
12058
|
+
(0, import_node_fs13.appendFileSync)(receiptPath, committed.signed + "\n");
|
|
12059
|
+
(0, import_node_fs13.writeFileSync)((0, import_node_path9.join)(dir, "receipts", "selective-disclosure.receipt.json"), JSON.stringify(committedReceipt, null, 2) + "\n");
|
|
12060
|
+
(0, import_node_fs13.writeFileSync)((0, import_node_path9.join)(dir, "receipts", "selective-disclosure.package.json"), JSON.stringify(disclosurePackage, null, 2) + "\n");
|
|
12061
|
+
(0, import_node_fs13.writeFileSync)((0, import_node_path9.join)(dir, "receipts", "selective-disclosure.tool-only.json"), JSON.stringify(disclosurePackage, null, 2) + "\n");
|
|
12062
|
+
(0, import_node_fs13.writeFileSync)((0, import_node_path9.join)(dir, "verification-results.json"), JSON.stringify({
|
|
11923
12063
|
original_receipt_valid: validOriginal,
|
|
11924
12064
|
tampered_receipt_valid: validTampered,
|
|
11925
12065
|
selective_disclosure_valid: disclosureVerification.valid,
|
|
@@ -12002,19 +12142,19 @@ async function handleKillerDemo(argv) {
|
|
|
12002
12142
|
"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.",
|
|
12003
12143
|
""
|
|
12004
12144
|
].join("\n");
|
|
12005
|
-
(0,
|
|
12006
|
-
(0,
|
|
12145
|
+
(0, import_node_fs13.writeFileSync)((0, import_node_path9.join)(dir, "DEMO-RUNBOOK.md"), runbook);
|
|
12146
|
+
(0, import_node_fs13.writeFileSync)((0, import_node_path9.join)(dir, "demo-summary.json"), JSON.stringify({
|
|
12007
12147
|
dir,
|
|
12008
12148
|
dashboard_command: `npx protect-mcp dashboard --dir ${dir} --policy ${policyPackPath} --open`,
|
|
12009
12149
|
policy_pack: policyPackPath,
|
|
12010
|
-
receipt: (0,
|
|
12011
|
-
tampered_receipt: (0,
|
|
12012
|
-
selective_disclosure_receipt: (0,
|
|
12013
|
-
selective_disclosure_package: (0,
|
|
12014
|
-
verification_results: (0,
|
|
12150
|
+
receipt: (0, import_node_path9.join)(dir, "receipts", "approved-pms-booking.receipt.json"),
|
|
12151
|
+
tampered_receipt: (0, import_node_path9.join)(dir, "receipts", "tampered.receipt.json"),
|
|
12152
|
+
selective_disclosure_receipt: (0, import_node_path9.join)(dir, "receipts", "selective-disclosure.receipt.json"),
|
|
12153
|
+
selective_disclosure_package: (0, import_node_path9.join)(dir, "receipts", "selective-disclosure.tool-only.json"),
|
|
12154
|
+
verification_results: (0, import_node_path9.join)(dir, "verification-results.json"),
|
|
12015
12155
|
registry: registry.registryPath,
|
|
12016
12156
|
verifier_page: registry.verifierPath,
|
|
12017
|
-
runbook: (0,
|
|
12157
|
+
runbook: (0, import_node_path9.join)(dir, "DEMO-RUNBOOK.md"),
|
|
12018
12158
|
original_valid: validOriginal.valid,
|
|
12019
12159
|
tampered_valid: validTampered.valid,
|
|
12020
12160
|
selective_disclosure_valid: disclosureVerification.valid
|
|
@@ -12027,9 +12167,9 @@ ${bold("protect-mcp killer-demo")}
|
|
|
12027
12167
|
`);
|
|
12028
12168
|
process.stdout.write(` Dashboard: ${dim(`npx protect-mcp dashboard --dir ${dir} --policy ${policyPackPath} --open`)}
|
|
12029
12169
|
`);
|
|
12030
|
-
process.stdout.write(` Runbook: ${(0,
|
|
12170
|
+
process.stdout.write(` Runbook: ${(0, import_node_path9.join)(dir, "DEMO-RUNBOOK.md")}
|
|
12031
12171
|
`);
|
|
12032
|
-
process.stdout.write(` Signed receipt: ${(0,
|
|
12172
|
+
process.stdout.write(` Signed receipt: ${(0, import_node_path9.join)(dir, "receipts", "approved-pms-booking.receipt.json")}
|
|
12033
12173
|
`);
|
|
12034
12174
|
process.stdout.write(` Tamper check: original=${validOriginal.valid ? green("valid") : red("invalid")} tampered=${validTampered.valid ? red("valid") : green("invalid")}
|
|
12035
12175
|
`);
|
|
@@ -12049,8 +12189,8 @@ async function handleVerifyDisclosure(argv) {
|
|
|
12049
12189
|
process.exit(1);
|
|
12050
12190
|
}
|
|
12051
12191
|
const { verifySelectiveDisclosurePackage: verifySelectiveDisclosurePackage2 } = await Promise.resolve().then(() => (init_signing_committed(), signing_committed_exports));
|
|
12052
|
-
const receipt = JSON.parse((0,
|
|
12053
|
-
const disclosure = JSON.parse((0,
|
|
12192
|
+
const receipt = JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path9.resolve)(receiptPath), "utf-8"));
|
|
12193
|
+
const disclosure = JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path9.resolve)(disclosurePath), "utf-8"));
|
|
12054
12194
|
const result = verifySelectiveDisclosurePackage2(receipt, disclosure);
|
|
12055
12195
|
process.stdout.write(`
|
|
12056
12196
|
${bold("protect-mcp verify-disclosure")}
|
|
@@ -12086,7 +12226,7 @@ ${red("Errors:")}
|
|
|
12086
12226
|
async function handlePolicyPacks(argv) {
|
|
12087
12227
|
const subcommand = argv[0] || "list";
|
|
12088
12228
|
const packArg = argv[1];
|
|
12089
|
-
const dir = (0,
|
|
12229
|
+
const dir = (0, import_node_path9.resolve)(flagValue(argv, "--dir") || "./cedar");
|
|
12090
12230
|
const force = argv.includes("--force");
|
|
12091
12231
|
if (subcommand === "list") {
|
|
12092
12232
|
process.stdout.write(`
|
|
@@ -12141,17 +12281,17 @@ ${bold(pack.name)} (${pack.id})
|
|
|
12141
12281
|
`);
|
|
12142
12282
|
process.exit(1);
|
|
12143
12283
|
}
|
|
12144
|
-
(0,
|
|
12284
|
+
(0, import_node_fs13.mkdirSync)(dir, { recursive: true });
|
|
12145
12285
|
const written = [];
|
|
12146
12286
|
for (const pack of packs) {
|
|
12147
12287
|
for (const file of pack.files) {
|
|
12148
|
-
const outPath = (0,
|
|
12149
|
-
if ((0,
|
|
12288
|
+
const outPath = (0, import_node_path9.join)(dir, file.path);
|
|
12289
|
+
if ((0, import_node_fs13.existsSync)(outPath) && !force) {
|
|
12150
12290
|
process.stderr.write(`Refusing to overwrite ${outPath}. Re-run with --force if intentional.
|
|
12151
12291
|
`);
|
|
12152
12292
|
process.exit(1);
|
|
12153
12293
|
}
|
|
12154
|
-
(0,
|
|
12294
|
+
(0, import_node_fs13.writeFileSync)(outPath, file.contents.endsWith("\n") ? file.contents : `${file.contents}
|
|
12155
12295
|
`);
|
|
12156
12296
|
written.push(outPath);
|
|
12157
12297
|
}
|
|
@@ -12176,7 +12316,7 @@ Next: ${dim(`protect-mcp serve --cedar ${dir}`)} for shadow mode, then add ${dim
|
|
|
12176
12316
|
async function handleConnectors(argv) {
|
|
12177
12317
|
const subcommand = argv[0] || "list";
|
|
12178
12318
|
const pilotArg = argv[1];
|
|
12179
|
-
const dir = (0,
|
|
12319
|
+
const dir = (0, import_node_path9.resolve)(flagValue(argv, "--dir") || process.cwd());
|
|
12180
12320
|
const force = argv.includes("--force");
|
|
12181
12321
|
if (subcommand === "list") {
|
|
12182
12322
|
process.stdout.write(`
|
|
@@ -12418,11 +12558,11 @@ ${"\u2500".repeat(60)}
|
|
|
12418
12558
|
`);
|
|
12419
12559
|
}
|
|
12420
12560
|
async function traceLocal(receiptId) {
|
|
12421
|
-
const { readFileSync: readFileSync11, existsSync:
|
|
12422
|
-
const { join:
|
|
12561
|
+
const { readFileSync: readFileSync11, existsSync: existsSync10 } = await import("fs");
|
|
12562
|
+
const { join: join9 } = await import("path");
|
|
12423
12563
|
const dir = process.cwd();
|
|
12424
|
-
const receiptsDir =
|
|
12425
|
-
if (!
|
|
12564
|
+
const receiptsDir = join9(dir, ".protect-mcp", "receipts");
|
|
12565
|
+
if (!existsSync10(receiptsDir)) {
|
|
12426
12566
|
process.stdout.write(` No local receipts found in ${receiptsDir}
|
|
12427
12567
|
|
|
12428
12568
|
`);
|
|
@@ -12436,7 +12576,7 @@ async function traceLocal(receiptId) {
|
|
|
12436
12576
|
const receipts = [];
|
|
12437
12577
|
for (const file of files) {
|
|
12438
12578
|
try {
|
|
12439
|
-
const content = readFileSync11(
|
|
12579
|
+
const content = readFileSync11(join9(receiptsDir, file), "utf-8");
|
|
12440
12580
|
const receipt = JSON.parse(content);
|
|
12441
12581
|
receipts.push(receipt);
|
|
12442
12582
|
} catch {
|
|
@@ -12493,8 +12633,8 @@ function getTypeEmoji(type) {
|
|
|
12493
12633
|
}
|
|
12494
12634
|
}
|
|
12495
12635
|
async function handleInitHooks(argv) {
|
|
12496
|
-
const { writeFileSync:
|
|
12497
|
-
const { join:
|
|
12636
|
+
const { writeFileSync: writeFileSync5, existsSync: existsSync10, mkdirSync: mkdirSync4, readFileSync: readFileSync11 } = await import("fs");
|
|
12637
|
+
const { join: join9 } = await import("path");
|
|
12498
12638
|
const { generateHookSettings: generateHookSettings2, generateSampleCedarPolicy: generateSampleCedarPolicy2, generateVerifyReceiptSkill: generateVerifyReceiptSkill2 } = await Promise.resolve().then(() => (init_hook_patterns(), hook_patterns_exports));
|
|
12499
12639
|
let dir = process.cwd();
|
|
12500
12640
|
const dirIdx = argv.indexOf("--dir");
|
|
@@ -12508,13 +12648,13 @@ ${bold("protect-mcp init-hooks")}
|
|
|
12508
12648
|
process.stdout.write(`${"\u2500".repeat(55)}
|
|
12509
12649
|
|
|
12510
12650
|
`);
|
|
12511
|
-
const claudeDir =
|
|
12512
|
-
const settingsPath =
|
|
12651
|
+
const claudeDir = join9(dir, ".claude");
|
|
12652
|
+
const settingsPath = join9(claudeDir, "settings.json");
|
|
12513
12653
|
let existingSettings = {};
|
|
12514
|
-
if (!
|
|
12515
|
-
|
|
12654
|
+
if (!existsSync10(claudeDir)) {
|
|
12655
|
+
mkdirSync4(claudeDir, { recursive: true });
|
|
12516
12656
|
}
|
|
12517
|
-
if (
|
|
12657
|
+
if (existsSync10(settingsPath)) {
|
|
12518
12658
|
try {
|
|
12519
12659
|
existingSettings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
|
|
12520
12660
|
} catch {
|
|
@@ -12530,7 +12670,7 @@ ${bold("protect-mcp init-hooks")}
|
|
|
12530
12670
|
...hookSettings.hooks
|
|
12531
12671
|
}
|
|
12532
12672
|
};
|
|
12533
|
-
|
|
12673
|
+
writeFileSync5(settingsPath, JSON.stringify(mergedSettings, null, 2) + "\n");
|
|
12534
12674
|
process.stdout.write(` ${green("\u2713")} ${settingsPath}
|
|
12535
12675
|
`);
|
|
12536
12676
|
process.stdout.write(` Hook URL: ${dim(hookUrl)}
|
|
@@ -12538,26 +12678,26 @@ ${bold("protect-mcp init-hooks")}
|
|
|
12538
12678
|
process.stdout.write(` Events: PreToolUse, PostToolUse, SubagentStart/Stop, Task, Session, Config, Stop
|
|
12539
12679
|
|
|
12540
12680
|
`);
|
|
12541
|
-
const keysDir =
|
|
12542
|
-
const keyPath =
|
|
12543
|
-
if (!
|
|
12544
|
-
if (!
|
|
12681
|
+
const keysDir = join9(dir, "keys");
|
|
12682
|
+
const keyPath = join9(keysDir, "gateway.json");
|
|
12683
|
+
if (!existsSync10(keyPath)) {
|
|
12684
|
+
if (!existsSync10(keysDir)) mkdirSync4(keysDir, { recursive: true });
|
|
12545
12685
|
const { randomBytes: rb } = await import("crypto");
|
|
12546
12686
|
try {
|
|
12547
12687
|
const { ed25519: ed255192 } = await Promise.resolve().then(() => (init_ed25519(), ed25519_exports));
|
|
12548
12688
|
const { bytesToHex: bytesToHex2 } = await Promise.resolve().then(() => (init_utils(), utils_exports));
|
|
12549
12689
|
const privateKey = rb(32);
|
|
12550
12690
|
const publicKey = ed255192.getPublicKey(privateKey);
|
|
12551
|
-
|
|
12691
|
+
writeFileSync5(keyPath, JSON.stringify({
|
|
12552
12692
|
privateKey: bytesToHex2(privateKey),
|
|
12553
12693
|
publicKey: bytesToHex2(publicKey),
|
|
12554
12694
|
kid: `hook-${Date.now()}`,
|
|
12555
12695
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12556
12696
|
warning: "KEEP THIS FILE SECRET. Never commit to version control."
|
|
12557
12697
|
}, null, 2) + "\n");
|
|
12558
|
-
const gitignorePath =
|
|
12559
|
-
if (!
|
|
12560
|
-
|
|
12698
|
+
const gitignorePath = join9(keysDir, ".gitignore");
|
|
12699
|
+
if (!existsSync10(gitignorePath)) {
|
|
12700
|
+
writeFileSync5(gitignorePath, "# Never commit signing keys\n*.json\n");
|
|
12561
12701
|
}
|
|
12562
12702
|
process.stdout.write(` ${green("\u2713")} ${keyPath} (Ed25519 keypair)
|
|
12563
12703
|
|
|
@@ -12572,11 +12712,11 @@ ${bold("protect-mcp init-hooks")}
|
|
|
12572
12712
|
|
|
12573
12713
|
`);
|
|
12574
12714
|
}
|
|
12575
|
-
const policiesDir =
|
|
12576
|
-
const cedarPath =
|
|
12577
|
-
if (!
|
|
12578
|
-
if (!
|
|
12579
|
-
|
|
12715
|
+
const policiesDir = join9(dir, "policies");
|
|
12716
|
+
const cedarPath = join9(policiesDir, "agent.cedar");
|
|
12717
|
+
if (!existsSync10(cedarPath)) {
|
|
12718
|
+
if (!existsSync10(policiesDir)) mkdirSync4(policiesDir, { recursive: true });
|
|
12719
|
+
writeFileSync5(cedarPath, generateSampleCedarPolicy2());
|
|
12580
12720
|
process.stdout.write(` ${green("\u2713")} ${cedarPath}
|
|
12581
12721
|
`);
|
|
12582
12722
|
process.stdout.write(` Edit to customize tool permissions. Cedar deny is AUTHORITATIVE.
|
|
@@ -12587,8 +12727,8 @@ ${bold("protect-mcp init-hooks")}
|
|
|
12587
12727
|
|
|
12588
12728
|
`);
|
|
12589
12729
|
}
|
|
12590
|
-
const configPath =
|
|
12591
|
-
if (!
|
|
12730
|
+
const configPath = join9(dir, "protect-mcp.json");
|
|
12731
|
+
if (!existsSync10(configPath)) {
|
|
12592
12732
|
const config = {
|
|
12593
12733
|
tools: { "*": { rate_limit: "100/hour" } },
|
|
12594
12734
|
default_tier: "unknown",
|
|
@@ -12598,16 +12738,16 @@ ${bold("protect-mcp init-hooks")}
|
|
|
12598
12738
|
enabled: true
|
|
12599
12739
|
}
|
|
12600
12740
|
};
|
|
12601
|
-
|
|
12741
|
+
writeFileSync5(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
12602
12742
|
process.stdout.write(` ${green("\u2713")} ${configPath}
|
|
12603
12743
|
|
|
12604
12744
|
`);
|
|
12605
12745
|
}
|
|
12606
|
-
const skillsDir =
|
|
12607
|
-
const skillPath =
|
|
12608
|
-
if (!
|
|
12609
|
-
|
|
12610
|
-
|
|
12746
|
+
const skillsDir = join9(dir, ".claude", "skills", "verify-receipt");
|
|
12747
|
+
const skillPath = join9(skillsDir, "SKILL.md");
|
|
12748
|
+
if (!existsSync10(skillPath)) {
|
|
12749
|
+
mkdirSync4(skillsDir, { recursive: true });
|
|
12750
|
+
writeFileSync5(skillPath, generateVerifyReceiptSkill2());
|
|
12611
12751
|
process.stdout.write(` ${green("\u2713")} ${skillPath}
|
|
12612
12752
|
`);
|
|
12613
12753
|
process.stdout.write(` Use ${dim("/verify-receipt")} in Claude Code to check audit trails.
|
|
@@ -12663,13 +12803,13 @@ ${bold("protect-mcp init-hooks")}
|
|
|
12663
12803
|
}
|
|
12664
12804
|
async function sendInstallTelemetry() {
|
|
12665
12805
|
try {
|
|
12666
|
-
const { existsSync:
|
|
12667
|
-
const { join:
|
|
12806
|
+
const { existsSync: existsSync10, mkdirSync: mkdirSync4, writeFileSync: writeFileSync5, readFileSync: readFileSync11 } = await import("fs");
|
|
12807
|
+
const { join: join9, dirname: dirname3 } = await import("path");
|
|
12668
12808
|
const { homedir } = await import("os");
|
|
12669
12809
|
const { fileURLToPath } = await import("url");
|
|
12670
|
-
const markerDir =
|
|
12671
|
-
const markerFile =
|
|
12672
|
-
if (
|
|
12810
|
+
const markerDir = join9(homedir(), ".protect-mcp");
|
|
12811
|
+
const markerFile = join9(markerDir, ".telemetry-sent");
|
|
12812
|
+
if (existsSync10(markerFile) || process.env.PROTECT_MCP_TELEMETRY === "off") {
|
|
12673
12813
|
return;
|
|
12674
12814
|
}
|
|
12675
12815
|
const version = await pkgVersion();
|
|
@@ -12689,10 +12829,10 @@ async function sendInstallTelemetry() {
|
|
|
12689
12829
|
signal: controller.signal
|
|
12690
12830
|
}).catch(() => {
|
|
12691
12831
|
}).finally(() => clearTimeout(timeout));
|
|
12692
|
-
if (!
|
|
12693
|
-
|
|
12832
|
+
if (!existsSync10(markerDir)) {
|
|
12833
|
+
mkdirSync4(markerDir, { recursive: true });
|
|
12694
12834
|
}
|
|
12695
|
-
|
|
12835
|
+
writeFileSync5(markerFile, String(Date.now()), "utf-8");
|
|
12696
12836
|
process.stderr.write(
|
|
12697
12837
|
"[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"
|
|
12698
12838
|
);
|
|
@@ -12708,8 +12848,8 @@ function loadPolicyArg(argv) {
|
|
|
12708
12848
|
const policyFile = flagValue(argv, "--policy");
|
|
12709
12849
|
try {
|
|
12710
12850
|
if (cedarDir) return loadCedarPolicies(cedarDir);
|
|
12711
|
-
if (policyFile && (0,
|
|
12712
|
-
return policySetFromSource((0,
|
|
12851
|
+
if (policyFile && (0, import_node_fs13.existsSync)(policyFile)) {
|
|
12852
|
+
return policySetFromSource((0, import_node_fs13.readFileSync)(policyFile, "utf-8"), (0, import_node_path9.basename)(policyFile));
|
|
12713
12853
|
}
|
|
12714
12854
|
} catch {
|
|
12715
12855
|
}
|
|
@@ -12810,7 +12950,7 @@ async function handleSign(argv) {
|
|
|
12810
12950
|
if (m.tool) tool = m.tool;
|
|
12811
12951
|
}
|
|
12812
12952
|
}
|
|
12813
|
-
if (keyPath && (0,
|
|
12953
|
+
if (keyPath && (0, import_node_fs13.existsSync)(keyPath)) {
|
|
12814
12954
|
try {
|
|
12815
12955
|
await initSigning({ enabled: true, key_path: keyPath });
|
|
12816
12956
|
} catch {
|
|
@@ -12827,12 +12967,12 @@ async function handleSign(argv) {
|
|
|
12827
12967
|
timestamp: Date.now()
|
|
12828
12968
|
});
|
|
12829
12969
|
try {
|
|
12830
|
-
(0,
|
|
12970
|
+
(0, import_node_fs13.mkdirSync)(receiptsDir, { recursive: true });
|
|
12831
12971
|
} catch {
|
|
12832
12972
|
}
|
|
12833
12973
|
const line = signed.signed ?? JSON.stringify({ tool, request_id: requestId, signed: false, note: signed.warning || "no signer configured" });
|
|
12834
12974
|
try {
|
|
12835
|
-
(0,
|
|
12975
|
+
(0, import_node_fs13.appendFileSync)((0, import_node_path9.join)(receiptsDir, "receipts.jsonl"), line + "\n");
|
|
12836
12976
|
} catch {
|
|
12837
12977
|
}
|
|
12838
12978
|
if (format === "hermes") {
|
|
@@ -12842,12 +12982,200 @@ async function handleSign(argv) {
|
|
|
12842
12982
|
process.stdout.write(JSON.stringify({ signed: Boolean(signed.signed), artifact_type: signed.artifact_type, request_id: requestId }) + "\n");
|
|
12843
12983
|
process.exit(0);
|
|
12844
12984
|
}
|
|
12985
|
+
async function handleSample(argv) {
|
|
12986
|
+
const dir = flagValue(argv, "--dir") || process.cwd();
|
|
12987
|
+
const { buildSampleKit: buildSampleKit2 } = await Promise.resolve().then(() => (init_sample(), sample_exports));
|
|
12988
|
+
let kit;
|
|
12989
|
+
try {
|
|
12990
|
+
kit = buildSampleKit2(dir, { force: argv.includes("--force") });
|
|
12991
|
+
} catch (err) {
|
|
12992
|
+
if (err?.code === "SAMPLE_EXISTS") {
|
|
12993
|
+
process.stderr.write(
|
|
12994
|
+
"\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"
|
|
12995
|
+
);
|
|
12996
|
+
process.exit(1);
|
|
12997
|
+
}
|
|
12998
|
+
throw err;
|
|
12999
|
+
}
|
|
13000
|
+
process.stdout.write(`
|
|
13001
|
+
${bold("\u{1F6E1} Sample record seeded")} \xB7 8 decisions (1 blocked, 2 payments), signed with a fresh key ${dim(`(kid ${kit.kid})`)}
|
|
13002
|
+
|
|
13003
|
+
`);
|
|
13004
|
+
process.stdout.write(" .protect-mcp-receipts.jsonl the signed sample record\n");
|
|
13005
|
+
process.stdout.write(" demo-tampered.jsonl the same record with ONE decision edited after signing\n");
|
|
13006
|
+
process.stdout.write(` keys/gateway.json sample keypair ${dim("(never commit)")}
|
|
13007
|
+
|
|
13008
|
+
`);
|
|
13009
|
+
process.stdout.write(`${bold("Replay the demo")} ${dim("(the film: legate.scopeblind.com/record)")}
|
|
13010
|
+
`);
|
|
13011
|
+
process.stdout.write(" npx protect-mcp record\n");
|
|
13012
|
+
process.stdout.write(" npx protect-mcp claim --payment-under 100 --anchor --output payments-under-100.json\n");
|
|
13013
|
+
process.stdout.write(" npx protect-mcp verify-claim payments-under-100.json\n");
|
|
13014
|
+
process.stdout.write(" npx protect-mcp anchor-record\n\n");
|
|
13015
|
+
process.stdout.write(`${dim("Drop demo-tampered.jsonl into the record page to watch tampering get caught.")}
|
|
13016
|
+
`);
|
|
13017
|
+
process.stdout.write(`${dim("Everything runs locally; --anchor publishes only a digest to the public log.")}
|
|
13018
|
+
|
|
13019
|
+
`);
|
|
13020
|
+
process.exit(0);
|
|
13021
|
+
}
|
|
13022
|
+
async function handlePolicy(argv) {
|
|
13023
|
+
const { readFileSync: rf, writeFileSync: wf, existsSync: ex, readdirSync: rd, appendFileSync: af } = await import("fs");
|
|
13024
|
+
const { join: pj } = await import("path");
|
|
13025
|
+
const { createHash: createHash6 } = await import("crypto");
|
|
13026
|
+
const sub = argv[0] || "list";
|
|
13027
|
+
const findDir = () => {
|
|
13028
|
+
for (const c of ["cedar", "policies", "."]) {
|
|
13029
|
+
try {
|
|
13030
|
+
if (ex(c) && rd(c).some((f) => f.endsWith(".cedar"))) return c;
|
|
13031
|
+
} catch {
|
|
13032
|
+
}
|
|
13033
|
+
}
|
|
13034
|
+
return null;
|
|
13035
|
+
};
|
|
13036
|
+
const dir = findDir();
|
|
13037
|
+
const cedarFiles = dir ? rd(dir).filter((f) => f.endsWith(".cedar")).sort() : [];
|
|
13038
|
+
const readAll = () => cedarFiles.map((f) => rf(pj(dir, f), "utf-8")).join("\n\n");
|
|
13039
|
+
const digestOf = (src) => createHash6("sha256").update(src).digest("hex").slice(0, 16);
|
|
13040
|
+
const stripComments = (src) => src.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "");
|
|
13041
|
+
const targetFile = cedarFiles.includes("agent.cedar") ? "agent.cedar" : cedarFiles[0];
|
|
13042
|
+
if (sub === "path") {
|
|
13043
|
+
if (!dir) {
|
|
13044
|
+
process.stdout.write("No Cedar policy directory found (looked in ./cedar, ./policies, .).\n");
|
|
13045
|
+
process.exit(0);
|
|
13046
|
+
}
|
|
13047
|
+
process.stdout.write(`${pj(dir, targetFile)}
|
|
13048
|
+
`);
|
|
13049
|
+
process.exit(0);
|
|
13050
|
+
}
|
|
13051
|
+
if (sub === "show") {
|
|
13052
|
+
if (!dir) {
|
|
13053
|
+
process.stderr.write("No Cedar policy found. Run: npx protect-mcp init-hooks\n");
|
|
13054
|
+
process.exit(1);
|
|
13055
|
+
}
|
|
13056
|
+
const src = readAll();
|
|
13057
|
+
process.stdout.write(`${bold("Cedar policy")} ${dim(`(${cedarFiles.length} file${cedarFiles.length === 1 ? "" : "s"} in ${dir}, digest ${digestOf(src)})`)}
|
|
13058
|
+
|
|
13059
|
+
`);
|
|
13060
|
+
process.stdout.write(src.endsWith("\n") ? src : src + "\n");
|
|
13061
|
+
process.exit(0);
|
|
13062
|
+
}
|
|
13063
|
+
if (sub === "allow" || sub === "deny") {
|
|
13064
|
+
const tool = argv[1];
|
|
13065
|
+
if (!tool) {
|
|
13066
|
+
process.stderr.write(`Usage: npx protect-mcp policy ${sub} <ToolName>
|
|
13067
|
+
`);
|
|
13068
|
+
process.exit(1);
|
|
13069
|
+
}
|
|
13070
|
+
if (!/^[A-Za-z0-9_.:-]+$/.test(tool)) {
|
|
13071
|
+
process.stderr.write(`Refusing: "${tool}" is not a valid tool name.
|
|
13072
|
+
`);
|
|
13073
|
+
process.exit(1);
|
|
13074
|
+
}
|
|
13075
|
+
if (!dir) {
|
|
13076
|
+
process.stderr.write("No Cedar policy found. Run: npx protect-mcp init-hooks\n");
|
|
13077
|
+
process.exit(1);
|
|
13078
|
+
}
|
|
13079
|
+
const effect = sub === "allow" ? "permit" : "forbid";
|
|
13080
|
+
const before = readAll();
|
|
13081
|
+
const ruleRe = new RegExp(`${effect}\\s*\\([^)]*resource\\s*==\\s*Tool::"${tool.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")}"`, "s");
|
|
13082
|
+
if (ruleRe.test(stripComments(before))) {
|
|
13083
|
+
process.stdout.write(`${dim("No change:")} a ${effect} rule for ${bold(tool)} already exists in ${targetFile}.
|
|
13084
|
+
`);
|
|
13085
|
+
process.exit(0);
|
|
13086
|
+
}
|
|
13087
|
+
const block = `
|
|
13088
|
+
// ${effect === "permit" ? "Allow" : "Block"} ${tool} (added by \`protect-mcp policy ${sub}\`)
|
|
13089
|
+
${effect}(
|
|
13090
|
+
principal,
|
|
13091
|
+
action == Action::"MCP::Tool::call",
|
|
13092
|
+
resource == Tool::"${tool}"
|
|
13093
|
+
);
|
|
13094
|
+
`;
|
|
13095
|
+
af(pj(dir, targetFile), block);
|
|
13096
|
+
const after = readAll();
|
|
13097
|
+
process.stdout.write(`${bold(sub === "allow" ? "\u2713 Allowed" : "\u2713 Denied")} ${tool}
|
|
13098
|
+
`);
|
|
13099
|
+
process.stdout.write(` ${dim(`appended to ${pj(dir, targetFile)}`)}
|
|
13100
|
+
`);
|
|
13101
|
+
process.stdout.write(` ${dim(`policy digest ${digestOf(before)} \u2192 ${digestOf(after)}`)}
|
|
13102
|
+
`);
|
|
13103
|
+
process.stdout.write(`
|
|
13104
|
+
${dim("A running gate (protect-mcp serve) hot-reloads on this change; no restart needed. The one-shot hook path re-reads per call.")}
|
|
13105
|
+
`);
|
|
13106
|
+
process.exit(0);
|
|
13107
|
+
}
|
|
13108
|
+
if (sub === "list") {
|
|
13109
|
+
if (!dir) {
|
|
13110
|
+
process.stderr.write("No Cedar policy found. Run: npx protect-mcp init-hooks\n");
|
|
13111
|
+
process.exit(1);
|
|
13112
|
+
}
|
|
13113
|
+
const src = readAll();
|
|
13114
|
+
const active = stripComments(src);
|
|
13115
|
+
const named = (effect) => {
|
|
13116
|
+
const set = /* @__PURE__ */ new Set();
|
|
13117
|
+
const re = new RegExp(`${effect}\\s*\\([\\s\\S]*?resource\\s*==\\s*Tool::"([^"]+)"[\\s\\S]*?\\);`, "g");
|
|
13118
|
+
let m;
|
|
13119
|
+
while (m = re.exec(active)) set.add(m[1]);
|
|
13120
|
+
return set;
|
|
13121
|
+
};
|
|
13122
|
+
const permitted = named("permit"), forbidden = named("forbid");
|
|
13123
|
+
const seen = /* @__PURE__ */ new Map();
|
|
13124
|
+
try {
|
|
13125
|
+
const logPath = pj(process.cwd(), ".protect-mcp-log.jsonl");
|
|
13126
|
+
if (ex(logPath)) {
|
|
13127
|
+
for (const line of rf(logPath, "utf-8").split("\n")) {
|
|
13128
|
+
if (!line.trim()) continue;
|
|
13129
|
+
try {
|
|
13130
|
+
const e = JSON.parse(line);
|
|
13131
|
+
if (!e.tool) continue;
|
|
13132
|
+
const rec = seen.get(e.tool) || { allow: 0, deny: 0 };
|
|
13133
|
+
if (e.decision === "deny") rec.deny++;
|
|
13134
|
+
else rec.allow++;
|
|
13135
|
+
seen.set(e.tool, rec);
|
|
13136
|
+
} catch {
|
|
13137
|
+
}
|
|
13138
|
+
}
|
|
13139
|
+
}
|
|
13140
|
+
} catch {
|
|
13141
|
+
}
|
|
13142
|
+
process.stdout.write(`${bold("Cedar policy")} ${dim(`(${dir}, digest ${digestOf(src)}) \xB7 default-deny, fail-closed`)}
|
|
13143
|
+
|
|
13144
|
+
`);
|
|
13145
|
+
const allTools = /* @__PURE__ */ new Set([...permitted, ...forbidden, ...seen.keys()]);
|
|
13146
|
+
if (allTools.size === 0) {
|
|
13147
|
+
process.stdout.write(dim(" No rules and no decisions logged yet.\n"));
|
|
13148
|
+
process.exit(0);
|
|
13149
|
+
}
|
|
13150
|
+
const rows = [...allTools].sort().map((t) => {
|
|
13151
|
+
const label = forbidden.has(t) ? "forbid" : permitted.has(t) ? "permit" : "default-deny";
|
|
13152
|
+
const colored = forbidden.has(t) ? red(label) : permitted.has(t) ? green(label) : yellow(label);
|
|
13153
|
+
const pad = " ".repeat(Math.max(1, 13 - label.length));
|
|
13154
|
+
const s = seen.get(t);
|
|
13155
|
+
const hits = s ? dim(` ${s.allow} allowed, ${s.deny} denied`) : "";
|
|
13156
|
+
return ` ${colored}${pad}${bold(t)}${hits}`;
|
|
13157
|
+
});
|
|
13158
|
+
process.stdout.write(rows.join("\n") + "\n");
|
|
13159
|
+
process.stdout.write(`
|
|
13160
|
+
${dim("Allow a tool: npx protect-mcp policy allow <ToolName> \xB7 Block one: policy deny <ToolName>")}
|
|
13161
|
+
`);
|
|
13162
|
+
process.exit(0);
|
|
13163
|
+
}
|
|
13164
|
+
process.stderr.write("Usage: npx protect-mcp policy <list|show|allow <tool>|deny <tool>|path>\n");
|
|
13165
|
+
process.exit(1);
|
|
13166
|
+
}
|
|
12845
13167
|
async function main() {
|
|
12846
13168
|
sendInstallTelemetry().catch(() => {
|
|
12847
13169
|
});
|
|
12848
13170
|
const args = process.argv.slice(2);
|
|
12849
13171
|
process.env.PROTECT_MCP_VERSION = process.env.PROTECT_MCP_VERSION || await pkgVersion();
|
|
12850
|
-
|
|
13172
|
+
const preSep = args.includes("--") ? args.slice(0, args.indexOf("--")) : args;
|
|
13173
|
+
if (args[0] === "version" || preSep.includes("--version") || preSep.includes("-V")) {
|
|
13174
|
+
process.stdout.write(`${process.env.PROTECT_MCP_VERSION || "unknown"}
|
|
13175
|
+
`);
|
|
13176
|
+
process.exit(0);
|
|
13177
|
+
}
|
|
13178
|
+
if (args.length === 0 || args[0] === "help" || preSep.includes("--help") || preSep.includes("-h")) {
|
|
12851
13179
|
printHelp();
|
|
12852
13180
|
process.exit(0);
|
|
12853
13181
|
}
|
|
@@ -12901,6 +13229,14 @@ async function main() {
|
|
|
12901
13229
|
await handleAnchorRecord(args.slice(1));
|
|
12902
13230
|
return;
|
|
12903
13231
|
}
|
|
13232
|
+
if (args[0] === "sample") {
|
|
13233
|
+
await handleSample(args.slice(1));
|
|
13234
|
+
return;
|
|
13235
|
+
}
|
|
13236
|
+
if (args[0] === "policy") {
|
|
13237
|
+
await handlePolicy(args.slice(1));
|
|
13238
|
+
return;
|
|
13239
|
+
}
|
|
12904
13240
|
if (args[0] === "init-hooks") {
|
|
12905
13241
|
await handleInitHooks(args.slice(1));
|
|
12906
13242
|
process.exit(0);
|
|
@@ -13005,10 +13341,10 @@ async function main() {
|
|
|
13005
13341
|
let cedarPolicySet = null;
|
|
13006
13342
|
let effectiveCedarDir = cedarDir;
|
|
13007
13343
|
if (!effectiveCedarDir && !policyPath) {
|
|
13008
|
-
const { existsSync:
|
|
13344
|
+
const { existsSync: existsSync10, readdirSync: readdirSync4 } = await import("fs");
|
|
13009
13345
|
for (const candidate of ["cedar", "policies", "."]) {
|
|
13010
13346
|
try {
|
|
13011
|
-
if (
|
|
13347
|
+
if (existsSync10(candidate) && readdirSync4(candidate).some((f) => f.endsWith(".cedar"))) {
|
|
13012
13348
|
effectiveCedarDir = candidate;
|
|
13013
13349
|
process.stderr.write(`[PROTECT_MCP] Auto-detected Cedar policies in ./${candidate}/
|
|
13014
13350
|
`);
|
|
@@ -13119,8 +13455,8 @@ async function handleSimulate(args) {
|
|
|
13119
13455
|
process.stderr.write("Usage: protect-mcp simulate --policy <path> [--log <path>] [--tier <tier>] [--json]\n");
|
|
13120
13456
|
process.exit(1);
|
|
13121
13457
|
}
|
|
13122
|
-
const { existsSync:
|
|
13123
|
-
if (!
|
|
13458
|
+
const { existsSync: existsSync10 } = await import("fs");
|
|
13459
|
+
if (!existsSync10(logPath)) {
|
|
13124
13460
|
process.stderr.write(`Log file not found: ${logPath}
|
|
13125
13461
|
`);
|
|
13126
13462
|
process.stderr.write("Run protect-mcp in shadow mode first to generate a log file.\n");
|
|
@@ -13142,8 +13478,8 @@ async function handleSimulate(args) {
|
|
|
13142
13478
|
}
|
|
13143
13479
|
}
|
|
13144
13480
|
async function handleDoctor() {
|
|
13145
|
-
const { existsSync:
|
|
13146
|
-
const { join:
|
|
13481
|
+
const { existsSync: existsSync10, readFileSync: readFileSync11, readdirSync: readdirSync4 } = await import("fs");
|
|
13482
|
+
const { join: join9 } = await import("path");
|
|
13147
13483
|
const { execSync } = await import("child_process");
|
|
13148
13484
|
const green2 = (s) => `\x1B[32m\u2713\x1B[0m ${s}`;
|
|
13149
13485
|
const red2 = (s) => `\x1B[31m\u2717\x1B[0m ${s}`;
|
|
@@ -13162,8 +13498,8 @@ async function handleDoctor() {
|
|
|
13162
13498
|
`));
|
|
13163
13499
|
issues++;
|
|
13164
13500
|
}
|
|
13165
|
-
const configPath =
|
|
13166
|
-
if (
|
|
13501
|
+
const configPath = join9(process.cwd(), "scopeblind.config.json");
|
|
13502
|
+
if (existsSync10(configPath)) {
|
|
13167
13503
|
try {
|
|
13168
13504
|
const config = JSON.parse(readFileSync11(configPath, "utf-8"));
|
|
13169
13505
|
if (config.signing?.private_key || config.signing?.key_file) {
|
|
@@ -13182,7 +13518,7 @@ async function handleDoctor() {
|
|
|
13182
13518
|
let policyFound = false;
|
|
13183
13519
|
for (const dir of ["cedar", "policies", "."]) {
|
|
13184
13520
|
try {
|
|
13185
|
-
if (
|
|
13521
|
+
if (existsSync10(dir) && readdirSync4(dir).some((f) => f.endsWith(".cedar"))) {
|
|
13186
13522
|
process.stdout.write(green2(`Cedar policies found in ./${dir}/
|
|
13187
13523
|
`));
|
|
13188
13524
|
policyFound = true;
|
|
@@ -13193,7 +13529,7 @@ async function handleDoctor() {
|
|
|
13193
13529
|
}
|
|
13194
13530
|
if (!policyFound) {
|
|
13195
13531
|
for (const name of ["policy.json", "protect-mcp.policy.json", "scopeblind-policy.json"]) {
|
|
13196
|
-
if (
|
|
13532
|
+
if (existsSync10(name)) {
|
|
13197
13533
|
process.stdout.write(green2(`JSON policy found: ${name}
|
|
13198
13534
|
`));
|
|
13199
13535
|
policyFound = true;
|
|
@@ -13214,9 +13550,9 @@ async function handleDoctor() {
|
|
|
13214
13550
|
} catch {
|
|
13215
13551
|
process.stdout.write(dim2(" Cedar WASM not installed\n"));
|
|
13216
13552
|
}
|
|
13217
|
-
const logFile =
|
|
13218
|
-
const receiptFile =
|
|
13219
|
-
if (
|
|
13553
|
+
const logFile = join9(process.cwd(), "protect-mcp-decisions.jsonl");
|
|
13554
|
+
const receiptFile = join9(process.cwd(), "protect-mcp-receipts.jsonl");
|
|
13555
|
+
if (existsSync10(logFile)) {
|
|
13220
13556
|
try {
|
|
13221
13557
|
const lines = readFileSync11(logFile, "utf-8").trim().split("\n").length;
|
|
13222
13558
|
process.stdout.write(green2(`Decision log: ${lines} entries
|
|
@@ -13227,7 +13563,7 @@ async function handleDoctor() {
|
|
|
13227
13563
|
} else {
|
|
13228
13564
|
process.stdout.write(dim2(" No decision log yet, will be created on first tool call\n"));
|
|
13229
13565
|
}
|
|
13230
|
-
if (
|
|
13566
|
+
if (existsSync10(receiptFile)) {
|
|
13231
13567
|
try {
|
|
13232
13568
|
const lines = readFileSync11(receiptFile, "utf-8").trim().split("\n").length;
|
|
13233
13569
|
process.stdout.write(green2(`Receipt file: ${lines} signed receipts
|
|
@@ -13298,9 +13634,9 @@ async function handleReport(args) {
|
|
|
13298
13634
|
}
|
|
13299
13635
|
}
|
|
13300
13636
|
const { generateReport: generateReport2, formatReportMarkdown: formatReportMarkdown2 } = await Promise.resolve().then(() => (init_report(), report_exports));
|
|
13301
|
-
const { join:
|
|
13302
|
-
const logPath =
|
|
13303
|
-
const receiptPath =
|
|
13637
|
+
const { join: join9 } = await import("path");
|
|
13638
|
+
const logPath = join9(dir, ".protect-mcp-log.jsonl");
|
|
13639
|
+
const receiptPath = join9(dir, ".protect-mcp-receipts.jsonl");
|
|
13304
13640
|
const report = generateReport2(logPath, receiptPath, period);
|
|
13305
13641
|
let output;
|
|
13306
13642
|
if (format === "md") {
|
|
@@ -13309,8 +13645,8 @@ async function handleReport(args) {
|
|
|
13309
13645
|
output = JSON.stringify(report, null, 2);
|
|
13310
13646
|
}
|
|
13311
13647
|
if (outputPath) {
|
|
13312
|
-
const { writeFileSync:
|
|
13313
|
-
|
|
13648
|
+
const { writeFileSync: writeFileSync5 } = await import("fs");
|
|
13649
|
+
writeFileSync5(outputPath, output, "utf-8");
|
|
13314
13650
|
process.stderr.write(`Report written to ${outputPath}
|
|
13315
13651
|
`);
|
|
13316
13652
|
} else {
|