protect-mcp 0.7.3 → 0.7.4
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 +48 -0
- package/README.md +155 -7
- package/dist/{bundle-XTR3YMPO.mjs → bundle-YUUHAG7O.mjs} +1 -1
- package/dist/chunk-36UID5WY.mjs +662 -0
- package/dist/{chunk-546U3A7R.mjs → chunk-D2RDY2JR.mjs} +92 -1
- package/dist/chunk-F2FKQ4XN.mjs +410 -0
- package/dist/{chunk-J6L4XCTE.mjs → chunk-KPSICBAJ.mjs} +75 -0
- package/dist/{chunk-G67NUBML.mjs → chunk-NVJHGXXG.mjs} +10 -2
- package/dist/{chunk-5JXFV37Y.mjs → chunk-PM2ZO57M.mjs} +10 -2
- package/dist/{chunk-OHUTUFTC.mjs → chunk-UBZJ3VI2.mjs} +19 -15
- package/dist/cli.js +4022 -512
- package/dist/cli.mjs +2027 -8
- package/dist/demo-server.d.mts +3 -0
- package/dist/demo-server.d.ts +3 -0
- package/dist/demo-server.js +75 -0
- package/dist/demo-server.mjs +1 -1
- package/dist/hook-server.js +117 -20
- package/dist/hook-server.mjs +2 -2
- package/dist/{http-transport-R5AO7X6D.mjs → http-transport-4GMMRPW7.mjs} +2 -2
- package/dist/index.d.mts +115 -1
- package/dist/index.d.ts +115 -1
- package/dist/index.js +961 -75
- package/dist/index.mjs +48 -249
- package/dist/receipt-registry-6CAOY6RP.mjs +279 -0
- package/dist/signing-committed-MMLJ6OGG.mjs +15 -0
- package/package.json +1 -1
- package/dist/chunk-ZBKJANP7.mjs +0 -145
package/dist/index.js
CHANGED
|
@@ -1002,10 +1002,10 @@ var require_types = __commonJS({
|
|
|
1002
1002
|
if (!jwtRegex.test(jwt))
|
|
1003
1003
|
return false;
|
|
1004
1004
|
try {
|
|
1005
|
-
const [
|
|
1006
|
-
if (!
|
|
1005
|
+
const [header2] = jwt.split(".");
|
|
1006
|
+
if (!header2)
|
|
1007
1007
|
return false;
|
|
1008
|
-
const base64 =
|
|
1008
|
+
const base64 = header2.replace(/-/g, "+").replace(/_/g, "/").padEnd(header2.length + (4 - header2.length % 4) % 4, "=");
|
|
1009
1009
|
const decoded = JSON.parse(atob(base64));
|
|
1010
1010
|
if (typeof decoded !== "object" || decoded === null)
|
|
1011
1011
|
return false;
|
|
@@ -6486,10 +6486,10 @@ var require_schemas = __commonJS({
|
|
|
6486
6486
|
const tokensParts = token.split(".");
|
|
6487
6487
|
if (tokensParts.length !== 3)
|
|
6488
6488
|
return false;
|
|
6489
|
-
const [
|
|
6490
|
-
if (!
|
|
6489
|
+
const [header2] = tokensParts;
|
|
6490
|
+
if (!header2)
|
|
6491
6491
|
return false;
|
|
6492
|
-
const parsedHeader = JSON.parse(atob(
|
|
6492
|
+
const parsedHeader = JSON.parse(atob(header2));
|
|
6493
6493
|
if ("typ" in parsedHeader && parsedHeader?.typ !== "JWT")
|
|
6494
6494
|
return false;
|
|
6495
6495
|
if (!parsedHeader.alg)
|
|
@@ -35019,7 +35019,9 @@ var require_mcp = __commonJS({
|
|
|
35019
35019
|
var index_exports = {};
|
|
35020
35020
|
__export(index_exports, {
|
|
35021
35021
|
BUILTIN_PATTERNS: () => BUILTIN_PATTERNS,
|
|
35022
|
+
CONNECTOR_PILOTS: () => CONNECTOR_PILOTS,
|
|
35022
35023
|
ConfidentialGate: () => ConfidentialGate,
|
|
35024
|
+
POLICY_PACKS: () => POLICY_PACKS,
|
|
35023
35025
|
ProtectGateway: () => ProtectGateway,
|
|
35024
35026
|
ReceiptPropagator: () => ReceiptPropagator,
|
|
35025
35027
|
ScopeBlindBridge: () => ScopeBlindBridge,
|
|
@@ -35029,6 +35031,9 @@ __export(index_exports, {
|
|
|
35029
35031
|
collectSignedReceipts: () => collectSignedReceipts,
|
|
35030
35032
|
computeCalibration: () => computeCalibration,
|
|
35031
35033
|
confidentialInference: () => confidentialInference,
|
|
35034
|
+
connectorDirectory: () => connectorDirectory,
|
|
35035
|
+
connectorDoctor: () => connectorDoctor,
|
|
35036
|
+
connectorPilotIds: () => connectorPilotIds,
|
|
35032
35037
|
createApprovalChallenge: () => createApprovalChallenge,
|
|
35033
35038
|
createApprovalReceiptPayload: () => createApprovalReceiptPayload,
|
|
35034
35039
|
createAttestationField: () => createAttestationField,
|
|
@@ -35040,6 +35045,7 @@ __export(index_exports, {
|
|
|
35040
35045
|
createReceiptChannel: () => createReceiptChannel,
|
|
35041
35046
|
createSandbox: () => createSandbox,
|
|
35042
35047
|
createSandboxServer: () => createSandboxServer,
|
|
35048
|
+
createSelectiveDisclosurePackage: () => createSelectiveDisclosurePackage,
|
|
35043
35049
|
destroySandbox: () => destroySandbox,
|
|
35044
35050
|
discloseField: () => discloseField,
|
|
35045
35051
|
ed25519ToDIDKey: () => ed25519ToDIDKey,
|
|
@@ -35060,6 +35066,8 @@ __export(index_exports, {
|
|
|
35060
35066
|
generateSampleCedarPolicy: () => generateSampleCedarPolicy,
|
|
35061
35067
|
generateSchemaStub: () => generateSchemaStub,
|
|
35062
35068
|
generateVerifyReceiptSkill: () => generateVerifyReceiptSkill,
|
|
35069
|
+
getConnectorPilot: () => getConnectorPilot,
|
|
35070
|
+
getPolicyPack: () => getPolicyPack,
|
|
35063
35071
|
getScopeBlindBridge: () => getScopeBlindBridge,
|
|
35064
35072
|
getSignerInfo: () => getSignerInfo,
|
|
35065
35073
|
getToolPolicy: () => getToolPolicy,
|
|
@@ -35080,8 +35088,10 @@ __export(index_exports, {
|
|
|
35080
35088
|
parseLogFile: () => parseLogFile,
|
|
35081
35089
|
parseNotificationConfigFromEnv: () => parseNotificationConfigFromEnv,
|
|
35082
35090
|
parseRateLimit: () => parseRateLimit,
|
|
35091
|
+
policyPackIds: () => policyPackIds,
|
|
35083
35092
|
policySetFromSource: () => policySetFromSource,
|
|
35084
35093
|
queryExternalPDP: () => queryExternalPDP,
|
|
35094
|
+
readInstalledConnectorPilots: () => readInstalledConnectorPilots,
|
|
35085
35095
|
receiptToVP: () => receiptToVP,
|
|
35086
35096
|
receiptsToHFRows: () => receiptsToHFRows,
|
|
35087
35097
|
redactFields: () => redactFields,
|
|
@@ -35105,13 +35115,15 @@ __export(index_exports, {
|
|
|
35105
35115
|
verifyApprovalAssertion: () => verifyApprovalAssertion,
|
|
35106
35116
|
verifyCommitment: () => verifyCommitment,
|
|
35107
35117
|
verifyEvidenceAttestation: () => verifyEvidenceAttestation,
|
|
35108
|
-
verifyRekorAnchor: () => verifyRekorAnchor
|
|
35118
|
+
verifyRekorAnchor: () => verifyRekorAnchor,
|
|
35119
|
+
verifySelectiveDisclosurePackage: () => verifySelectiveDisclosurePackage,
|
|
35120
|
+
writeConnectorPilots: () => writeConnectorPilots
|
|
35109
35121
|
});
|
|
35110
35122
|
module.exports = __toCommonJS(index_exports);
|
|
35111
35123
|
|
|
35112
35124
|
// src/gateway.ts
|
|
35113
35125
|
var import_node_child_process = require("child_process");
|
|
35114
|
-
var
|
|
35126
|
+
var import_node_crypto4 = require("crypto");
|
|
35115
35127
|
var import_node_readline = require("readline");
|
|
35116
35128
|
var import_node_fs6 = require("fs");
|
|
35117
35129
|
var import_node_path4 = require("path");
|
|
@@ -35541,6 +35553,7 @@ function signDecision(entry) {
|
|
|
35541
35553
|
if (entry.timing) payload.timing = entry.timing;
|
|
35542
35554
|
if (entry.swarm) payload.swarm = entry.swarm;
|
|
35543
35555
|
if (entry.payload_digest) payload.payload_digest = entry.payload_digest;
|
|
35556
|
+
if (entry.action_readback) payload.action_readback = entry.action_readback;
|
|
35544
35557
|
if (entry.deny_iteration) payload.deny_iteration = entry.deny_iteration;
|
|
35545
35558
|
const result = artifactsModule.createSignedArtifact(
|
|
35546
35559
|
artifactType,
|
|
@@ -36347,6 +36360,95 @@ function handleListApprovals(res, approvalStore) {
|
|
|
36347
36360
|
res.end(JSON.stringify({ grants }));
|
|
36348
36361
|
}
|
|
36349
36362
|
|
|
36363
|
+
// src/action-readback.ts
|
|
36364
|
+
var import_node_crypto3 = require("crypto");
|
|
36365
|
+
var SECRET_KEY_RE = /(api[_-]?key|authorization|bearer|credential|password|secret|session|token|private[_-]?key)/i;
|
|
36366
|
+
var DESTINATION_KEYS = [
|
|
36367
|
+
"path",
|
|
36368
|
+
"file_path",
|
|
36369
|
+
"filePath",
|
|
36370
|
+
"url",
|
|
36371
|
+
"uri",
|
|
36372
|
+
"endpoint",
|
|
36373
|
+
"host",
|
|
36374
|
+
"hostname",
|
|
36375
|
+
"repo",
|
|
36376
|
+
"repository",
|
|
36377
|
+
"branch",
|
|
36378
|
+
"channel",
|
|
36379
|
+
"to",
|
|
36380
|
+
"recipient",
|
|
36381
|
+
"symbol",
|
|
36382
|
+
"account",
|
|
36383
|
+
"bucket",
|
|
36384
|
+
"database",
|
|
36385
|
+
"table",
|
|
36386
|
+
"service"
|
|
36387
|
+
];
|
|
36388
|
+
function stableStringify(value) {
|
|
36389
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
36390
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
36391
|
+
const obj = value;
|
|
36392
|
+
return `{${Object.keys(obj).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(obj[key])}`).join(",")}}`;
|
|
36393
|
+
}
|
|
36394
|
+
function redact(value, path = [], redacted = [], disclosed = [], depth = 0) {
|
|
36395
|
+
if (depth > 4) return "[truncated-depth]";
|
|
36396
|
+
if (value === null || value === void 0) return value;
|
|
36397
|
+
if (typeof value !== "object") {
|
|
36398
|
+
if (path.length > 0) disclosed.push(path.join("."));
|
|
36399
|
+
if (typeof value === "string" && value.length > 240) return `${value.slice(0, 240)}...`;
|
|
36400
|
+
return value;
|
|
36401
|
+
}
|
|
36402
|
+
if (Array.isArray(value)) {
|
|
36403
|
+
return value.slice(0, 20).map((item, idx) => redact(item, [...path, String(idx)], redacted, disclosed, depth + 1));
|
|
36404
|
+
}
|
|
36405
|
+
const out = {};
|
|
36406
|
+
for (const [key, child] of Object.entries(value)) {
|
|
36407
|
+
const childPath = [...path, key];
|
|
36408
|
+
if (SECRET_KEY_RE.test(key)) {
|
|
36409
|
+
redacted.push(childPath.join("."));
|
|
36410
|
+
out[key] = "[redacted]";
|
|
36411
|
+
continue;
|
|
36412
|
+
}
|
|
36413
|
+
out[key] = redact(child, childPath, redacted, disclosed, depth + 1);
|
|
36414
|
+
}
|
|
36415
|
+
return out;
|
|
36416
|
+
}
|
|
36417
|
+
function firstStringValue(input, keys) {
|
|
36418
|
+
for (const key of keys) {
|
|
36419
|
+
const value = input[key];
|
|
36420
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
36421
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
36422
|
+
}
|
|
36423
|
+
return void 0;
|
|
36424
|
+
}
|
|
36425
|
+
function actionFor(tool, input) {
|
|
36426
|
+
const explicit = firstStringValue(input, ["action", "operation", "method", "verb", "command"]);
|
|
36427
|
+
if (explicit) return explicit.length > 90 ? `${explicit.slice(0, 90)}...` : explicit;
|
|
36428
|
+
return tool;
|
|
36429
|
+
}
|
|
36430
|
+
function buildActionReadback(tool, input) {
|
|
36431
|
+
const normalized = input && typeof input === "object" && !Array.isArray(input) ? input : { value: input };
|
|
36432
|
+
const canonical = stableStringify(normalized);
|
|
36433
|
+
const redactedFields = [];
|
|
36434
|
+
const disclosedFields = [];
|
|
36435
|
+
const payloadPreview = redact(normalized, [], redactedFields, disclosedFields);
|
|
36436
|
+
const action = actionFor(tool, normalized);
|
|
36437
|
+
const destination = firstStringValue(normalized, DESTINATION_KEYS);
|
|
36438
|
+
const summary = destination ? `${tool} -> ${destination}` : `${tool} request`;
|
|
36439
|
+
return {
|
|
36440
|
+
tool,
|
|
36441
|
+
action,
|
|
36442
|
+
destination,
|
|
36443
|
+
payload_preview: payloadPreview,
|
|
36444
|
+
payload_hash: (0, import_node_crypto3.createHash)("sha256").update(canonical).digest("hex"),
|
|
36445
|
+
payload_bytes: Buffer.byteLength(canonical, "utf-8"),
|
|
36446
|
+
disclosed_fields: [...new Set(disclosedFields)].slice(0, 80),
|
|
36447
|
+
redacted_fields: [...new Set(redactedFields)].slice(0, 80),
|
|
36448
|
+
summary
|
|
36449
|
+
};
|
|
36450
|
+
}
|
|
36451
|
+
|
|
36350
36452
|
// src/gateway.ts
|
|
36351
36453
|
var LOG_FILE2 = ".protect-mcp-log.jsonl";
|
|
36352
36454
|
var RECEIPTS_FILE = ".protect-mcp-receipts.jsonl";
|
|
@@ -36362,7 +36464,7 @@ var ProtectGateway = class {
|
|
|
36362
36464
|
/** Approval grants keyed by request_id (scoped to the specific action that was requested) */
|
|
36363
36465
|
approvalStore = /* @__PURE__ */ new Map();
|
|
36364
36466
|
/** Random nonce generated at startup — required for approval endpoint authentication */
|
|
36365
|
-
approvalNonce = (0,
|
|
36467
|
+
approvalNonce = (0, import_node_crypto4.randomBytes)(16).toString("hex");
|
|
36366
36468
|
currentTier = "unknown";
|
|
36367
36469
|
admissionResult = null;
|
|
36368
36470
|
/** Notification config for approval gates (SMS, webhook, email) */
|
|
@@ -36516,8 +36618,10 @@ var ProtectGateway = class {
|
|
|
36516
36618
|
}
|
|
36517
36619
|
async interceptToolCall(request) {
|
|
36518
36620
|
const toolName = request.params?.name || "unknown";
|
|
36519
|
-
const requestId = (0,
|
|
36621
|
+
const requestId = (0, import_node_crypto4.randomUUID)().slice(0, 12);
|
|
36520
36622
|
const mode = this.config.enforce ? "enforce" : "shadow";
|
|
36623
|
+
const toolInput = request.params?.arguments && typeof request.params.arguments === "object" ? request.params.arguments : request.params || {};
|
|
36624
|
+
const actionReadback = buildActionReadback(toolName, toolInput);
|
|
36521
36625
|
let resolvedAgentKid = this.admissionResult?.agent_id;
|
|
36522
36626
|
let effectiveToolPolicy;
|
|
36523
36627
|
if (this.config.multiAgent?.enabled) {
|
|
@@ -36527,7 +36631,7 @@ var ProtectGateway = class {
|
|
|
36527
36631
|
if (agentOverrides && agentOverrides[toolName]) {
|
|
36528
36632
|
effectiveToolPolicy = { ...getToolPolicy(toolName, this.config.policy), ...agentOverrides[toolName] };
|
|
36529
36633
|
} else if (!resolvedAgentKid && this.config.multiAgent.unknownAgentPolicy === "deny") {
|
|
36530
|
-
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "unknown_agent_denied", request_id: requestId, tier: this.currentTier });
|
|
36634
|
+
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "unknown_agent_denied", request_id: requestId, tier: this.currentTier, action_readback: actionReadback });
|
|
36531
36635
|
if (this.config.enforce) {
|
|
36532
36636
|
return this.makeErrorResponse(request.id, -32600, `Tool "${toolName}" denied: unidentified agent`);
|
|
36533
36637
|
}
|
|
@@ -36548,7 +36652,7 @@ var ProtectGateway = class {
|
|
|
36548
36652
|
if (cred.resolved) {
|
|
36549
36653
|
credentialRef = cred.label;
|
|
36550
36654
|
} else if (cred.error && !cred.error.includes("not configured")) {
|
|
36551
|
-
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "credential_error", request_id: requestId, tier: this.currentTier, credential_ref: toolName });
|
|
36655
|
+
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "credential_error", request_id: requestId, tier: this.currentTier, credential_ref: toolName, action_readback: actionReadback });
|
|
36552
36656
|
if (this.config.enforce) {
|
|
36553
36657
|
return this.makeErrorResponse(request.id, -32600, `Credential error for tool "${toolName}"`);
|
|
36554
36658
|
}
|
|
@@ -36563,13 +36667,13 @@ var ProtectGateway = class {
|
|
|
36563
36667
|
});
|
|
36564
36668
|
if (!cedarDecision.allowed) {
|
|
36565
36669
|
const reason = cedarDecision.reason || "cedar_deny";
|
|
36566
|
-
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: reason, request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
|
|
36670
|
+
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: reason, request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
|
|
36567
36671
|
if (this.config.enforce) {
|
|
36568
36672
|
return this.makeErrorResponse(request.id, -32600, `Tool "${toolName}" denied by Cedar policy`);
|
|
36569
36673
|
}
|
|
36570
36674
|
return null;
|
|
36571
36675
|
}
|
|
36572
|
-
this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: "cedar_allow", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
|
|
36676
|
+
this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: "cedar_allow", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
|
|
36573
36677
|
return null;
|
|
36574
36678
|
} catch (err) {
|
|
36575
36679
|
if (this.config.verbose) this.log(`Cedar evaluation error: ${err instanceof Error ? err.message : err}`);
|
|
@@ -36587,7 +36691,7 @@ var ProtectGateway = class {
|
|
|
36587
36691
|
const externalDecision = await queryExternalPDP(ctx, this.config.policy.external);
|
|
36588
36692
|
if (!externalDecision.allowed) {
|
|
36589
36693
|
const reason = `external_pdp_deny${externalDecision.reason ? ": " + externalDecision.reason : ""}`;
|
|
36590
|
-
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: reason, request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
|
|
36694
|
+
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: reason, request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
|
|
36591
36695
|
if (this.config.enforce) {
|
|
36592
36696
|
return this.makeErrorResponse(request.id, -32600, `Tool "${toolName}" denied by external policy engine`);
|
|
36593
36697
|
}
|
|
@@ -36599,7 +36703,7 @@ var ProtectGateway = class {
|
|
|
36599
36703
|
}
|
|
36600
36704
|
if (toolPolicy.min_tier) {
|
|
36601
36705
|
if (!meetsMinTier(this.currentTier, toolPolicy.min_tier)) {
|
|
36602
|
-
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "tier_insufficient", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
|
|
36706
|
+
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "tier_insufficient", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
|
|
36603
36707
|
if (this.config.enforce) {
|
|
36604
36708
|
return this.makeErrorResponse(request.id, -32600, `Tool "${toolName}" requires tier "${toolPolicy.min_tier}"`);
|
|
36605
36709
|
}
|
|
@@ -36607,7 +36711,7 @@ var ProtectGateway = class {
|
|
|
36607
36711
|
}
|
|
36608
36712
|
}
|
|
36609
36713
|
if (toolPolicy.block) {
|
|
36610
|
-
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "policy_block", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
|
|
36714
|
+
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "policy_block", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
|
|
36611
36715
|
if (this.config.enforce) {
|
|
36612
36716
|
return this.makeErrorResponse(request.id, -32600, `Tool "${toolName}" is blocked by policy`);
|
|
36613
36717
|
}
|
|
@@ -36618,10 +36722,10 @@ var ProtectGateway = class {
|
|
|
36618
36722
|
const alwaysGrant = this.approvalStore.get(`always:${toolName}`);
|
|
36619
36723
|
if (grant && Date.now() < grant.expires_at || alwaysGrant && Date.now() < alwaysGrant.expires_at) {
|
|
36620
36724
|
if (grant && grant.mode === "once") this.approvalStore.delete(requestId);
|
|
36621
|
-
this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: "approval_granted", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
|
|
36725
|
+
this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: "approval_granted", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
|
|
36622
36726
|
return null;
|
|
36623
36727
|
}
|
|
36624
|
-
this.emitDecisionLog({ tool: toolName, decision: "require_approval", reason_code: "requires_human_approval", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
|
|
36728
|
+
this.emitDecisionLog({ tool: toolName, decision: "require_approval", reason_code: "requires_human_approval", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
|
|
36625
36729
|
if (this.notificationConfig) {
|
|
36626
36730
|
sendApprovalNotification(this.notificationConfig, {
|
|
36627
36731
|
requestId,
|
|
@@ -36644,7 +36748,7 @@ var ProtectGateway = class {
|
|
|
36644
36748
|
content: [
|
|
36645
36749
|
{
|
|
36646
36750
|
type: "text",
|
|
36647
|
-
text: `REQUIRES_APPROVAL: The tool "${toolName}" requires human approval before execution. Request ID: ${requestId}. Approval nonce: ${this.approvalNonce}. Tell the user you need their approval to use "${toolName}" and will retry when granted. Do NOT retry this tool call until the user explicitly approves it.`
|
|
36751
|
+
text: `REQUIRES_APPROVAL: The tool "${toolName}" requires human approval before execution. Exact action: ${actionReadback.summary}. Payload hash: ${actionReadback.payload_hash.slice(0, 16)}\u2026 Request ID: ${requestId}. Approval nonce: ${this.approvalNonce}. Tell the user you need their approval to use "${toolName}" and will retry when granted. Do NOT retry this tool call until the user explicitly approves it.`
|
|
36648
36752
|
}
|
|
36649
36753
|
],
|
|
36650
36754
|
isError: true
|
|
@@ -36660,19 +36764,19 @@ var ProtectGateway = class {
|
|
|
36660
36764
|
const key = `tool:${toolName}:${this.currentTier}`;
|
|
36661
36765
|
const { allowed, remaining } = checkRateLimit(key, limit, this.rateLimitStore);
|
|
36662
36766
|
if (!allowed) {
|
|
36663
|
-
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "rate_limit_exceeded", request_id: requestId, rate_limit_remaining: 0, tier: this.currentTier, credential_ref: credentialRef });
|
|
36767
|
+
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "rate_limit_exceeded", request_id: requestId, rate_limit_remaining: 0, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
|
|
36664
36768
|
if (this.config.enforce) {
|
|
36665
36769
|
return this.makeErrorResponse(request.id, -32600, `Tool "${toolName}" rate limit exceeded (${rateSpec})`);
|
|
36666
36770
|
}
|
|
36667
36771
|
return null;
|
|
36668
36772
|
}
|
|
36669
|
-
this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: "policy_allow", request_id: requestId, rate_limit_remaining: remaining, tier: this.currentTier, credential_ref: credentialRef });
|
|
36773
|
+
this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: "policy_allow", request_id: requestId, rate_limit_remaining: remaining, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
|
|
36670
36774
|
} catch {
|
|
36671
|
-
this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: "default_allow", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
|
|
36775
|
+
this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: "default_allow", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
|
|
36672
36776
|
}
|
|
36673
36777
|
} else {
|
|
36674
36778
|
const reasonCode = this.config.enforce ? "policy_allow" : "observe_mode";
|
|
36675
|
-
this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: reasonCode, request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
|
|
36779
|
+
this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: reasonCode, request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
|
|
36676
36780
|
}
|
|
36677
36781
|
return null;
|
|
36678
36782
|
}
|
|
@@ -36694,8 +36798,8 @@ var ProtectGateway = class {
|
|
|
36694
36798
|
*/
|
|
36695
36799
|
emitDecisionLog(entry) {
|
|
36696
36800
|
const mode = this.config.enforce ? "enforce" : "shadow";
|
|
36697
|
-
const otelTraceId = entry.otel_trace_id || (0,
|
|
36698
|
-
const otelSpanId = entry.otel_span_id || (0,
|
|
36801
|
+
const otelTraceId = entry.otel_trace_id || (0, import_node_crypto4.randomBytes)(16).toString("hex");
|
|
36802
|
+
const otelSpanId = entry.otel_span_id || (0, import_node_crypto4.randomBytes)(8).toString("hex");
|
|
36699
36803
|
const log = {
|
|
36700
36804
|
v: 2,
|
|
36701
36805
|
tool: entry.tool || "unknown",
|
|
@@ -36703,12 +36807,13 @@ var ProtectGateway = class {
|
|
|
36703
36807
|
reason_code: entry.reason_code || "default_allow",
|
|
36704
36808
|
policy_digest: this.config.policyDigest,
|
|
36705
36809
|
policy_engine: this.config.policy?.policy_engine || "built-in",
|
|
36706
|
-
request_id: entry.request_id || (0,
|
|
36810
|
+
request_id: entry.request_id || (0, import_node_crypto4.randomUUID)().slice(0, 12),
|
|
36707
36811
|
timestamp: Date.now(),
|
|
36708
36812
|
mode,
|
|
36709
36813
|
...entry.rate_limit_remaining !== void 0 && { rate_limit_remaining: entry.rate_limit_remaining },
|
|
36710
36814
|
...entry.tier && { tier: entry.tier },
|
|
36711
36815
|
...entry.credential_ref && { credential_ref: entry.credential_ref },
|
|
36816
|
+
...entry.action_readback && { action_readback: entry.action_readback },
|
|
36712
36817
|
otel_trace_id: otelTraceId,
|
|
36713
36818
|
otel_span_id: otelSpanId
|
|
36714
36819
|
};
|
|
@@ -39058,6 +39163,50 @@ function collectPath(leaves, index, out) {
|
|
|
39058
39163
|
out.push(merkleRoot(leaves.slice(0, k)));
|
|
39059
39164
|
}
|
|
39060
39165
|
}
|
|
39166
|
+
function verifyProof(expectedRootHex, leafHash, proof) {
|
|
39167
|
+
if (proof.index < 0 || proof.index >= proof.treeSize) return false;
|
|
39168
|
+
if (proof.treeSize === 1) {
|
|
39169
|
+
return proof.siblings.length === 0 && bytesToHex(leafHash).toLowerCase() === expectedRootHex.toLowerCase();
|
|
39170
|
+
}
|
|
39171
|
+
let result;
|
|
39172
|
+
try {
|
|
39173
|
+
result = reconstructRoot(
|
|
39174
|
+
leafHash,
|
|
39175
|
+
proof.index,
|
|
39176
|
+
proof.treeSize,
|
|
39177
|
+
proof.siblings
|
|
39178
|
+
);
|
|
39179
|
+
} catch {
|
|
39180
|
+
return false;
|
|
39181
|
+
}
|
|
39182
|
+
return bytesToHex(result).toLowerCase() === expectedRootHex.toLowerCase();
|
|
39183
|
+
}
|
|
39184
|
+
function reconstructRoot(leafHash, index, treeSize, siblings) {
|
|
39185
|
+
if (treeSize === 1) {
|
|
39186
|
+
if (siblings.length !== 0) {
|
|
39187
|
+
throw new Error("reconstructRoot: extra siblings at single-leaf level");
|
|
39188
|
+
}
|
|
39189
|
+
return leafHash;
|
|
39190
|
+
}
|
|
39191
|
+
if (siblings.length === 0) {
|
|
39192
|
+
throw new Error("reconstructRoot: ran out of siblings before single-leaf");
|
|
39193
|
+
}
|
|
39194
|
+
const k = largestPowerOfTwoLessThan(treeSize);
|
|
39195
|
+
const outermostSibling = hexToBytes(siblings[siblings.length - 1]);
|
|
39196
|
+
const innerSiblings = siblings.slice(0, -1);
|
|
39197
|
+
if (index < k) {
|
|
39198
|
+
const leftHash = reconstructRoot(leafHash, index, k, innerSiblings);
|
|
39199
|
+
return hashInternal(leftHash, outermostSibling);
|
|
39200
|
+
} else {
|
|
39201
|
+
const rightHash = reconstructRoot(
|
|
39202
|
+
leafHash,
|
|
39203
|
+
index - k,
|
|
39204
|
+
treeSize - k,
|
|
39205
|
+
innerSiblings
|
|
39206
|
+
);
|
|
39207
|
+
return hashInternal(outermostSibling, rightHash);
|
|
39208
|
+
}
|
|
39209
|
+
}
|
|
39061
39210
|
function largestPowerOfTwoLessThan(n) {
|
|
39062
39211
|
if (n < 2) {
|
|
39063
39212
|
throw new Error(`largestPowerOfTwoLessThan: n must be >= 2 (got ${n})`);
|
|
@@ -39085,6 +39234,17 @@ function base64urlNoPad(bytes) {
|
|
|
39085
39234
|
const std = typeof Buffer !== "undefined" ? Buffer.from(bytes).toString("base64") : btoa(String.fromCharCode(...bytes));
|
|
39086
39235
|
return std.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
39087
39236
|
}
|
|
39237
|
+
function base64urlDecode(s) {
|
|
39238
|
+
const std = s.replace(/-/g, "+").replace(/_/g, "/");
|
|
39239
|
+
const padded = std + "=".repeat((4 - std.length % 4) % 4);
|
|
39240
|
+
if (typeof Buffer !== "undefined") {
|
|
39241
|
+
return new Uint8Array(Buffer.from(padded, "base64"));
|
|
39242
|
+
}
|
|
39243
|
+
const bin = atob(padded);
|
|
39244
|
+
const out = new Uint8Array(bin.length);
|
|
39245
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
39246
|
+
return out;
|
|
39247
|
+
}
|
|
39088
39248
|
function encodeLeaf(field) {
|
|
39089
39249
|
const obj = {
|
|
39090
39250
|
name: field.name,
|
|
@@ -39218,11 +39378,128 @@ function discloseField(receiptHash, fieldName, openings) {
|
|
|
39218
39378
|
proof
|
|
39219
39379
|
};
|
|
39220
39380
|
}
|
|
39381
|
+
function createSelectiveDisclosurePackage(receipt, fieldNames, openings) {
|
|
39382
|
+
const receiptHash = receiptHashHex(receipt);
|
|
39383
|
+
const committedFieldsRoot = typeof receipt.committed_fields_root === "string" ? receipt.committed_fields_root : "";
|
|
39384
|
+
if (!committedFieldsRoot) {
|
|
39385
|
+
throw new Error("selective disclosure requires a committed receipt with committed_fields_root");
|
|
39386
|
+
}
|
|
39387
|
+
const committedFieldNames = committedFieldNamesFromReceipt(receipt, openings);
|
|
39388
|
+
const uniqueFields = Array.from(new Set(fieldNames));
|
|
39389
|
+
for (const fieldName of uniqueFields) {
|
|
39390
|
+
if (!committedFieldNames.includes(fieldName)) {
|
|
39391
|
+
throw new Error(`selective disclosure: field "${fieldName}" is not committed by this receipt`);
|
|
39392
|
+
}
|
|
39393
|
+
}
|
|
39394
|
+
const disclosures = uniqueFields.map((fieldName) => discloseField(receiptHash, fieldName, openings));
|
|
39395
|
+
const hiddenFields = committedFieldNames.filter((fieldName) => !uniqueFields.includes(fieldName));
|
|
39396
|
+
return {
|
|
39397
|
+
type: "scopeblind.selective_disclosure.v0",
|
|
39398
|
+
version: 0,
|
|
39399
|
+
parent_receipt_hash: receiptHash,
|
|
39400
|
+
committed_fields_root: committedFieldsRoot,
|
|
39401
|
+
disclosed_fields: uniqueFields,
|
|
39402
|
+
hidden_fields: hiddenFields,
|
|
39403
|
+
disclosures,
|
|
39404
|
+
verifier_explanation: {
|
|
39405
|
+
summary: "This package opens selected committed receipt fields and leaves the rest hidden.",
|
|
39406
|
+
disclosed: uniqueFields.length ? `Disclosed fields: ${uniqueFields.join(", ")}.` : "No fields were disclosed.",
|
|
39407
|
+
hidden: hiddenFields.length ? `Hidden committed fields: ${hiddenFields.join(", ")}. Their salted commitments remain bound to the signed receipt root.` : "No committed fields remain hidden.",
|
|
39408
|
+
limitation: "Selective Disclosure v0 uses salted SHA-256 commitments and Merkle proofs. It is not a full zero-knowledge proof."
|
|
39409
|
+
}
|
|
39410
|
+
};
|
|
39411
|
+
}
|
|
39412
|
+
function verifySelectiveDisclosurePackage(receipt, disclosure) {
|
|
39413
|
+
const errors = [];
|
|
39414
|
+
if (disclosure.type !== "scopeblind.selective_disclosure.v0") {
|
|
39415
|
+
errors.push("disclosure.type is not scopeblind.selective_disclosure.v0");
|
|
39416
|
+
}
|
|
39417
|
+
const actualReceiptHash = receiptHashHex(receipt);
|
|
39418
|
+
const receiptHashValid = disclosure.parent_receipt_hash === actualReceiptHash;
|
|
39419
|
+
if (!receiptHashValid) {
|
|
39420
|
+
errors.push("parent_receipt_hash does not match the supplied receipt");
|
|
39421
|
+
}
|
|
39422
|
+
const root = typeof receipt.committed_fields_root === "string" ? receipt.committed_fields_root : "";
|
|
39423
|
+
const commitmentRootValid = Boolean(root) && disclosure.committed_fields_root === root;
|
|
39424
|
+
if (!commitmentRootValid) {
|
|
39425
|
+
errors.push("committed_fields_root does not match the supplied receipt");
|
|
39426
|
+
}
|
|
39427
|
+
const signatureValid = verifyCommittedReceiptSignature(receipt);
|
|
39428
|
+
if (signatureValid === false) {
|
|
39429
|
+
errors.push("receipt signature failed verification");
|
|
39430
|
+
}
|
|
39431
|
+
const committedFieldNames = committedFieldNamesFromReceipt(receipt, {});
|
|
39432
|
+
const disclosed = /* @__PURE__ */ new Set();
|
|
39433
|
+
for (const item of disclosure.disclosures || []) {
|
|
39434
|
+
if (item.parent_receipt_hash !== disclosure.parent_receipt_hash) {
|
|
39435
|
+
errors.push(`disclosure for "${item.name}" targets a different receipt hash`);
|
|
39436
|
+
continue;
|
|
39437
|
+
}
|
|
39438
|
+
if (!committedFieldNames.includes(item.name)) {
|
|
39439
|
+
errors.push(`field "${item.name}" is not listed in committed_field_names`);
|
|
39440
|
+
continue;
|
|
39441
|
+
}
|
|
39442
|
+
const leafBytes = encodeLeaf({
|
|
39443
|
+
name: item.name,
|
|
39444
|
+
salt: base64urlDecode(item.salt),
|
|
39445
|
+
value: item.value
|
|
39446
|
+
});
|
|
39447
|
+
const ok = root ? verifyProof(root, hashLeaf(leafBytes), item.proof) : false;
|
|
39448
|
+
if (!ok) {
|
|
39449
|
+
errors.push(`field "${item.name}" failed Merkle inclusion verification`);
|
|
39450
|
+
} else {
|
|
39451
|
+
disclosed.add(item.name);
|
|
39452
|
+
}
|
|
39453
|
+
}
|
|
39454
|
+
const disclosedFields = Array.from(disclosed);
|
|
39455
|
+
const hiddenFields = committedFieldNames.filter((fieldName) => !disclosed.has(fieldName));
|
|
39456
|
+
const valid = errors.length === 0 && receiptHashValid && commitmentRootValid && signatureValid !== false;
|
|
39457
|
+
const explanation = [
|
|
39458
|
+
valid ? "Selective disclosure verified: the disclosed fields open to the signed receipt commitment root." : "Selective disclosure failed verification.",
|
|
39459
|
+
signatureValid === true ? "Receipt signature verified against the embedded Ed25519 public key." : signatureValid === null ? "Receipt signature was not checked because the committed receipt did not carry an embedded Ed25519 signature object." : "Receipt signature did not verify.",
|
|
39460
|
+
disclosedFields.length ? `Disclosed fields: ${disclosedFields.join(", ")}.` : "No fields were disclosed.",
|
|
39461
|
+
hiddenFields.length ? `Hidden fields: ${hiddenFields.join(", ")}. These remain private but bound to the same commitment root.` : "No committed fields remain hidden.",
|
|
39462
|
+
"Limitation: this is salted commitment disclosure, not full zero-knowledge."
|
|
39463
|
+
];
|
|
39464
|
+
return {
|
|
39465
|
+
valid,
|
|
39466
|
+
receipt_hash_valid: receiptHashValid,
|
|
39467
|
+
signature_valid: signatureValid,
|
|
39468
|
+
commitment_root_valid: commitmentRootValid,
|
|
39469
|
+
disclosed_fields: disclosedFields,
|
|
39470
|
+
hidden_fields: hiddenFields,
|
|
39471
|
+
errors,
|
|
39472
|
+
explanation
|
|
39473
|
+
};
|
|
39474
|
+
}
|
|
39475
|
+
function committedFieldNamesFromReceipt(receipt, openings) {
|
|
39476
|
+
const fromReceipt = Array.isArray(receipt.committed_field_names) ? receipt.committed_field_names.filter((fieldName) => typeof fieldName === "string") : [];
|
|
39477
|
+
const names = fromReceipt.length ? fromReceipt : Object.keys(openings);
|
|
39478
|
+
return Array.from(new Set(names)).sort();
|
|
39479
|
+
}
|
|
39480
|
+
function receiptHashHex(receipt) {
|
|
39481
|
+
return bytesToHex(sha2562(new TextEncoder().encode(jcs(receipt))));
|
|
39482
|
+
}
|
|
39483
|
+
function verifyCommittedReceiptSignature(receipt) {
|
|
39484
|
+
const signature = receipt.signature;
|
|
39485
|
+
if (!signature || typeof signature !== "object") return null;
|
|
39486
|
+
const sig = signature;
|
|
39487
|
+
if (sig.alg !== "EdDSA" || typeof sig.sig !== "string" || typeof sig.public_key !== "string") {
|
|
39488
|
+
return null;
|
|
39489
|
+
}
|
|
39490
|
+
const { signature: _signature, ...payloadWithoutSig } = receipt;
|
|
39491
|
+
const messageHash = sha2562(new TextEncoder().encode(jcs(payloadWithoutSig)));
|
|
39492
|
+
try {
|
|
39493
|
+
return ed25519.verify(base64urlDecode(sig.sig), messageHash, hexToBytes(sig.public_key));
|
|
39494
|
+
} catch {
|
|
39495
|
+
return false;
|
|
39496
|
+
}
|
|
39497
|
+
}
|
|
39221
39498
|
|
|
39222
39499
|
// src/bundle.ts
|
|
39223
39500
|
function createAuditBundle(opts) {
|
|
39224
39501
|
const receipts = opts.receipts.filter(
|
|
39225
|
-
(r) => r && typeof r === "object" && typeof r.signature === "string"
|
|
39502
|
+
(r) => r && typeof r === "object" && (typeof r.signature === "string" || r.signature !== null && typeof r.signature === "object")
|
|
39226
39503
|
);
|
|
39227
39504
|
if (receipts.length === 0) {
|
|
39228
39505
|
throw new Error("Audit bundle requires at least one signed receipt");
|
|
@@ -39251,10 +39528,18 @@ function createAuditBundle(opts) {
|
|
|
39251
39528
|
time_range: timeRange,
|
|
39252
39529
|
receipts,
|
|
39253
39530
|
anchors: opts.anchors || [],
|
|
39531
|
+
selective_disclosures: opts.selectiveDisclosures || [],
|
|
39532
|
+
privacy: {
|
|
39533
|
+
selective_disclosure: {
|
|
39534
|
+
supported: true,
|
|
39535
|
+
model: "salted_commitments_merkle_v0",
|
|
39536
|
+
statement: "Committed receipts may disclose selected fields with salted Merkle openings. Undisclosed committed fields remain hidden while staying bound to the signed commitment root."
|
|
39537
|
+
}
|
|
39538
|
+
},
|
|
39254
39539
|
verification: {
|
|
39255
39540
|
algorithm: "ed25519",
|
|
39256
39541
|
signing_keys: Array.from(keyMap.values()),
|
|
39257
|
-
instructions: `Verify each receipt by: (1) remove the "signature" field, (2) canonicalize the remaining object with JCS (sorted keys at every level), (3) encode as UTF-8 bytes, (4) verify the Ed25519 signature using the signing key matching the receipt's "kid" field. CLI: npx @veritasacta/verify bundle.json --bundle`
|
|
39542
|
+
instructions: `Verify each receipt by: (1) remove the "signature" field, (2) canonicalize the remaining object with JCS (sorted keys at every level), (3) encode as UTF-8 bytes, (4) verify the Ed25519 signature using the signing key matching the receipt's "kid" field. For scopeblind.selective_disclosure.v0 packages, recompute each disclosed leaf and verify it against the receipt committed_fields_root; fields not disclosed remain hidden. CLI: npx @veritasacta/verify bundle.json --bundle`
|
|
39258
39543
|
}
|
|
39259
39544
|
};
|
|
39260
39545
|
}
|
|
@@ -39717,7 +40002,7 @@ function validateEvidenceReceipt(receipt) {
|
|
|
39717
40002
|
|
|
39718
40003
|
// src/hook-server.ts
|
|
39719
40004
|
var import_node_http2 = require("http");
|
|
39720
|
-
var
|
|
40005
|
+
var import_node_crypto5 = require("crypto");
|
|
39721
40006
|
var import_node_fs9 = require("fs");
|
|
39722
40007
|
var import_node_path5 = require("path");
|
|
39723
40008
|
|
|
@@ -39900,7 +40185,7 @@ function computePayloadDigest(input) {
|
|
|
39900
40185
|
return void 0;
|
|
39901
40186
|
}
|
|
39902
40187
|
return {
|
|
39903
|
-
input_hash: (0,
|
|
40188
|
+
input_hash: (0, import_node_crypto5.createHash)("sha256").update(content).digest("hex"),
|
|
39904
40189
|
input_size: size,
|
|
39905
40190
|
truncated: true,
|
|
39906
40191
|
preview: content.slice(0, 256)
|
|
@@ -39913,7 +40198,7 @@ function computeOutputDigest(output) {
|
|
|
39913
40198
|
return void 0;
|
|
39914
40199
|
}
|
|
39915
40200
|
return {
|
|
39916
|
-
output_hash: (0,
|
|
40201
|
+
output_hash: (0, import_node_crypto5.createHash)("sha256").update(content).digest("hex"),
|
|
39917
40202
|
output_size: size
|
|
39918
40203
|
};
|
|
39919
40204
|
}
|
|
@@ -39936,13 +40221,14 @@ function detectSandboxState() {
|
|
|
39936
40221
|
async function handlePreToolUse(input, state) {
|
|
39937
40222
|
const hookStart = Date.now();
|
|
39938
40223
|
const toolName = input.toolName || "unknown";
|
|
39939
|
-
const requestId = input.toolUseId || (0,
|
|
40224
|
+
const requestId = input.toolUseId || (0, import_node_crypto5.randomUUID)().slice(0, 12);
|
|
39940
40225
|
state.inflightTools.set(requestId, {
|
|
39941
40226
|
tool: toolName,
|
|
39942
40227
|
startedAt: hookStart,
|
|
39943
40228
|
requestId
|
|
39944
40229
|
});
|
|
39945
40230
|
const payloadDigest = computePayloadDigest(input.toolInput);
|
|
40231
|
+
const actionReadback = buildActionReadback(toolName, input.toolInput || {});
|
|
39946
40232
|
const swarm = {
|
|
39947
40233
|
...state.swarmContext,
|
|
39948
40234
|
...input.agentId && { agent_id: input.agentId },
|
|
@@ -39982,6 +40268,7 @@ async function handlePreToolUse(input, state) {
|
|
|
39982
40268
|
swarm: swarm.team_name ? swarm : void 0,
|
|
39983
40269
|
timing: { hook_latency_ms: hookLatency2, started_at: hookStart },
|
|
39984
40270
|
payload_digest: payloadDigest,
|
|
40271
|
+
action_readback: actionReadback,
|
|
39985
40272
|
deny_iteration: denyCount,
|
|
39986
40273
|
sandbox_state: detectSandboxState(),
|
|
39987
40274
|
plan_receipt_id: state.activePlanReceiptId || void 0
|
|
@@ -40016,6 +40303,7 @@ async function handlePreToolUse(input, state) {
|
|
|
40016
40303
|
swarm: swarm.team_name ? swarm : void 0,
|
|
40017
40304
|
timing: { hook_latency_ms: hookLatency2, started_at: hookStart },
|
|
40018
40305
|
payload_digest: payloadDigest,
|
|
40306
|
+
action_readback: actionReadback,
|
|
40019
40307
|
sandbox_state: detectSandboxState()
|
|
40020
40308
|
});
|
|
40021
40309
|
return {
|
|
@@ -40040,6 +40328,7 @@ async function handlePreToolUse(input, state) {
|
|
|
40040
40328
|
swarm: swarm.team_name ? swarm : void 0,
|
|
40041
40329
|
timing: { hook_latency_ms: hookLatency2, started_at: hookStart },
|
|
40042
40330
|
payload_digest: payloadDigest,
|
|
40331
|
+
action_readback: actionReadback,
|
|
40043
40332
|
sandbox_state: detectSandboxState()
|
|
40044
40333
|
});
|
|
40045
40334
|
return {
|
|
@@ -40060,13 +40349,15 @@ async function handlePreToolUse(input, state) {
|
|
|
40060
40349
|
hook_event: "PreToolUse",
|
|
40061
40350
|
swarm: swarm.team_name ? swarm : void 0,
|
|
40062
40351
|
timing: { hook_latency_ms: hookLatency2, started_at: hookStart },
|
|
40352
|
+
payload_digest: payloadDigest,
|
|
40353
|
+
action_readback: actionReadback,
|
|
40063
40354
|
sandbox_state: detectSandboxState()
|
|
40064
40355
|
});
|
|
40065
40356
|
return {
|
|
40066
40357
|
hookSpecificOutput: {
|
|
40067
40358
|
hookEventName: "PreToolUse",
|
|
40068
40359
|
permissionDecision: "ask",
|
|
40069
|
-
permissionDecisionReason: `[ScopeBlind]
|
|
40360
|
+
permissionDecisionReason: `[ScopeBlind] Approval required for exactly this action: ${actionReadback.summary}. Payload hash: ${actionReadback.payload_hash.slice(0, 16)}\u2026 Policy: ${state.policyDigest}`
|
|
40070
40361
|
}
|
|
40071
40362
|
};
|
|
40072
40363
|
}
|
|
@@ -40111,6 +40402,7 @@ async function handlePreToolUse(input, state) {
|
|
|
40111
40402
|
swarm: swarm.team_name ? swarm : void 0,
|
|
40112
40403
|
timing: { hook_latency_ms: hookLatency, started_at: hookStart },
|
|
40113
40404
|
payload_digest: payloadDigest,
|
|
40405
|
+
action_readback: actionReadback,
|
|
40114
40406
|
sandbox_state: detectSandboxState(),
|
|
40115
40407
|
plan_receipt_id: state.activePlanReceiptId || void 0
|
|
40116
40408
|
});
|
|
@@ -40127,7 +40419,7 @@ async function handlePreToolUse(input, state) {
|
|
|
40127
40419
|
}
|
|
40128
40420
|
async function handlePostToolUse(input, state) {
|
|
40129
40421
|
const toolName = input.toolName || "unknown";
|
|
40130
|
-
const requestId = input.toolUseId || (0,
|
|
40422
|
+
const requestId = input.toolUseId || (0, import_node_crypto5.randomUUID)().slice(0, 12);
|
|
40131
40423
|
const now = Date.now();
|
|
40132
40424
|
const inflight = state.inflightTools.get(requestId);
|
|
40133
40425
|
const timing = {
|
|
@@ -40139,7 +40431,7 @@ async function handlePostToolUse(input, state) {
|
|
|
40139
40431
|
state.inflightTools.delete(requestId);
|
|
40140
40432
|
}
|
|
40141
40433
|
const outputDigest = computeOutputDigest(input.toolResult);
|
|
40142
|
-
const receiptId = (0,
|
|
40434
|
+
const receiptId = (0, import_node_crypto5.randomUUID)().slice(0, 8);
|
|
40143
40435
|
const policyName = state.cedarPolicies ? `cedar:${state.policyDigest}` : state.policyDigest;
|
|
40144
40436
|
const additionalContext = `[ScopeBlind] Tool call receipted. Policy: ${policyName}. Decision: allow. Receipt: #${receiptId}.` + (timing.tool_duration_ms !== void 0 ? ` Duration: ${timing.tool_duration_ms}ms.` : "") + (timing.hook_latency_ms !== void 0 ? ` Overhead: ${timing.hook_latency_ms}ms.` : "");
|
|
40145
40437
|
emitDecisionLog(state, {
|
|
@@ -40171,7 +40463,7 @@ function handleSubagentStart(input, state) {
|
|
|
40171
40463
|
tool: `subagent:${agentId}`,
|
|
40172
40464
|
decision: "allow",
|
|
40173
40465
|
reason_code: "subagent_started",
|
|
40174
|
-
request_id: (0,
|
|
40466
|
+
request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
|
|
40175
40467
|
hook_event: "SubagentStart",
|
|
40176
40468
|
swarm: {
|
|
40177
40469
|
...state.swarmContext,
|
|
@@ -40192,7 +40484,7 @@ function handleSubagentStop(input, state) {
|
|
|
40192
40484
|
tool: `subagent:${agentId}`,
|
|
40193
40485
|
decision: "allow",
|
|
40194
40486
|
reason_code: "subagent_stopped",
|
|
40195
|
-
request_id: (0,
|
|
40487
|
+
request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
|
|
40196
40488
|
hook_event: "SubagentStop",
|
|
40197
40489
|
swarm: {
|
|
40198
40490
|
...state.swarmContext,
|
|
@@ -40207,7 +40499,7 @@ function handleTaskCreated(input, state) {
|
|
|
40207
40499
|
tool: `task:${input.taskId || "unknown"}`,
|
|
40208
40500
|
decision: "allow",
|
|
40209
40501
|
reason_code: "task_created",
|
|
40210
|
-
request_id: (0,
|
|
40502
|
+
request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
|
|
40211
40503
|
hook_event: "TaskCreated",
|
|
40212
40504
|
swarm: {
|
|
40213
40505
|
...state.swarmContext,
|
|
@@ -40221,7 +40513,7 @@ function handleTaskCompleted(input, state) {
|
|
|
40221
40513
|
tool: `task:${input.taskId || "unknown"}`,
|
|
40222
40514
|
decision: "allow",
|
|
40223
40515
|
reason_code: "task_completed",
|
|
40224
|
-
request_id: (0,
|
|
40516
|
+
request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
|
|
40225
40517
|
hook_event: "TaskCompleted",
|
|
40226
40518
|
swarm: state.swarmContext
|
|
40227
40519
|
});
|
|
@@ -40232,7 +40524,7 @@ function handleSessionStart(input, state) {
|
|
|
40232
40524
|
tool: "session",
|
|
40233
40525
|
decision: "allow",
|
|
40234
40526
|
reason_code: "session_started",
|
|
40235
|
-
request_id: input.sessionId || (0,
|
|
40527
|
+
request_id: input.sessionId || (0, import_node_crypto5.randomUUID)().slice(0, 12),
|
|
40236
40528
|
hook_event: "SessionStart",
|
|
40237
40529
|
swarm: state.swarmContext,
|
|
40238
40530
|
sandbox_state: detectSandboxState()
|
|
@@ -40255,7 +40547,7 @@ function handleSessionEnd(input, state) {
|
|
|
40255
40547
|
tool: "session",
|
|
40256
40548
|
decision: "allow",
|
|
40257
40549
|
reason_code: "session_ended",
|
|
40258
|
-
request_id: input.sessionId || (0,
|
|
40550
|
+
request_id: input.sessionId || (0, import_node_crypto5.randomUUID)().slice(0, 12),
|
|
40259
40551
|
hook_event: "SessionEnd",
|
|
40260
40552
|
swarm: state.swarmContext
|
|
40261
40553
|
});
|
|
@@ -40266,7 +40558,7 @@ function handleTeammateIdle(input, state) {
|
|
|
40266
40558
|
tool: `teammate:${input.agentId || "unknown"}`,
|
|
40267
40559
|
decision: "allow",
|
|
40268
40560
|
reason_code: "teammate_idle",
|
|
40269
|
-
request_id: (0,
|
|
40561
|
+
request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
|
|
40270
40562
|
hook_event: "TeammateIdle",
|
|
40271
40563
|
swarm: {
|
|
40272
40564
|
...state.swarmContext,
|
|
@@ -40294,7 +40586,7 @@ function handleConfigChange(input, state) {
|
|
|
40294
40586
|
tool: "config",
|
|
40295
40587
|
decision: "deny",
|
|
40296
40588
|
reason_code: "config_tamper_detected",
|
|
40297
|
-
request_id: (0,
|
|
40589
|
+
request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
|
|
40298
40590
|
hook_event: "ConfigChange",
|
|
40299
40591
|
swarm: state.swarmContext
|
|
40300
40592
|
});
|
|
@@ -40303,7 +40595,7 @@ function handleConfigChange(input, state) {
|
|
|
40303
40595
|
tool: "config",
|
|
40304
40596
|
decision: "allow",
|
|
40305
40597
|
reason_code: "config_changed",
|
|
40306
|
-
request_id: (0,
|
|
40598
|
+
request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
|
|
40307
40599
|
hook_event: "ConfigChange"
|
|
40308
40600
|
});
|
|
40309
40601
|
}
|
|
@@ -40325,7 +40617,7 @@ function handleStop(input, state) {
|
|
|
40325
40617
|
tool: "session",
|
|
40326
40618
|
decision: "allow",
|
|
40327
40619
|
reason_code: "agent_stopped",
|
|
40328
|
-
request_id: (0,
|
|
40620
|
+
request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
|
|
40329
40621
|
hook_event: "Stop",
|
|
40330
40622
|
swarm: state.swarmContext
|
|
40331
40623
|
});
|
|
@@ -40333,8 +40625,8 @@ function handleStop(input, state) {
|
|
|
40333
40625
|
}
|
|
40334
40626
|
function emitDecisionLog(state, entry) {
|
|
40335
40627
|
const mode = state.enforce ? "enforce" : "shadow";
|
|
40336
|
-
const otelTraceId = (0,
|
|
40337
|
-
const otelSpanId = (0,
|
|
40628
|
+
const otelTraceId = (0, import_node_crypto5.randomBytes)(16).toString("hex");
|
|
40629
|
+
const otelSpanId = (0, import_node_crypto5.randomBytes)(8).toString("hex");
|
|
40338
40630
|
const log = {
|
|
40339
40631
|
v: 2,
|
|
40340
40632
|
tool: entry.tool || "unknown",
|
|
@@ -40342,7 +40634,7 @@ function emitDecisionLog(state, entry) {
|
|
|
40342
40634
|
reason_code: entry.reason_code || "default_allow",
|
|
40343
40635
|
policy_digest: state.policyDigest,
|
|
40344
40636
|
policy_engine: state.cedarPolicies ? "cedar" : "built-in",
|
|
40345
|
-
request_id: entry.request_id || (0,
|
|
40637
|
+
request_id: entry.request_id || (0, import_node_crypto5.randomUUID)().slice(0, 12),
|
|
40346
40638
|
timestamp: Date.now(),
|
|
40347
40639
|
mode,
|
|
40348
40640
|
otel_trace_id: otelTraceId,
|
|
@@ -41321,8 +41613,515 @@ function generateSchemaStub(namespace = "ScopeBlind") {
|
|
|
41321
41613
|
].join("\n");
|
|
41322
41614
|
}
|
|
41323
41615
|
|
|
41616
|
+
// src/policy-packs.ts
|
|
41617
|
+
var header = (id, description) => `// ScopeBlind protect-mcp policy pack: ${id}
|
|
41618
|
+
// ${description}
|
|
41619
|
+
// Start in shadow mode, review receipts, then run with --enforce.
|
|
41620
|
+
|
|
41621
|
+
`;
|
|
41622
|
+
var defaultPermit = `
|
|
41623
|
+
// Default posture: allow non-matching calls so teams can start in shadow mode.
|
|
41624
|
+
// Tighten this after reviewing your local action dashboard.
|
|
41625
|
+
permit(principal, action == Action::"MCP::Tool::call", resource);
|
|
41626
|
+
`;
|
|
41627
|
+
var filesystemSafe = `${header("filesystem-safe", "Block common destructive filesystem and secret-file access patterns.")}// Destructive file tools are never safe as an unattended default.
|
|
41628
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"delete_file");
|
|
41629
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"remove_file");
|
|
41630
|
+
|
|
41631
|
+
// Secret-like reads by path.
|
|
41632
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource) when {
|
|
41633
|
+
context has "input" && context.input has "path" && (
|
|
41634
|
+
context.input.path like "*/.env*" ||
|
|
41635
|
+
context.input.path like "*/id_rsa*" ||
|
|
41636
|
+
context.input.path like "*/.ssh/*" ||
|
|
41637
|
+
context.input.path like "*secret*" ||
|
|
41638
|
+
context.input.path like "*credential*"
|
|
41639
|
+
)
|
|
41640
|
+
};
|
|
41641
|
+
|
|
41642
|
+
// Dangerous shell operations that mutate or destroy local state.
|
|
41643
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"Bash") when {
|
|
41644
|
+
context has "command" && (
|
|
41645
|
+
context.command like "*rm -rf*" ||
|
|
41646
|
+
context.command like "*mkfs*" ||
|
|
41647
|
+
context.command like "*dd if=*" ||
|
|
41648
|
+
context.command like "*chmod -R 777*" ||
|
|
41649
|
+
context.command like "*chown -R*"
|
|
41650
|
+
)
|
|
41651
|
+
};
|
|
41652
|
+
${defaultPermit}`;
|
|
41653
|
+
var gitSafe = `${header("git-safe", "Prevent unattended history rewrites, force pushes, and destructive repo cleanup.")}forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"Bash") when {
|
|
41654
|
+
context has "command" && (
|
|
41655
|
+
context.command like "*git push --force*" ||
|
|
41656
|
+
context.command like "*git push -f*" ||
|
|
41657
|
+
context.command like "*git reset --hard*" ||
|
|
41658
|
+
context.command like "*git clean -fd*" ||
|
|
41659
|
+
context.command like "*git checkout --*" ||
|
|
41660
|
+
context.command like "*git branch -D*" ||
|
|
41661
|
+
context.command like "*gh repo delete*"
|
|
41662
|
+
)
|
|
41663
|
+
};
|
|
41664
|
+
${defaultPermit}`;
|
|
41665
|
+
var emailSafe = `${header("email-safe", "Permit drafting but block unattended external sends.")}forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"mail.send");
|
|
41666
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"email.send");
|
|
41667
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"send_email");
|
|
41668
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"gmail.send");
|
|
41669
|
+
|
|
41670
|
+
// Shell fallbacks that send mail are blocked too.
|
|
41671
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"Bash") when {
|
|
41672
|
+
context has "command" && (
|
|
41673
|
+
context.command like "*sendmail*" ||
|
|
41674
|
+
context.command like "*mailx*" ||
|
|
41675
|
+
context.command like "*smtp*"
|
|
41676
|
+
)
|
|
41677
|
+
};
|
|
41678
|
+
${defaultPermit}`;
|
|
41679
|
+
var databaseSafe = `${header("database-safe", "Allow reads, block write/admin SQL unless explicitly approved elsewhere.")}forbid(principal, action == Action::"MCP::Tool::call", resource) when {
|
|
41680
|
+
context has "input" && context.input has "query" && (
|
|
41681
|
+
context.input.query like "*DROP *" ||
|
|
41682
|
+
context.input.query like "*TRUNCATE *" ||
|
|
41683
|
+
context.input.query like "*DELETE *" ||
|
|
41684
|
+
context.input.query like "*UPDATE *" ||
|
|
41685
|
+
context.input.query like "*INSERT *" ||
|
|
41686
|
+
context.input.query like "*ALTER *" ||
|
|
41687
|
+
context.input.query like "*GRANT *" ||
|
|
41688
|
+
context.input.query like "*REVOKE *"
|
|
41689
|
+
)
|
|
41690
|
+
};
|
|
41691
|
+
${defaultPermit}`;
|
|
41692
|
+
var cloudSpendSafe = `${header("cloud-spend-safe", "Block cloud actions that can create spend or destroy infrastructure.")}forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"Bash") when {
|
|
41693
|
+
context has "command" && (
|
|
41694
|
+
context.command like "*terraform destroy*" ||
|
|
41695
|
+
context.command like "*terraform apply*" ||
|
|
41696
|
+
context.command like "*pulumi up*" ||
|
|
41697
|
+
context.command like "*pulumi destroy*" ||
|
|
41698
|
+
context.command like "*aws ec2 run-instances*" ||
|
|
41699
|
+
context.command like "*aws rds create*" ||
|
|
41700
|
+
context.command like "*gcloud compute instances create*" ||
|
|
41701
|
+
context.command like "*az vm create*" ||
|
|
41702
|
+
context.command like "*kubectl delete*"
|
|
41703
|
+
)
|
|
41704
|
+
};
|
|
41705
|
+
${defaultPermit}`;
|
|
41706
|
+
var secretsSafe = `${header("secrets-safe", "Block secret exfiltration from files, env, shell, and common credential tools.")}forbid(principal, action == Action::"MCP::Tool::call", resource) when {
|
|
41707
|
+
context has "input" && context.input has "path" && (
|
|
41708
|
+
context.input.path like "*/.env*" ||
|
|
41709
|
+
context.input.path like "*/.aws/credentials*" ||
|
|
41710
|
+
context.input.path like "*/.npmrc*" ||
|
|
41711
|
+
context.input.path like "*/.netrc*" ||
|
|
41712
|
+
context.input.path like "*/id_rsa*" ||
|
|
41713
|
+
context.input.path like "*secret*" ||
|
|
41714
|
+
context.input.path like "*token*"
|
|
41715
|
+
)
|
|
41716
|
+
};
|
|
41717
|
+
|
|
41718
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"Bash") when {
|
|
41719
|
+
context has "command" && (
|
|
41720
|
+
context.command like "*printenv*" ||
|
|
41721
|
+
context.command like "*env |*" ||
|
|
41722
|
+
context.command like "*security find-generic-password*" ||
|
|
41723
|
+
context.command like "*aws secretsmanager get-secret-value*" ||
|
|
41724
|
+
context.command like "*gcloud secrets versions access*" ||
|
|
41725
|
+
context.command like "*op read*"
|
|
41726
|
+
)
|
|
41727
|
+
};
|
|
41728
|
+
${defaultPermit}`;
|
|
41729
|
+
var financeMandateSafe = `${header("finance-mandate-safe", "Block restricted-list and concentration-limit breaches in booking tools.")}forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"pms.book") when {
|
|
41730
|
+
context has "input" && context.input has "on_restricted_list" && context.input.on_restricted_list == true
|
|
41731
|
+
};
|
|
41732
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"booking.execute") when {
|
|
41733
|
+
context has "input" && context.input has "on_restricted_list" && context.input.on_restricted_list == true
|
|
41734
|
+
};
|
|
41735
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource == Tool::"booking.ticket") when {
|
|
41736
|
+
context has "input" && context.input has "on_restricted_list" && context.input.on_restricted_list == true
|
|
41737
|
+
};
|
|
41738
|
+
|
|
41739
|
+
// Default example caps: single-name > 10%, gross > 200%, net > 100%.
|
|
41740
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource) when {
|
|
41741
|
+
context has "input" && context.input has "post_trade_weight_bps" && context.input.post_trade_weight_bps > 1000
|
|
41742
|
+
};
|
|
41743
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource) when {
|
|
41744
|
+
context has "input" && context.input has "post_trade_gross_exposure_bps" && context.input.post_trade_gross_exposure_bps > 20000
|
|
41745
|
+
};
|
|
41746
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource) when {
|
|
41747
|
+
context has "input" && context.input has "post_trade_net_exposure_bps" && context.input.post_trade_net_exposure_bps > 10000
|
|
41748
|
+
};
|
|
41749
|
+
${defaultPermit}`;
|
|
41750
|
+
var POLICY_PACKS = [
|
|
41751
|
+
{
|
|
41752
|
+
id: "filesystem-safe",
|
|
41753
|
+
name: "Filesystem Safe",
|
|
41754
|
+
description: "Blocks destructive filesystem calls and secret-like path reads.",
|
|
41755
|
+
recommendedMode: "shadow-first",
|
|
41756
|
+
files: [{ path: "filesystem-safe.cedar", contents: filesystemSafe }]
|
|
41757
|
+
},
|
|
41758
|
+
{
|
|
41759
|
+
id: "git-safe",
|
|
41760
|
+
name: "Git Safe",
|
|
41761
|
+
description: "Blocks force pushes, hard resets, destructive cleanup, and repo deletion.",
|
|
41762
|
+
recommendedMode: "shadow-first",
|
|
41763
|
+
files: [{ path: "git-safe.cedar", contents: gitSafe }]
|
|
41764
|
+
},
|
|
41765
|
+
{
|
|
41766
|
+
id: "email-safe",
|
|
41767
|
+
name: "Email Safe",
|
|
41768
|
+
description: "Allows drafting workflows while blocking unattended sends.",
|
|
41769
|
+
recommendedMode: "shadow-first",
|
|
41770
|
+
files: [{ path: "email-safe.cedar", contents: emailSafe }]
|
|
41771
|
+
},
|
|
41772
|
+
{
|
|
41773
|
+
id: "database-safe",
|
|
41774
|
+
name: "Database Safe",
|
|
41775
|
+
description: "Allows read-oriented DB tools while blocking mutating/admin SQL.",
|
|
41776
|
+
recommendedMode: "shadow-first",
|
|
41777
|
+
files: [{ path: "database-safe.cedar", contents: databaseSafe }]
|
|
41778
|
+
},
|
|
41779
|
+
{
|
|
41780
|
+
id: "cloud-spend-safe",
|
|
41781
|
+
name: "Cloud Spend Safe",
|
|
41782
|
+
description: "Blocks obvious cloud spend creation and infrastructure destruction.",
|
|
41783
|
+
recommendedMode: "shadow-first",
|
|
41784
|
+
files: [{ path: "cloud-spend-safe.cedar", contents: cloudSpendSafe }]
|
|
41785
|
+
},
|
|
41786
|
+
{
|
|
41787
|
+
id: "secrets-safe",
|
|
41788
|
+
name: "Secrets Safe",
|
|
41789
|
+
description: "Blocks common file, env, shell, and cloud secret exfiltration paths.",
|
|
41790
|
+
recommendedMode: "enforce-ready",
|
|
41791
|
+
files: [{ path: "secrets-safe.cedar", contents: secretsSafe }]
|
|
41792
|
+
},
|
|
41793
|
+
{
|
|
41794
|
+
id: "finance-mandate-safe",
|
|
41795
|
+
name: "Finance Mandate Safe",
|
|
41796
|
+
description: "Blocks restricted-list and concentration breaches in booking flows.",
|
|
41797
|
+
recommendedMode: "shadow-first",
|
|
41798
|
+
files: [{ path: "finance-mandate-safe.cedar", contents: financeMandateSafe }]
|
|
41799
|
+
}
|
|
41800
|
+
];
|
|
41801
|
+
function getPolicyPack(id) {
|
|
41802
|
+
return POLICY_PACKS.find((pack) => pack.id === id);
|
|
41803
|
+
}
|
|
41804
|
+
function policyPackIds() {
|
|
41805
|
+
return POLICY_PACKS.map((pack) => pack.id);
|
|
41806
|
+
}
|
|
41807
|
+
|
|
41808
|
+
// src/connector-pilots.ts
|
|
41809
|
+
var import_node_fs10 = require("fs");
|
|
41810
|
+
var import_node_path6 = require("path");
|
|
41811
|
+
var defaultPermit2 = `
|
|
41812
|
+
// Default posture: observe all non-matching tools so the connector can be piloted in shadow mode.
|
|
41813
|
+
permit(principal, action == Action::"MCP::Tool::call", resource);
|
|
41814
|
+
`;
|
|
41815
|
+
var CONNECTOR_PILOTS = [
|
|
41816
|
+
{
|
|
41817
|
+
id: "github",
|
|
41818
|
+
category: "code",
|
|
41819
|
+
name: "GitHub pull-request control",
|
|
41820
|
+
status: "usable-pilot",
|
|
41821
|
+
description: "Controls GitHub REST/MCP calls for issue, PR, branch, and workflow actions.",
|
|
41822
|
+
value: "Useful when agents already have repo access through GitHub MCP, gh, or a GitHub-backed tool server.",
|
|
41823
|
+
env: [
|
|
41824
|
+
{ name: "GITHUB_TOKEN", required: true, description: "Fine-grained token scoped to the pilot repo." },
|
|
41825
|
+
{ name: "GITHUB_REPOSITORY", required: true, description: "owner/repo target for the pilot." }
|
|
41826
|
+
],
|
|
41827
|
+
tools: ["github.rest.request", "github.issue.create", "github.pull_request.merge", "github.workflow.dispatch"],
|
|
41828
|
+
actions: [
|
|
41829
|
+
{ name: "Read repo metadata", tool: "github.rest.request", risk: "low", mode: "observe", description: "GET-only repository and PR inspection." },
|
|
41830
|
+
{ name: "Create issue or comment", tool: "github.issue.create", risk: "medium", mode: "require_approval", description: "External write to the system of record." },
|
|
41831
|
+
{ name: "Merge PR / dispatch workflow", tool: "github.pull_request.merge", risk: "high", mode: "require_approval", description: "Code-changing or CI-triggering action." }
|
|
41832
|
+
],
|
|
41833
|
+
setup: [
|
|
41834
|
+
"Create a fine-grained GitHub token for one repository.",
|
|
41835
|
+
"Set GITHUB_TOKEN and GITHUB_REPOSITORY.",
|
|
41836
|
+
"Run the agent through protect-mcp and review GitHub tool calls in the dashboard."
|
|
41837
|
+
],
|
|
41838
|
+
config: {
|
|
41839
|
+
type: "scopeblind.connector_pilot.v1",
|
|
41840
|
+
provider: "github",
|
|
41841
|
+
target_env: ["GITHUB_TOKEN", "GITHUB_REPOSITORY"],
|
|
41842
|
+
safe_read_probe: "GET /repos/{GITHUB_REPOSITORY}",
|
|
41843
|
+
controlled_tools: ["github.rest.request", "github.issue.create", "github.pull_request.merge", "github.workflow.dispatch"],
|
|
41844
|
+
approval_required_for: ["POST", "PATCH", "PUT", "DELETE", "merge", "workflow_dispatch"],
|
|
41845
|
+
receipt_fields: ["method", "path", "repo", "actor", "payload_hash", "approval_reason"]
|
|
41846
|
+
},
|
|
41847
|
+
cedar: `${defaultPermit2}
|
|
41848
|
+
// GitHub pilot: reads are observed; writes and merges need exact-action approval.
|
|
41849
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource)
|
|
41850
|
+
when { context.tool == "github.pull_request.merge" };
|
|
41851
|
+
|
|
41852
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource)
|
|
41853
|
+
when { context.tool == "github.workflow.dispatch" && !context.approved };
|
|
41854
|
+
|
|
41855
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource)
|
|
41856
|
+
when { context.tool == "github.issue.create" && !context.approved };
|
|
41857
|
+
`
|
|
41858
|
+
},
|
|
41859
|
+
{
|
|
41860
|
+
id: "email-gmail",
|
|
41861
|
+
category: "communications",
|
|
41862
|
+
name: "Gmail self-send / draft approval",
|
|
41863
|
+
status: "usable-pilot",
|
|
41864
|
+
description: "Uses the existing Gmail OAuth connector path and restricts send mode to email.self for the first production pilot.",
|
|
41865
|
+
value: "Makes external communications reviewable before an agent can send mail.",
|
|
41866
|
+
env: [
|
|
41867
|
+
{ name: "GOOGLE_CLIENT_ID", required: true, description: "OAuth client for Gmail." },
|
|
41868
|
+
{ name: "GOOGLE_CLIENT_SECRET", required: true, description: "OAuth client secret." },
|
|
41869
|
+
{ name: "CONNECTOR_TOKEN_KEY", required: true, description: "AES-GCM key material for sealed connector tokens." }
|
|
41870
|
+
],
|
|
41871
|
+
tools: ["gmail.draft.create", "gmail.send.email_self", "email.send"],
|
|
41872
|
+
actions: [
|
|
41873
|
+
{ name: "Create draft", tool: "gmail.draft.create", risk: "medium", mode: "require_approval", description: "Draft content can leak sensitive information." },
|
|
41874
|
+
{ name: "Self-send test", tool: "gmail.send.email_self", risk: "medium", mode: "require_approval", description: "First release allows only sending to the account owner." },
|
|
41875
|
+
{ name: "External send", tool: "email.send", risk: "high", mode: "deny", description: "Direct external send stays blocked until a customer-specific allowlist exists." }
|
|
41876
|
+
],
|
|
41877
|
+
setup: [
|
|
41878
|
+
"Configure Google OAuth redirect /fn/connectors/gmail/callback.",
|
|
41879
|
+
"Connect Gmail through the hosted console or local connector flow.",
|
|
41880
|
+
"Keep send mode to email.self until the customer approves recipient allowlists."
|
|
41881
|
+
],
|
|
41882
|
+
config: {
|
|
41883
|
+
type: "scopeblind.connector_pilot.v1",
|
|
41884
|
+
provider: "gmail",
|
|
41885
|
+
hosted_functions: ["/fn/connectors/gmail/start", "/fn/connectors/gmail/callback", "/fn/connectors/gmail/send", "/fn/connectors/gmail/status"],
|
|
41886
|
+
first_release_scope: "email.self",
|
|
41887
|
+
denied_until_configured: ["email.send.external", "email.bulk_send"],
|
|
41888
|
+
receipt_fields: ["to_hash", "subject_hash", "body_hash", "approval_reason", "gmail_message_id"]
|
|
41889
|
+
},
|
|
41890
|
+
cedar: `${defaultPermit2}
|
|
41891
|
+
// Email pilot: no direct external send. Draft/self-send require exact approval.
|
|
41892
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource)
|
|
41893
|
+
when { context.tool == "email.send" };
|
|
41894
|
+
|
|
41895
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource)
|
|
41896
|
+
when { context.tool == "gmail.draft.create" && !context.approved };
|
|
41897
|
+
|
|
41898
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource)
|
|
41899
|
+
when { context.tool == "gmail.send.email_self" && !context.approved };
|
|
41900
|
+
`
|
|
41901
|
+
},
|
|
41902
|
+
{
|
|
41903
|
+
id: "filesystem-git",
|
|
41904
|
+
category: "local-computer",
|
|
41905
|
+
name: "Filesystem and Git control",
|
|
41906
|
+
status: "usable-pilot",
|
|
41907
|
+
description: "Controls reads, writes, shell commands, and Git mutation in the local project.",
|
|
41908
|
+
value: "Immediately useful with Claude Code, Codex, Cursor, and any agent that edits files or runs shell commands.",
|
|
41909
|
+
env: [],
|
|
41910
|
+
tools: ["Read", "Write", "Edit", "MultiEdit", "Bash", "git.commit", "git.push"],
|
|
41911
|
+
actions: [
|
|
41912
|
+
{ name: "Read files", tool: "Read", risk: "low", mode: "observe", description: "Observe file reads for audit context." },
|
|
41913
|
+
{ name: "Write/edit files", tool: "Write", risk: "medium", mode: "require_approval", description: "Require approval for sensitive paths or broad rewrites." },
|
|
41914
|
+
{ name: "Git push/reset", tool: "Bash", risk: "high", mode: "require_approval", description: "Commands that publish, reset, or delete require exact-action approval." }
|
|
41915
|
+
],
|
|
41916
|
+
setup: [
|
|
41917
|
+
"Run protect-mcp init-hooks in the project.",
|
|
41918
|
+
"Install filesystem-safe and Git-safe policy packs.",
|
|
41919
|
+
"Review the dashboard before turning on enforce mode."
|
|
41920
|
+
],
|
|
41921
|
+
config: {
|
|
41922
|
+
type: "scopeblind.connector_pilot.v1",
|
|
41923
|
+
provider: "filesystem-git",
|
|
41924
|
+
local_only: true,
|
|
41925
|
+
protected_paths: [".env", ".ssh", "keys/", "secrets/", "node_modules/"],
|
|
41926
|
+
dangerous_command_patterns: ["rm -rf", "git push", "git reset --hard", "curl | sh", "chmod 777"],
|
|
41927
|
+
receipt_fields: ["tool", "path_hash", "command_hash", "diff_hash", "approval_reason"]
|
|
41928
|
+
},
|
|
41929
|
+
cedar: `${defaultPermit2}
|
|
41930
|
+
// Filesystem/Git pilot: dangerous shell and protected-path writes need approval.
|
|
41931
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource)
|
|
41932
|
+
when { context.tool == "Bash" && context.command_pattern.contains("git reset --hard") && !context.approved };
|
|
41933
|
+
|
|
41934
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource)
|
|
41935
|
+
when { context.tool == "Bash" && context.command_pattern.contains("git push") && !context.approved };
|
|
41936
|
+
|
|
41937
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource)
|
|
41938
|
+
when { ["Write", "Edit", "MultiEdit"].contains(context.tool) && context.path.contains(".env") && !context.approved };
|
|
41939
|
+
`
|
|
41940
|
+
},
|
|
41941
|
+
{
|
|
41942
|
+
id: "slack-teams",
|
|
41943
|
+
category: "communications",
|
|
41944
|
+
name: "Slack or Teams outbound approval",
|
|
41945
|
+
status: "usable-pilot",
|
|
41946
|
+
description: "Controls messages to Slack channels or Microsoft Teams webhooks.",
|
|
41947
|
+
value: "Makes high-impact internal broadcasts and client channels approval-gated.",
|
|
41948
|
+
env: [
|
|
41949
|
+
{ name: "SLACK_BOT_TOKEN", required: false, description: "Slack bot token for chat.postMessage pilots." },
|
|
41950
|
+
{ name: "SLACK_CHANNEL_ID", required: false, description: "Default Slack channel for the pilot." },
|
|
41951
|
+
{ name: "TEAMS_WEBHOOK_URL", required: false, description: "Teams incoming webhook URL if Teams is preferred." }
|
|
41952
|
+
],
|
|
41953
|
+
tools: ["slack.chat.postMessage", "slack.files.upload", "teams.webhook.post"],
|
|
41954
|
+
actions: [
|
|
41955
|
+
{ name: "Post internal message", tool: "slack.chat.postMessage", risk: "medium", mode: "require_approval", description: "Message text and channel are read back before send." },
|
|
41956
|
+
{ name: "Upload file", tool: "slack.files.upload", risk: "high", mode: "require_approval", description: "Files can leak customer data and need explicit approval." },
|
|
41957
|
+
{ name: "Teams webhook post", tool: "teams.webhook.post", risk: "medium", mode: "require_approval", description: "Webhook destination and payload hash are receipted." }
|
|
41958
|
+
],
|
|
41959
|
+
setup: [
|
|
41960
|
+
"Choose Slack or Teams for the first pilot, not both.",
|
|
41961
|
+
"Set the relevant token/webhook environment variables.",
|
|
41962
|
+
"Start with a private test channel and exact-action approval for every send."
|
|
41963
|
+
],
|
|
41964
|
+
config: {
|
|
41965
|
+
type: "scopeblind.connector_pilot.v1",
|
|
41966
|
+
provider: "slack-or-teams",
|
|
41967
|
+
supported_modes: ["slack.chat.postMessage", "teams.webhook.post"],
|
|
41968
|
+
require_channel_allowlist: true,
|
|
41969
|
+
receipt_fields: ["channel_hash", "message_hash", "file_hash", "approval_reason", "provider_message_id"]
|
|
41970
|
+
},
|
|
41971
|
+
cedar: `${defaultPermit2}
|
|
41972
|
+
// Slack/Teams pilot: all outbound posts and uploads require approval by default.
|
|
41973
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource)
|
|
41974
|
+
when { ["slack.chat.postMessage", "slack.files.upload", "teams.webhook.post"].contains(context.tool) && !context.approved };
|
|
41975
|
+
`
|
|
41976
|
+
},
|
|
41977
|
+
{
|
|
41978
|
+
id: "finance-pms",
|
|
41979
|
+
category: "finance",
|
|
41980
|
+
name: "Finance PMS mock-to-real adapter",
|
|
41981
|
+
status: "usable-pilot",
|
|
41982
|
+
description: "Stages orders into a PMS adapter contract, with mock mode locally and real mode through PMS_ADAPTER_URL.",
|
|
41983
|
+
value: "Gives hedge funds the controlled booking path: parse, mandate-check, approve, book, corroborate, receipt.",
|
|
41984
|
+
env: [
|
|
41985
|
+
{ name: "PMS_ADAPTER_URL", required: false, description: "Customer-owned adapter endpoint. Omit for local mock mode." },
|
|
41986
|
+
{ name: "PMS_ADAPTER_TOKEN", required: false, description: "Bearer token for the customer-owned PMS adapter." }
|
|
41987
|
+
],
|
|
41988
|
+
tools: ["pms.order.stage", "pms.order.book", "pms.order.cancel", "pms.reconcile"],
|
|
41989
|
+
actions: [
|
|
41990
|
+
{ name: "Stage order", tool: "pms.order.stage", risk: "medium", mode: "require_approval", description: "Creates a booking ticket but does not execute." },
|
|
41991
|
+
{ name: "Book order", tool: "pms.order.book", risk: "high", mode: "require_approval", description: "Must pass mandate checks and human readback." },
|
|
41992
|
+
{ name: "Cancel/order correction", tool: "pms.order.cancel", risk: "high", mode: "require_approval", description: "Mutates book state and requires approval." }
|
|
41993
|
+
],
|
|
41994
|
+
setup: [
|
|
41995
|
+
"Run local mock mode first with the Legate finance pilot pack.",
|
|
41996
|
+
"Point PMS_ADAPTER_URL at a customer-owned bridge when ready.",
|
|
41997
|
+
"Require mandate checks and exact-action approval before pms.order.book."
|
|
41998
|
+
],
|
|
41999
|
+
config: {
|
|
42000
|
+
type: "scopeblind.connector_pilot.v1",
|
|
42001
|
+
provider: "finance-pms",
|
|
42002
|
+
mode: "mock-first",
|
|
42003
|
+
adapter_contract: {
|
|
42004
|
+
stage: "POST /orders/stage",
|
|
42005
|
+
book: "POST /orders/book",
|
|
42006
|
+
cancel: "POST /orders/{client_order_id}/cancel",
|
|
42007
|
+
reconcile: "GET /orders/{client_order_id}"
|
|
42008
|
+
},
|
|
42009
|
+
receipt_fields: ["client_order_id", "side", "symbol_hash", "qty", "price", "mandate_digest", "approval_reason", "external_confirmation_hash"]
|
|
42010
|
+
},
|
|
42011
|
+
cedar: `${defaultPermit2}
|
|
42012
|
+
// Finance/PMS pilot: booking actions require mandate pass and exact approval.
|
|
42013
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource)
|
|
42014
|
+
when { ["pms.order.stage", "pms.order.book", "pms.order.cancel"].contains(context.tool) && !context.approved };
|
|
42015
|
+
|
|
42016
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource)
|
|
42017
|
+
when { context.tool == "pms.order.book" && context.mandate_passed != true };
|
|
42018
|
+
`
|
|
42019
|
+
}
|
|
42020
|
+
];
|
|
42021
|
+
function connectorPilotIds() {
|
|
42022
|
+
return CONNECTOR_PILOTS.map((pilot) => pilot.id);
|
|
42023
|
+
}
|
|
42024
|
+
function getConnectorPilot(id) {
|
|
42025
|
+
return CONNECTOR_PILOTS.find((pilot) => pilot.id === id);
|
|
42026
|
+
}
|
|
42027
|
+
function connectorDirectory(dir) {
|
|
42028
|
+
return (0, import_node_path6.join)(dir, ".protect-mcp", "connectors");
|
|
42029
|
+
}
|
|
42030
|
+
function writeConnectorPilots(opts) {
|
|
42031
|
+
const directory = connectorDirectory(opts.dir);
|
|
42032
|
+
(0, import_node_fs10.mkdirSync)(directory, { recursive: true });
|
|
42033
|
+
const selected = opts.ids && opts.ids.length > 0 && !opts.ids.includes("all") ? opts.ids.map((id) => {
|
|
42034
|
+
const pilot = getConnectorPilot(id);
|
|
42035
|
+
if (!pilot) throw new Error(`Unknown connector pilot: ${id}`);
|
|
42036
|
+
return pilot;
|
|
42037
|
+
}) : CONNECTOR_PILOTS;
|
|
42038
|
+
const written = [];
|
|
42039
|
+
for (const pilot of selected) {
|
|
42040
|
+
const configPath = (0, import_node_path6.join)(directory, `${pilot.id}.json`);
|
|
42041
|
+
const policyPath = (0, import_node_path6.join)(directory, `${pilot.id}.cedar`);
|
|
42042
|
+
if (!opts.force && ((0, import_node_fs10.existsSync)(configPath) || (0, import_node_fs10.existsSync)(policyPath))) {
|
|
42043
|
+
throw new Error(`Refusing to overwrite ${pilot.id}. Re-run with --force if intentional.`);
|
|
42044
|
+
}
|
|
42045
|
+
(0, import_node_fs10.writeFileSync)(configPath, JSON.stringify({ ...pilot.config, id: pilot.id, name: pilot.name, category: pilot.category, tools: pilot.tools, actions: pilot.actions, setup: pilot.setup }, null, 2) + "\n");
|
|
42046
|
+
(0, import_node_fs10.writeFileSync)(policyPath, pilot.cedar.endsWith("\n") ? pilot.cedar : `${pilot.cedar}
|
|
42047
|
+
`);
|
|
42048
|
+
written.push(configPath, policyPath);
|
|
42049
|
+
}
|
|
42050
|
+
(0, import_node_fs10.writeFileSync)((0, import_node_path6.join)(directory, "README.md"), renderConnectorReadme(selected));
|
|
42051
|
+
written.push((0, import_node_path6.join)(directory, "README.md"));
|
|
42052
|
+
return { written, pilots: selected, directory };
|
|
42053
|
+
}
|
|
42054
|
+
function readInstalledConnectorPilots(dir) {
|
|
42055
|
+
const directory = connectorDirectory(dir);
|
|
42056
|
+
if (!(0, import_node_fs10.existsSync)(directory)) return [];
|
|
42057
|
+
return (0, import_node_fs10.readdirSync)(directory).filter((name) => name.endsWith(".json")).map((name) => {
|
|
42058
|
+
const configPath = (0, import_node_path6.join)(directory, name);
|
|
42059
|
+
try {
|
|
42060
|
+
const parsed = JSON.parse((0, import_node_fs10.readFileSync)(configPath, "utf-8"));
|
|
42061
|
+
const id = String(parsed.id || name.replace(/\.json$/, ""));
|
|
42062
|
+
const pilot = getConnectorPilot(id);
|
|
42063
|
+
return {
|
|
42064
|
+
id,
|
|
42065
|
+
name: String(parsed.name || pilot?.name || id),
|
|
42066
|
+
category: String(parsed.category || pilot?.category || "unknown"),
|
|
42067
|
+
status: String(parsed.status || parsed.type || "installed"),
|
|
42068
|
+
config_path: configPath,
|
|
42069
|
+
policy_path: (0, import_node_path6.join)(directory, `${id}.cedar`)
|
|
42070
|
+
};
|
|
42071
|
+
} catch {
|
|
42072
|
+
return null;
|
|
42073
|
+
}
|
|
42074
|
+
}).filter(Boolean);
|
|
42075
|
+
}
|
|
42076
|
+
function connectorDoctor(dir, env = process.env) {
|
|
42077
|
+
const installed = new Set(readInstalledConnectorPilots(dir).map((pilot) => pilot.id));
|
|
42078
|
+
return CONNECTOR_PILOTS.map((pilot) => {
|
|
42079
|
+
const envRows = pilot.env.map((item) => ({
|
|
42080
|
+
name: item.name,
|
|
42081
|
+
required: item.required,
|
|
42082
|
+
present: Boolean(env[item.name]),
|
|
42083
|
+
description: item.description
|
|
42084
|
+
}));
|
|
42085
|
+
const missingRequired = envRows.filter((item) => item.required && !item.present).map((item) => item.name);
|
|
42086
|
+
const optionalPresent = envRows.filter((item) => !item.required && item.present).map((item) => item.name);
|
|
42087
|
+
const optionalProviderReady = pilot.id === "slack-teams" ? Boolean(env.SLACK_BOT_TOKEN || env.TEAMS_WEBHOOK_URL) : pilot.id === "finance-pms" ? Boolean(env.PMS_ADAPTER_URL) : false;
|
|
42088
|
+
const mockModeReady = pilot.id === "finance-pms";
|
|
42089
|
+
return {
|
|
42090
|
+
id: pilot.id,
|
|
42091
|
+
name: pilot.name,
|
|
42092
|
+
category: pilot.category,
|
|
42093
|
+
installed: installed.has(pilot.id),
|
|
42094
|
+
usable: missingRequired.length === 0 && (pilot.env.some((item) => item.required) || pilot.env.length === 0 || optionalProviderReady || mockModeReady),
|
|
42095
|
+
mode: pilot.id === "finance-pms" && !env.PMS_ADAPTER_URL ? "mock" : pilot.id === "slack-teams" && !env.SLACK_BOT_TOKEN && !env.TEAMS_WEBHOOK_URL ? "needs_provider_choice" : "configured_or_local",
|
|
42096
|
+
missing_required: missingRequired,
|
|
42097
|
+
optional_present: optionalPresent,
|
|
42098
|
+
tools: pilot.tools,
|
|
42099
|
+
next: missingRequired.length > 0 ? `Set ${missingRequired.join(", ")}` : installed.has(pilot.id) ? "Run through protect-mcp and inspect the dashboard." : `Install with protect-mcp connectors init ${pilot.id}`
|
|
42100
|
+
};
|
|
42101
|
+
});
|
|
42102
|
+
}
|
|
42103
|
+
function renderConnectorReadme(pilots) {
|
|
42104
|
+
return `# protect-mcp connector pilots
|
|
42105
|
+
|
|
42106
|
+
These files make real tool classes visible and controllable without uploading raw prompts or payloads.
|
|
42107
|
+
|
|
42108
|
+
${pilots.map((pilot) => `## ${pilot.name}
|
|
42109
|
+
|
|
42110
|
+
${pilot.description}
|
|
42111
|
+
|
|
42112
|
+
Value: ${pilot.value}
|
|
42113
|
+
|
|
42114
|
+
Tools: ${pilot.tools.map((tool) => `\`${tool}\``).join(", ")}
|
|
42115
|
+
|
|
42116
|
+
Setup:
|
|
42117
|
+
${pilot.setup.map((step) => `- ${step}`).join("\n")}
|
|
42118
|
+
`).join("\n")}
|
|
42119
|
+
Next: run \`npx protect-mcp dashboard --open\` and review tool inventory, policy coverage, approvals, and receipts.
|
|
42120
|
+
`;
|
|
42121
|
+
}
|
|
42122
|
+
|
|
41324
42123
|
// src/rekor-anchor.ts
|
|
41325
|
-
var
|
|
42124
|
+
var import_node_crypto6 = require("crypto");
|
|
41326
42125
|
var REKOR_API = "https://rekor.sigstore.dev/api/v1";
|
|
41327
42126
|
async function anchorToRekor(receiptHash, signature, publicKeyPem) {
|
|
41328
42127
|
const entry = {
|
|
@@ -41391,7 +42190,7 @@ async function verifyRekorAnchor(logIndex, expectedHash) {
|
|
|
41391
42190
|
}
|
|
41392
42191
|
function hashReceipt(receipt) {
|
|
41393
42192
|
const canonical = JSON.stringify(receipt, Object.keys(receipt).sort());
|
|
41394
|
-
return (0,
|
|
42193
|
+
return (0, import_node_crypto6.createHash)("sha256").update(canonical).digest("hex");
|
|
41395
42194
|
}
|
|
41396
42195
|
function createLogAnchorField(anchor) {
|
|
41397
42196
|
return {
|
|
@@ -41404,7 +42203,7 @@ function createLogAnchorField(anchor) {
|
|
|
41404
42203
|
}
|
|
41405
42204
|
|
|
41406
42205
|
// src/selective-disclosure.ts
|
|
41407
|
-
var
|
|
42206
|
+
var import_node_crypto7 = require("crypto");
|
|
41408
42207
|
function redactFields(receipt, fieldsToRedact) {
|
|
41409
42208
|
const redacted = JSON.parse(JSON.stringify(receipt));
|
|
41410
42209
|
const salts = [];
|
|
@@ -41420,7 +42219,7 @@ function redactFields(receipt, fieldsToRedact) {
|
|
|
41420
42219
|
if (i === parts.length - 1) {
|
|
41421
42220
|
if (key in current) {
|
|
41422
42221
|
const originalValue = current[key];
|
|
41423
|
-
const salt = (0,
|
|
42222
|
+
const salt = (0, import_node_crypto7.randomBytes)(16).toString("hex");
|
|
41424
42223
|
const commitment = computeCommitment(salt, originalValue);
|
|
41425
42224
|
salts.push({ field: fieldPath, salt, originalValue });
|
|
41426
42225
|
current[key] = `sha256(salt + ${typeof originalValue === "string" ? "..." : JSON.stringify(originalValue).slice(0, 20) + "..."})`;
|
|
@@ -41492,11 +42291,11 @@ function createDisclosurePackage(allSalts, fieldsToDisclose) {
|
|
|
41492
42291
|
}
|
|
41493
42292
|
function computeCommitment(salt, value) {
|
|
41494
42293
|
const serialized = typeof value === "string" ? value : JSON.stringify(value);
|
|
41495
|
-
return (0,
|
|
42294
|
+
return (0, import_node_crypto7.createHash)("sha256").update(salt + serialized).digest("hex");
|
|
41496
42295
|
}
|
|
41497
42296
|
function hashObject(obj) {
|
|
41498
42297
|
const canonical = JSON.stringify(obj, Object.keys(obj).sort());
|
|
41499
|
-
return (0,
|
|
42298
|
+
return (0, import_node_crypto7.createHash)("sha256").update(canonical).digest("hex");
|
|
41500
42299
|
}
|
|
41501
42300
|
|
|
41502
42301
|
// src/huggingface-export.ts
|
|
@@ -41637,10 +42436,10 @@ MIT
|
|
|
41637
42436
|
}
|
|
41638
42437
|
|
|
41639
42438
|
// src/webauthn-approval.ts
|
|
41640
|
-
var
|
|
42439
|
+
var import_node_crypto8 = require("crypto");
|
|
41641
42440
|
function createApprovalChallenge(requestId, toolName, agentId, rpId = "scopeblind.com", timeoutSeconds = 300) {
|
|
41642
|
-
const challengeBytes = (0,
|
|
41643
|
-
const contextHash = (0,
|
|
42441
|
+
const challengeBytes = (0, import_node_crypto8.randomBytes)(32);
|
|
42442
|
+
const contextHash = (0, import_node_crypto8.createHash)("sha256").update(JSON.stringify({ requestId, toolName, agentId, timestamp: Date.now() })).digest("hex");
|
|
41644
42443
|
return {
|
|
41645
42444
|
challenge: base64urlEncode(challengeBytes),
|
|
41646
42445
|
requestId,
|
|
@@ -41655,14 +42454,14 @@ function createApprovalChallenge(requestId, toolName, agentId, rpId = "scopeblin
|
|
|
41655
42454
|
function toCredentialRequestOptions(challenge, allowCredentials) {
|
|
41656
42455
|
return {
|
|
41657
42456
|
publicKey: {
|
|
41658
|
-
challenge:
|
|
42457
|
+
challenge: base64urlDecode2(challenge.challenge).buffer,
|
|
41659
42458
|
rpId: challenge.rpId,
|
|
41660
42459
|
timeout: challenge.timeoutSeconds * 1e3,
|
|
41661
42460
|
userVerification: "required",
|
|
41662
42461
|
// Always require biometric
|
|
41663
42462
|
...allowCredentials ? {
|
|
41664
42463
|
allowCredentials: allowCredentials.map((c) => ({
|
|
41665
|
-
id:
|
|
42464
|
+
id: base64urlDecode2(c.id).buffer,
|
|
41666
42465
|
type: "public-key"
|
|
41667
42466
|
}))
|
|
41668
42467
|
} : {}
|
|
@@ -41683,7 +42482,7 @@ function verifyApprovalAssertion(challenge, assertion) {
|
|
|
41683
42482
|
approvedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
41684
42483
|
};
|
|
41685
42484
|
}
|
|
41686
|
-
const authData =
|
|
42485
|
+
const authData = base64urlDecode2(assertion.authenticatorData);
|
|
41687
42486
|
const flags = authData[32];
|
|
41688
42487
|
const userPresent = !!(flags & 1);
|
|
41689
42488
|
const userVerified = !!(flags & 4);
|
|
@@ -41691,7 +42490,7 @@ function verifyApprovalAssertion(challenge, assertion) {
|
|
|
41691
42490
|
const signCount = authData.length >= 37 ? authData[33] << 24 | authData[34] << 16 | authData[35] << 8 | authData[36] : 0;
|
|
41692
42491
|
let authenticatorType = "unknown";
|
|
41693
42492
|
try {
|
|
41694
|
-
const clientData = JSON.parse(Buffer.from(
|
|
42493
|
+
const clientData = JSON.parse(Buffer.from(base64urlDecode2(assertion.clientDataJSON)).toString());
|
|
41695
42494
|
if (clientData.type === "webauthn.get") {
|
|
41696
42495
|
authenticatorType = "platform";
|
|
41697
42496
|
}
|
|
@@ -41720,13 +42519,13 @@ function createApprovalReceiptPayload(challenge, result) {
|
|
|
41720
42519
|
context_hash: result.contextHash,
|
|
41721
42520
|
approved_at: result.approvedAt,
|
|
41722
42521
|
// Hash the credential ID for privacy — don't store the raw ID
|
|
41723
|
-
credential_id_hash: (0,
|
|
42522
|
+
credential_id_hash: (0, import_node_crypto8.createHash)("sha256").update(result.credentialId).digest("hex").slice(0, 16)
|
|
41724
42523
|
};
|
|
41725
42524
|
}
|
|
41726
42525
|
function base64urlEncode(buffer) {
|
|
41727
42526
|
return Buffer.from(buffer).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
41728
42527
|
}
|
|
41729
|
-
function
|
|
42528
|
+
function base64urlDecode2(str) {
|
|
41730
42529
|
const base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
41731
42530
|
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
41732
42531
|
return new Uint8Array(Buffer.from(padded, "base64"));
|
|
@@ -41974,7 +42773,7 @@ function evaluatePolicy(tool, policy) {
|
|
|
41974
42773
|
}
|
|
41975
42774
|
|
|
41976
42775
|
// src/evidence-authenticity.ts
|
|
41977
|
-
var
|
|
42776
|
+
var import_node_crypto9 = require("crypto");
|
|
41978
42777
|
async function createEvidenceAttestation(input) {
|
|
41979
42778
|
const tlsNotaryAvailable = await isTLSNotaryAvailable();
|
|
41980
42779
|
if (tlsNotaryAvailable) {
|
|
@@ -42034,14 +42833,14 @@ async function verifyEvidenceAttestation(attestation) {
|
|
|
42034
42833
|
}
|
|
42035
42834
|
}
|
|
42036
42835
|
function hashResponseBody(body) {
|
|
42037
|
-
return (0,
|
|
42836
|
+
return (0, import_node_crypto9.createHash)("sha256").update(typeof body === "string" ? body : body).digest("hex");
|
|
42038
42837
|
}
|
|
42039
42838
|
function createAttestationField(attestation) {
|
|
42040
42839
|
return {
|
|
42041
42840
|
evidence_authenticity: {
|
|
42042
42841
|
version: attestation.version,
|
|
42043
42842
|
method: attestation.method,
|
|
42044
|
-
url_hash: (0,
|
|
42843
|
+
url_hash: (0, import_node_crypto9.createHash)("sha256").update(attestation.url).digest("hex").slice(0, 16),
|
|
42045
42844
|
response_hash: attestation.responseHash,
|
|
42046
42845
|
fetched_at: attestation.fetchedAt,
|
|
42047
42846
|
verified: attestation.verified,
|
|
@@ -42072,7 +42871,7 @@ async function createTLSNotaryAttestation(input) {
|
|
|
42072
42871
|
}
|
|
42073
42872
|
|
|
42074
42873
|
// src/c2pa-credentials.ts
|
|
42075
|
-
var
|
|
42874
|
+
var import_node_crypto10 = require("crypto");
|
|
42076
42875
|
function createC2PAManifest(receipts, options) {
|
|
42077
42876
|
const generator = options.generator || "protect-mcp";
|
|
42078
42877
|
const version = options.version || "0.3.3";
|
|
@@ -42086,7 +42885,7 @@ function createC2PAManifest(receipts, options) {
|
|
|
42086
42885
|
(r) => r.payload?.decision === "deny"
|
|
42087
42886
|
);
|
|
42088
42887
|
const receiptHashes = receipts.map(
|
|
42089
|
-
(r) => (0,
|
|
42888
|
+
(r) => (0, import_node_crypto10.createHash)("sha256").update(JSON.stringify(r)).digest("hex")
|
|
42090
42889
|
);
|
|
42091
42890
|
const merkleRoot2 = computeMerkleRoot(receiptHashes);
|
|
42092
42891
|
const assertions = [
|
|
@@ -42228,7 +43027,7 @@ function computeMerkleRoot(hashes) {
|
|
|
42228
43027
|
const left = hashes[i];
|
|
42229
43028
|
const right = i + 1 < hashes.length ? hashes[i + 1] : left;
|
|
42230
43029
|
nextLevel.push(
|
|
42231
|
-
(0,
|
|
43030
|
+
(0, import_node_crypto10.createHash)("sha256").update(left + right).digest("hex")
|
|
42232
43031
|
);
|
|
42233
43032
|
}
|
|
42234
43033
|
return computeMerkleRoot(nextLevel);
|
|
@@ -42281,7 +43080,7 @@ function toManifoldFormat(prediction) {
|
|
|
42281
43080
|
}
|
|
42282
43081
|
|
|
42283
43082
|
// src/agent-exchange.ts
|
|
42284
|
-
var
|
|
43083
|
+
var import_node_crypto11 = require("crypto");
|
|
42285
43084
|
var ReceiptPropagator = class {
|
|
42286
43085
|
issuer;
|
|
42287
43086
|
signer;
|
|
@@ -42302,7 +43101,7 @@ var ReceiptPropagator = class {
|
|
|
42302
43101
|
delegate(delegateId, options) {
|
|
42303
43102
|
const now = /* @__PURE__ */ new Date();
|
|
42304
43103
|
const receipt = {
|
|
42305
|
-
receipt_id: `del_${(0,
|
|
43104
|
+
receipt_id: `del_${(0, import_node_crypto11.randomUUID)().slice(0, 12)}`,
|
|
42306
43105
|
receipt_type: "delegation",
|
|
42307
43106
|
issuer_id: this.issuer,
|
|
42308
43107
|
event_time: now.toISOString(),
|
|
@@ -42359,7 +43158,7 @@ var ReceiptPropagator = class {
|
|
|
42359
43158
|
const currentCount = this.delegationCallCounts.get(options.delegation_receipt) || 0;
|
|
42360
43159
|
this.delegationCallCounts.set(options.delegation_receipt, currentCount + 1);
|
|
42361
43160
|
const receipt = {
|
|
42362
|
-
receipt_id: `act_${(0,
|
|
43161
|
+
receipt_id: `act_${(0, import_node_crypto11.randomUUID)().slice(0, 12)}`,
|
|
42363
43162
|
receipt_type: "execution",
|
|
42364
43163
|
issuer_id: this.issuer,
|
|
42365
43164
|
event_time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -42451,7 +43250,7 @@ function createReceiptChannel(orchestratorId) {
|
|
|
42451
43250
|
async withDelegation(delegateId, tools, fn, options) {
|
|
42452
43251
|
const delegation = propagator.delegate(delegateId, {
|
|
42453
43252
|
tools,
|
|
42454
|
-
scope: options?.scope || `task-${(0,
|
|
43253
|
+
scope: options?.scope || `task-${(0, import_node_crypto11.randomUUID)().slice(0, 8)}`,
|
|
42455
43254
|
ttl: options?.ttl || 3600,
|
|
42456
43255
|
maxCalls: options?.maxCalls
|
|
42457
43256
|
});
|
|
@@ -42601,6 +43400,48 @@ var TOOLS = [
|
|
|
42601
43400
|
},
|
|
42602
43401
|
required: ["environment"]
|
|
42603
43402
|
}
|
|
43403
|
+
},
|
|
43404
|
+
{
|
|
43405
|
+
name: "github_create_pr",
|
|
43406
|
+
description: "Create a GitHub pull request",
|
|
43407
|
+
inputSchema: {
|
|
43408
|
+
type: "object",
|
|
43409
|
+
properties: {
|
|
43410
|
+
repo: { type: "string", description: "Repository name" },
|
|
43411
|
+
branch: { type: "string", description: "Source branch" },
|
|
43412
|
+
title: { type: "string", description: "Pull request title" }
|
|
43413
|
+
},
|
|
43414
|
+
required: ["repo", "branch", "title"]
|
|
43415
|
+
}
|
|
43416
|
+
},
|
|
43417
|
+
{
|
|
43418
|
+
name: "send_email",
|
|
43419
|
+
description: "Send an email",
|
|
43420
|
+
inputSchema: {
|
|
43421
|
+
type: "object",
|
|
43422
|
+
properties: {
|
|
43423
|
+
to: { type: "string", description: "Recipient email address" },
|
|
43424
|
+
subject: { type: "string", description: "Subject line" },
|
|
43425
|
+
body: { type: "string", description: "Message body" }
|
|
43426
|
+
},
|
|
43427
|
+
required: ["to", "subject"]
|
|
43428
|
+
}
|
|
43429
|
+
},
|
|
43430
|
+
{
|
|
43431
|
+
name: "pms_book_fill",
|
|
43432
|
+
description: "Book a fill into the mock portfolio management system",
|
|
43433
|
+
inputSchema: {
|
|
43434
|
+
type: "object",
|
|
43435
|
+
properties: {
|
|
43436
|
+
account: { type: "string", description: "Portfolio or fund account" },
|
|
43437
|
+
symbol: { type: "string", description: "Instrument symbol" },
|
|
43438
|
+
side: { type: "string", enum: ["BUY", "SELL"] },
|
|
43439
|
+
quantity: { type: "number" },
|
|
43440
|
+
price: { type: "number" },
|
|
43441
|
+
strategy: { type: "string" }
|
|
43442
|
+
},
|
|
43443
|
+
required: ["account", "symbol", "side", "quantity", "price"]
|
|
43444
|
+
}
|
|
42604
43445
|
}
|
|
42605
43446
|
];
|
|
42606
43447
|
function handleRequest(request) {
|
|
@@ -42648,6 +43489,15 @@ Contents: Hello from protect-mcp demo server!`;
|
|
|
42648
43489
|
case "deploy":
|
|
42649
43490
|
resultText = `[demo] Deployed to ${args.environment || "staging"}${args.reason ? ` (reason: ${args.reason})` : ""}`;
|
|
42650
43491
|
break;
|
|
43492
|
+
case "github_create_pr":
|
|
43493
|
+
resultText = `[demo] Created PR in ${args.repo || "scopeblind/demo"} from ${args.branch || "agent/demo"}: ${args.title || "Agent change"}`;
|
|
43494
|
+
break;
|
|
43495
|
+
case "send_email":
|
|
43496
|
+
resultText = `[demo] Drafted email to ${args.to || "pm@example.com"}: ${args.subject || "Agent update"}`;
|
|
43497
|
+
break;
|
|
43498
|
+
case "pms_book_fill":
|
|
43499
|
+
resultText = `[demo] Booked ${args.side || "BUY"} ${args.quantity || 0} ${args.symbol || "AAPL"} @ ${args.price || 0} into ${args.account || "Demo Fund"} (${args.strategy || "Unassigned"})`;
|
|
43500
|
+
break;
|
|
42651
43501
|
default:
|
|
42652
43502
|
resultText = `[demo] Unknown tool: ${toolName}`;
|
|
42653
43503
|
}
|
|
@@ -42720,12 +43570,38 @@ function createSandboxServer() {
|
|
|
42720
43570
|
{ environment: z.enum(["staging", "production"]).describe("Target environment"), reason: z.string().optional().describe("Deployment reason") },
|
|
42721
43571
|
async (args) => ({ content: [{ type: "text", text: `[demo] Deployed to ${args.environment}` }] })
|
|
42722
43572
|
);
|
|
43573
|
+
server.tool("github_create_pr", "Create a GitHub pull request", {
|
|
43574
|
+
repo: z.string(),
|
|
43575
|
+
branch: z.string(),
|
|
43576
|
+
title: z.string()
|
|
43577
|
+
}, async (args) => ({
|
|
43578
|
+
content: [{ type: "text", text: `[demo] PR created in ${args.repo} from ${args.branch}: ${args.title}` }]
|
|
43579
|
+
}));
|
|
43580
|
+
server.tool("send_email", "Send an email", {
|
|
43581
|
+
to: z.string(),
|
|
43582
|
+
subject: z.string(),
|
|
43583
|
+
body: z.string().optional()
|
|
43584
|
+
}, async (args) => ({
|
|
43585
|
+
content: [{ type: "text", text: `[demo] Email prepared for ${args.to}: ${args.subject}` }]
|
|
43586
|
+
}));
|
|
43587
|
+
server.tool("pms_book_fill", "Book a fill into a mock PMS", {
|
|
43588
|
+
account: z.string(),
|
|
43589
|
+
symbol: z.string(),
|
|
43590
|
+
side: z.enum(["BUY", "SELL"]),
|
|
43591
|
+
quantity: z.number(),
|
|
43592
|
+
price: z.number(),
|
|
43593
|
+
strategy: z.string().optional()
|
|
43594
|
+
}, async (args) => ({
|
|
43595
|
+
content: [{ type: "text", text: `[demo] Booked ${args.side} ${args.quantity} ${args.symbol} @ ${args.price} into ${args.account}` }]
|
|
43596
|
+
}));
|
|
42723
43597
|
return server;
|
|
42724
43598
|
}
|
|
42725
43599
|
// Annotate the CommonJS export names for ESM import in node:
|
|
42726
43600
|
0 && (module.exports = {
|
|
42727
43601
|
BUILTIN_PATTERNS,
|
|
43602
|
+
CONNECTOR_PILOTS,
|
|
42728
43603
|
ConfidentialGate,
|
|
43604
|
+
POLICY_PACKS,
|
|
42729
43605
|
ProtectGateway,
|
|
42730
43606
|
ReceiptPropagator,
|
|
42731
43607
|
ScopeBlindBridge,
|
|
@@ -42735,6 +43611,9 @@ function createSandboxServer() {
|
|
|
42735
43611
|
collectSignedReceipts,
|
|
42736
43612
|
computeCalibration,
|
|
42737
43613
|
confidentialInference,
|
|
43614
|
+
connectorDirectory,
|
|
43615
|
+
connectorDoctor,
|
|
43616
|
+
connectorPilotIds,
|
|
42738
43617
|
createApprovalChallenge,
|
|
42739
43618
|
createApprovalReceiptPayload,
|
|
42740
43619
|
createAttestationField,
|
|
@@ -42746,6 +43625,7 @@ function createSandboxServer() {
|
|
|
42746
43625
|
createReceiptChannel,
|
|
42747
43626
|
createSandbox,
|
|
42748
43627
|
createSandboxServer,
|
|
43628
|
+
createSelectiveDisclosurePackage,
|
|
42749
43629
|
destroySandbox,
|
|
42750
43630
|
discloseField,
|
|
42751
43631
|
ed25519ToDIDKey,
|
|
@@ -42766,6 +43646,8 @@ function createSandboxServer() {
|
|
|
42766
43646
|
generateSampleCedarPolicy,
|
|
42767
43647
|
generateSchemaStub,
|
|
42768
43648
|
generateVerifyReceiptSkill,
|
|
43649
|
+
getConnectorPilot,
|
|
43650
|
+
getPolicyPack,
|
|
42769
43651
|
getScopeBlindBridge,
|
|
42770
43652
|
getSignerInfo,
|
|
42771
43653
|
getToolPolicy,
|
|
@@ -42786,8 +43668,10 @@ function createSandboxServer() {
|
|
|
42786
43668
|
parseLogFile,
|
|
42787
43669
|
parseNotificationConfigFromEnv,
|
|
42788
43670
|
parseRateLimit,
|
|
43671
|
+
policyPackIds,
|
|
42789
43672
|
policySetFromSource,
|
|
42790
43673
|
queryExternalPDP,
|
|
43674
|
+
readInstalledConnectorPilots,
|
|
42791
43675
|
receiptToVP,
|
|
42792
43676
|
receiptsToHFRows,
|
|
42793
43677
|
redactFields,
|
|
@@ -42811,7 +43695,9 @@ function createSandboxServer() {
|
|
|
42811
43695
|
verifyApprovalAssertion,
|
|
42812
43696
|
verifyCommitment,
|
|
42813
43697
|
verifyEvidenceAttestation,
|
|
42814
|
-
verifyRekorAnchor
|
|
43698
|
+
verifyRekorAnchor,
|
|
43699
|
+
verifySelectiveDisclosurePackage,
|
|
43700
|
+
writeConnectorPilots
|
|
42815
43701
|
});
|
|
42816
43702
|
/**
|
|
42817
43703
|
* scopeblind-bridge.ts
|