protect-mcp 0.7.2 → 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 +69 -0
- package/README.md +157 -9
- 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-X63ELMU4.mjs → chunk-NVJHGXXG.mjs} +37 -7
- package/dist/{chunk-5JXFV37Y.mjs → chunk-PM2ZO57M.mjs} +10 -2
- package/dist/{chunk-OHUTUFTC.mjs → chunk-UBZJ3VI2.mjs} +19 -15
- package/dist/cli.js +4034 -502
- package/dist/cli.mjs +2028 -9
- 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 +144 -25
- 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 +988 -80
- 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 },
|
|
@@ -39960,7 +40246,10 @@ async function handlePreToolUse(input, state) {
|
|
|
39960
40246
|
context: {
|
|
39961
40247
|
hook_event: "PreToolUse",
|
|
39962
40248
|
...input.toolInput || {}
|
|
39963
|
-
}
|
|
40249
|
+
},
|
|
40250
|
+
// Also expose the raw tool input under context.input so policies written
|
|
40251
|
+
// against the documented nested shape match on the hook path too.
|
|
40252
|
+
toolInput: input.toolInput || {}
|
|
39964
40253
|
});
|
|
39965
40254
|
if (!cedarDecision.allowed) {
|
|
39966
40255
|
const reason = cedarDecision.reason || "cedar_deny";
|
|
@@ -39979,6 +40268,7 @@ async function handlePreToolUse(input, state) {
|
|
|
39979
40268
|
swarm: swarm.team_name ? swarm : void 0,
|
|
39980
40269
|
timing: { hook_latency_ms: hookLatency2, started_at: hookStart },
|
|
39981
40270
|
payload_digest: payloadDigest,
|
|
40271
|
+
action_readback: actionReadback,
|
|
39982
40272
|
deny_iteration: denyCount,
|
|
39983
40273
|
sandbox_state: detectSandboxState(),
|
|
39984
40274
|
plan_receipt_id: state.activePlanReceiptId || void 0
|
|
@@ -39999,10 +40289,30 @@ async function handlePreToolUse(input, state) {
|
|
|
39999
40289
|
};
|
|
40000
40290
|
}
|
|
40001
40291
|
} catch (err) {
|
|
40002
|
-
|
|
40003
|
-
|
|
40004
|
-
`
|
|
40005
|
-
|
|
40292
|
+
const hookLatency2 = Date.now() - hookStart;
|
|
40293
|
+
process.stderr.write(
|
|
40294
|
+
`[PROTECT_MCP] Cedar eval threw for "${toolName}", failing closed: ${err instanceof Error ? err.message : err}
|
|
40295
|
+
`
|
|
40296
|
+
);
|
|
40297
|
+
emitDecisionLog(state, {
|
|
40298
|
+
tool: toolName,
|
|
40299
|
+
decision: "deny",
|
|
40300
|
+
reason_code: "cedar_eval_error",
|
|
40301
|
+
request_id: requestId,
|
|
40302
|
+
hook_event: "PreToolUse",
|
|
40303
|
+
swarm: swarm.team_name ? swarm : void 0,
|
|
40304
|
+
timing: { hook_latency_ms: hookLatency2, started_at: hookStart },
|
|
40305
|
+
payload_digest: payloadDigest,
|
|
40306
|
+
action_readback: actionReadback,
|
|
40307
|
+
sandbox_state: detectSandboxState()
|
|
40308
|
+
});
|
|
40309
|
+
return {
|
|
40310
|
+
hookSpecificOutput: {
|
|
40311
|
+
hookEventName: "PreToolUse",
|
|
40312
|
+
permissionDecision: "deny",
|
|
40313
|
+
permissionDecisionReason: `[ScopeBlind] Denied: the policy engine errored while evaluating "${toolName}", so the gate fails closed rather than allow an unverified call. Check the policy set and server logs.`
|
|
40314
|
+
}
|
|
40315
|
+
};
|
|
40006
40316
|
}
|
|
40007
40317
|
}
|
|
40008
40318
|
if (state.jsonPolicy?.policy) {
|
|
@@ -40018,6 +40328,7 @@ async function handlePreToolUse(input, state) {
|
|
|
40018
40328
|
swarm: swarm.team_name ? swarm : void 0,
|
|
40019
40329
|
timing: { hook_latency_ms: hookLatency2, started_at: hookStart },
|
|
40020
40330
|
payload_digest: payloadDigest,
|
|
40331
|
+
action_readback: actionReadback,
|
|
40021
40332
|
sandbox_state: detectSandboxState()
|
|
40022
40333
|
});
|
|
40023
40334
|
return {
|
|
@@ -40038,13 +40349,15 @@ async function handlePreToolUse(input, state) {
|
|
|
40038
40349
|
hook_event: "PreToolUse",
|
|
40039
40350
|
swarm: swarm.team_name ? swarm : void 0,
|
|
40040
40351
|
timing: { hook_latency_ms: hookLatency2, started_at: hookStart },
|
|
40352
|
+
payload_digest: payloadDigest,
|
|
40353
|
+
action_readback: actionReadback,
|
|
40041
40354
|
sandbox_state: detectSandboxState()
|
|
40042
40355
|
});
|
|
40043
40356
|
return {
|
|
40044
40357
|
hookSpecificOutput: {
|
|
40045
40358
|
hookEventName: "PreToolUse",
|
|
40046
40359
|
permissionDecision: "ask",
|
|
40047
|
-
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}`
|
|
40048
40361
|
}
|
|
40049
40362
|
};
|
|
40050
40363
|
}
|
|
@@ -40089,6 +40402,7 @@ async function handlePreToolUse(input, state) {
|
|
|
40089
40402
|
swarm: swarm.team_name ? swarm : void 0,
|
|
40090
40403
|
timing: { hook_latency_ms: hookLatency, started_at: hookStart },
|
|
40091
40404
|
payload_digest: payloadDigest,
|
|
40405
|
+
action_readback: actionReadback,
|
|
40092
40406
|
sandbox_state: detectSandboxState(),
|
|
40093
40407
|
plan_receipt_id: state.activePlanReceiptId || void 0
|
|
40094
40408
|
});
|
|
@@ -40105,7 +40419,7 @@ async function handlePreToolUse(input, state) {
|
|
|
40105
40419
|
}
|
|
40106
40420
|
async function handlePostToolUse(input, state) {
|
|
40107
40421
|
const toolName = input.toolName || "unknown";
|
|
40108
|
-
const requestId = input.toolUseId || (0,
|
|
40422
|
+
const requestId = input.toolUseId || (0, import_node_crypto5.randomUUID)().slice(0, 12);
|
|
40109
40423
|
const now = Date.now();
|
|
40110
40424
|
const inflight = state.inflightTools.get(requestId);
|
|
40111
40425
|
const timing = {
|
|
@@ -40117,7 +40431,7 @@ async function handlePostToolUse(input, state) {
|
|
|
40117
40431
|
state.inflightTools.delete(requestId);
|
|
40118
40432
|
}
|
|
40119
40433
|
const outputDigest = computeOutputDigest(input.toolResult);
|
|
40120
|
-
const receiptId = (0,
|
|
40434
|
+
const receiptId = (0, import_node_crypto5.randomUUID)().slice(0, 8);
|
|
40121
40435
|
const policyName = state.cedarPolicies ? `cedar:${state.policyDigest}` : state.policyDigest;
|
|
40122
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.` : "");
|
|
40123
40437
|
emitDecisionLog(state, {
|
|
@@ -40149,7 +40463,7 @@ function handleSubagentStart(input, state) {
|
|
|
40149
40463
|
tool: `subagent:${agentId}`,
|
|
40150
40464
|
decision: "allow",
|
|
40151
40465
|
reason_code: "subagent_started",
|
|
40152
|
-
request_id: (0,
|
|
40466
|
+
request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
|
|
40153
40467
|
hook_event: "SubagentStart",
|
|
40154
40468
|
swarm: {
|
|
40155
40469
|
...state.swarmContext,
|
|
@@ -40170,7 +40484,7 @@ function handleSubagentStop(input, state) {
|
|
|
40170
40484
|
tool: `subagent:${agentId}`,
|
|
40171
40485
|
decision: "allow",
|
|
40172
40486
|
reason_code: "subagent_stopped",
|
|
40173
|
-
request_id: (0,
|
|
40487
|
+
request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
|
|
40174
40488
|
hook_event: "SubagentStop",
|
|
40175
40489
|
swarm: {
|
|
40176
40490
|
...state.swarmContext,
|
|
@@ -40185,7 +40499,7 @@ function handleTaskCreated(input, state) {
|
|
|
40185
40499
|
tool: `task:${input.taskId || "unknown"}`,
|
|
40186
40500
|
decision: "allow",
|
|
40187
40501
|
reason_code: "task_created",
|
|
40188
|
-
request_id: (0,
|
|
40502
|
+
request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
|
|
40189
40503
|
hook_event: "TaskCreated",
|
|
40190
40504
|
swarm: {
|
|
40191
40505
|
...state.swarmContext,
|
|
@@ -40199,7 +40513,7 @@ function handleTaskCompleted(input, state) {
|
|
|
40199
40513
|
tool: `task:${input.taskId || "unknown"}`,
|
|
40200
40514
|
decision: "allow",
|
|
40201
40515
|
reason_code: "task_completed",
|
|
40202
|
-
request_id: (0,
|
|
40516
|
+
request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
|
|
40203
40517
|
hook_event: "TaskCompleted",
|
|
40204
40518
|
swarm: state.swarmContext
|
|
40205
40519
|
});
|
|
@@ -40210,7 +40524,7 @@ function handleSessionStart(input, state) {
|
|
|
40210
40524
|
tool: "session",
|
|
40211
40525
|
decision: "allow",
|
|
40212
40526
|
reason_code: "session_started",
|
|
40213
|
-
request_id: input.sessionId || (0,
|
|
40527
|
+
request_id: input.sessionId || (0, import_node_crypto5.randomUUID)().slice(0, 12),
|
|
40214
40528
|
hook_event: "SessionStart",
|
|
40215
40529
|
swarm: state.swarmContext,
|
|
40216
40530
|
sandbox_state: detectSandboxState()
|
|
@@ -40233,7 +40547,7 @@ function handleSessionEnd(input, state) {
|
|
|
40233
40547
|
tool: "session",
|
|
40234
40548
|
decision: "allow",
|
|
40235
40549
|
reason_code: "session_ended",
|
|
40236
|
-
request_id: input.sessionId || (0,
|
|
40550
|
+
request_id: input.sessionId || (0, import_node_crypto5.randomUUID)().slice(0, 12),
|
|
40237
40551
|
hook_event: "SessionEnd",
|
|
40238
40552
|
swarm: state.swarmContext
|
|
40239
40553
|
});
|
|
@@ -40244,7 +40558,7 @@ function handleTeammateIdle(input, state) {
|
|
|
40244
40558
|
tool: `teammate:${input.agentId || "unknown"}`,
|
|
40245
40559
|
decision: "allow",
|
|
40246
40560
|
reason_code: "teammate_idle",
|
|
40247
|
-
request_id: (0,
|
|
40561
|
+
request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
|
|
40248
40562
|
hook_event: "TeammateIdle",
|
|
40249
40563
|
swarm: {
|
|
40250
40564
|
...state.swarmContext,
|
|
@@ -40272,7 +40586,7 @@ function handleConfigChange(input, state) {
|
|
|
40272
40586
|
tool: "config",
|
|
40273
40587
|
decision: "deny",
|
|
40274
40588
|
reason_code: "config_tamper_detected",
|
|
40275
|
-
request_id: (0,
|
|
40589
|
+
request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
|
|
40276
40590
|
hook_event: "ConfigChange",
|
|
40277
40591
|
swarm: state.swarmContext
|
|
40278
40592
|
});
|
|
@@ -40281,7 +40595,7 @@ function handleConfigChange(input, state) {
|
|
|
40281
40595
|
tool: "config",
|
|
40282
40596
|
decision: "allow",
|
|
40283
40597
|
reason_code: "config_changed",
|
|
40284
|
-
request_id: (0,
|
|
40598
|
+
request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
|
|
40285
40599
|
hook_event: "ConfigChange"
|
|
40286
40600
|
});
|
|
40287
40601
|
}
|
|
@@ -40303,7 +40617,7 @@ function handleStop(input, state) {
|
|
|
40303
40617
|
tool: "session",
|
|
40304
40618
|
decision: "allow",
|
|
40305
40619
|
reason_code: "agent_stopped",
|
|
40306
|
-
request_id: (0,
|
|
40620
|
+
request_id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
|
|
40307
40621
|
hook_event: "Stop",
|
|
40308
40622
|
swarm: state.swarmContext
|
|
40309
40623
|
});
|
|
@@ -40311,8 +40625,8 @@ function handleStop(input, state) {
|
|
|
40311
40625
|
}
|
|
40312
40626
|
function emitDecisionLog(state, entry) {
|
|
40313
40627
|
const mode = state.enforce ? "enforce" : "shadow";
|
|
40314
|
-
const otelTraceId = (0,
|
|
40315
|
-
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");
|
|
40316
40630
|
const log = {
|
|
40317
40631
|
v: 2,
|
|
40318
40632
|
tool: entry.tool || "unknown",
|
|
@@ -40320,7 +40634,7 @@ function emitDecisionLog(state, entry) {
|
|
|
40320
40634
|
reason_code: entry.reason_code || "default_allow",
|
|
40321
40635
|
policy_digest: state.policyDigest,
|
|
40322
40636
|
policy_engine: state.cedarPolicies ? "cedar" : "built-in",
|
|
40323
|
-
request_id: entry.request_id || (0,
|
|
40637
|
+
request_id: entry.request_id || (0, import_node_crypto5.randomUUID)().slice(0, 12),
|
|
40324
40638
|
timestamp: Date.now(),
|
|
40325
40639
|
mode,
|
|
40326
40640
|
otel_trace_id: otelTraceId,
|
|
@@ -41299,8 +41613,515 @@ function generateSchemaStub(namespace = "ScopeBlind") {
|
|
|
41299
41613
|
].join("\n");
|
|
41300
41614
|
}
|
|
41301
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
|
+
|
|
41302
42123
|
// src/rekor-anchor.ts
|
|
41303
|
-
var
|
|
42124
|
+
var import_node_crypto6 = require("crypto");
|
|
41304
42125
|
var REKOR_API = "https://rekor.sigstore.dev/api/v1";
|
|
41305
42126
|
async function anchorToRekor(receiptHash, signature, publicKeyPem) {
|
|
41306
42127
|
const entry = {
|
|
@@ -41369,7 +42190,7 @@ async function verifyRekorAnchor(logIndex, expectedHash) {
|
|
|
41369
42190
|
}
|
|
41370
42191
|
function hashReceipt(receipt) {
|
|
41371
42192
|
const canonical = JSON.stringify(receipt, Object.keys(receipt).sort());
|
|
41372
|
-
return (0,
|
|
42193
|
+
return (0, import_node_crypto6.createHash)("sha256").update(canonical).digest("hex");
|
|
41373
42194
|
}
|
|
41374
42195
|
function createLogAnchorField(anchor) {
|
|
41375
42196
|
return {
|
|
@@ -41382,7 +42203,7 @@ function createLogAnchorField(anchor) {
|
|
|
41382
42203
|
}
|
|
41383
42204
|
|
|
41384
42205
|
// src/selective-disclosure.ts
|
|
41385
|
-
var
|
|
42206
|
+
var import_node_crypto7 = require("crypto");
|
|
41386
42207
|
function redactFields(receipt, fieldsToRedact) {
|
|
41387
42208
|
const redacted = JSON.parse(JSON.stringify(receipt));
|
|
41388
42209
|
const salts = [];
|
|
@@ -41398,7 +42219,7 @@ function redactFields(receipt, fieldsToRedact) {
|
|
|
41398
42219
|
if (i === parts.length - 1) {
|
|
41399
42220
|
if (key in current) {
|
|
41400
42221
|
const originalValue = current[key];
|
|
41401
|
-
const salt = (0,
|
|
42222
|
+
const salt = (0, import_node_crypto7.randomBytes)(16).toString("hex");
|
|
41402
42223
|
const commitment = computeCommitment(salt, originalValue);
|
|
41403
42224
|
salts.push({ field: fieldPath, salt, originalValue });
|
|
41404
42225
|
current[key] = `sha256(salt + ${typeof originalValue === "string" ? "..." : JSON.stringify(originalValue).slice(0, 20) + "..."})`;
|
|
@@ -41470,11 +42291,11 @@ function createDisclosurePackage(allSalts, fieldsToDisclose) {
|
|
|
41470
42291
|
}
|
|
41471
42292
|
function computeCommitment(salt, value) {
|
|
41472
42293
|
const serialized = typeof value === "string" ? value : JSON.stringify(value);
|
|
41473
|
-
return (0,
|
|
42294
|
+
return (0, import_node_crypto7.createHash)("sha256").update(salt + serialized).digest("hex");
|
|
41474
42295
|
}
|
|
41475
42296
|
function hashObject(obj) {
|
|
41476
42297
|
const canonical = JSON.stringify(obj, Object.keys(obj).sort());
|
|
41477
|
-
return (0,
|
|
42298
|
+
return (0, import_node_crypto7.createHash)("sha256").update(canonical).digest("hex");
|
|
41478
42299
|
}
|
|
41479
42300
|
|
|
41480
42301
|
// src/huggingface-export.ts
|
|
@@ -41615,10 +42436,10 @@ MIT
|
|
|
41615
42436
|
}
|
|
41616
42437
|
|
|
41617
42438
|
// src/webauthn-approval.ts
|
|
41618
|
-
var
|
|
42439
|
+
var import_node_crypto8 = require("crypto");
|
|
41619
42440
|
function createApprovalChallenge(requestId, toolName, agentId, rpId = "scopeblind.com", timeoutSeconds = 300) {
|
|
41620
|
-
const challengeBytes = (0,
|
|
41621
|
-
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");
|
|
41622
42443
|
return {
|
|
41623
42444
|
challenge: base64urlEncode(challengeBytes),
|
|
41624
42445
|
requestId,
|
|
@@ -41633,14 +42454,14 @@ function createApprovalChallenge(requestId, toolName, agentId, rpId = "scopeblin
|
|
|
41633
42454
|
function toCredentialRequestOptions(challenge, allowCredentials) {
|
|
41634
42455
|
return {
|
|
41635
42456
|
publicKey: {
|
|
41636
|
-
challenge:
|
|
42457
|
+
challenge: base64urlDecode2(challenge.challenge).buffer,
|
|
41637
42458
|
rpId: challenge.rpId,
|
|
41638
42459
|
timeout: challenge.timeoutSeconds * 1e3,
|
|
41639
42460
|
userVerification: "required",
|
|
41640
42461
|
// Always require biometric
|
|
41641
42462
|
...allowCredentials ? {
|
|
41642
42463
|
allowCredentials: allowCredentials.map((c) => ({
|
|
41643
|
-
id:
|
|
42464
|
+
id: base64urlDecode2(c.id).buffer,
|
|
41644
42465
|
type: "public-key"
|
|
41645
42466
|
}))
|
|
41646
42467
|
} : {}
|
|
@@ -41661,7 +42482,7 @@ function verifyApprovalAssertion(challenge, assertion) {
|
|
|
41661
42482
|
approvedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
41662
42483
|
};
|
|
41663
42484
|
}
|
|
41664
|
-
const authData =
|
|
42485
|
+
const authData = base64urlDecode2(assertion.authenticatorData);
|
|
41665
42486
|
const flags = authData[32];
|
|
41666
42487
|
const userPresent = !!(flags & 1);
|
|
41667
42488
|
const userVerified = !!(flags & 4);
|
|
@@ -41669,7 +42490,7 @@ function verifyApprovalAssertion(challenge, assertion) {
|
|
|
41669
42490
|
const signCount = authData.length >= 37 ? authData[33] << 24 | authData[34] << 16 | authData[35] << 8 | authData[36] : 0;
|
|
41670
42491
|
let authenticatorType = "unknown";
|
|
41671
42492
|
try {
|
|
41672
|
-
const clientData = JSON.parse(Buffer.from(
|
|
42493
|
+
const clientData = JSON.parse(Buffer.from(base64urlDecode2(assertion.clientDataJSON)).toString());
|
|
41673
42494
|
if (clientData.type === "webauthn.get") {
|
|
41674
42495
|
authenticatorType = "platform";
|
|
41675
42496
|
}
|
|
@@ -41698,13 +42519,13 @@ function createApprovalReceiptPayload(challenge, result) {
|
|
|
41698
42519
|
context_hash: result.contextHash,
|
|
41699
42520
|
approved_at: result.approvedAt,
|
|
41700
42521
|
// Hash the credential ID for privacy — don't store the raw ID
|
|
41701
|
-
credential_id_hash: (0,
|
|
42522
|
+
credential_id_hash: (0, import_node_crypto8.createHash)("sha256").update(result.credentialId).digest("hex").slice(0, 16)
|
|
41702
42523
|
};
|
|
41703
42524
|
}
|
|
41704
42525
|
function base64urlEncode(buffer) {
|
|
41705
42526
|
return Buffer.from(buffer).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
41706
42527
|
}
|
|
41707
|
-
function
|
|
42528
|
+
function base64urlDecode2(str) {
|
|
41708
42529
|
const base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
41709
42530
|
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
41710
42531
|
return new Uint8Array(Buffer.from(padded, "base64"));
|
|
@@ -41952,7 +42773,7 @@ function evaluatePolicy(tool, policy) {
|
|
|
41952
42773
|
}
|
|
41953
42774
|
|
|
41954
42775
|
// src/evidence-authenticity.ts
|
|
41955
|
-
var
|
|
42776
|
+
var import_node_crypto9 = require("crypto");
|
|
41956
42777
|
async function createEvidenceAttestation(input) {
|
|
41957
42778
|
const tlsNotaryAvailable = await isTLSNotaryAvailable();
|
|
41958
42779
|
if (tlsNotaryAvailable) {
|
|
@@ -42012,14 +42833,14 @@ async function verifyEvidenceAttestation(attestation) {
|
|
|
42012
42833
|
}
|
|
42013
42834
|
}
|
|
42014
42835
|
function hashResponseBody(body) {
|
|
42015
|
-
return (0,
|
|
42836
|
+
return (0, import_node_crypto9.createHash)("sha256").update(typeof body === "string" ? body : body).digest("hex");
|
|
42016
42837
|
}
|
|
42017
42838
|
function createAttestationField(attestation) {
|
|
42018
42839
|
return {
|
|
42019
42840
|
evidence_authenticity: {
|
|
42020
42841
|
version: attestation.version,
|
|
42021
42842
|
method: attestation.method,
|
|
42022
|
-
url_hash: (0,
|
|
42843
|
+
url_hash: (0, import_node_crypto9.createHash)("sha256").update(attestation.url).digest("hex").slice(0, 16),
|
|
42023
42844
|
response_hash: attestation.responseHash,
|
|
42024
42845
|
fetched_at: attestation.fetchedAt,
|
|
42025
42846
|
verified: attestation.verified,
|
|
@@ -42050,7 +42871,7 @@ async function createTLSNotaryAttestation(input) {
|
|
|
42050
42871
|
}
|
|
42051
42872
|
|
|
42052
42873
|
// src/c2pa-credentials.ts
|
|
42053
|
-
var
|
|
42874
|
+
var import_node_crypto10 = require("crypto");
|
|
42054
42875
|
function createC2PAManifest(receipts, options) {
|
|
42055
42876
|
const generator = options.generator || "protect-mcp";
|
|
42056
42877
|
const version = options.version || "0.3.3";
|
|
@@ -42064,7 +42885,7 @@ function createC2PAManifest(receipts, options) {
|
|
|
42064
42885
|
(r) => r.payload?.decision === "deny"
|
|
42065
42886
|
);
|
|
42066
42887
|
const receiptHashes = receipts.map(
|
|
42067
|
-
(r) => (0,
|
|
42888
|
+
(r) => (0, import_node_crypto10.createHash)("sha256").update(JSON.stringify(r)).digest("hex")
|
|
42068
42889
|
);
|
|
42069
42890
|
const merkleRoot2 = computeMerkleRoot(receiptHashes);
|
|
42070
42891
|
const assertions = [
|
|
@@ -42206,7 +43027,7 @@ function computeMerkleRoot(hashes) {
|
|
|
42206
43027
|
const left = hashes[i];
|
|
42207
43028
|
const right = i + 1 < hashes.length ? hashes[i + 1] : left;
|
|
42208
43029
|
nextLevel.push(
|
|
42209
|
-
(0,
|
|
43030
|
+
(0, import_node_crypto10.createHash)("sha256").update(left + right).digest("hex")
|
|
42210
43031
|
);
|
|
42211
43032
|
}
|
|
42212
43033
|
return computeMerkleRoot(nextLevel);
|
|
@@ -42259,7 +43080,7 @@ function toManifoldFormat(prediction) {
|
|
|
42259
43080
|
}
|
|
42260
43081
|
|
|
42261
43082
|
// src/agent-exchange.ts
|
|
42262
|
-
var
|
|
43083
|
+
var import_node_crypto11 = require("crypto");
|
|
42263
43084
|
var ReceiptPropagator = class {
|
|
42264
43085
|
issuer;
|
|
42265
43086
|
signer;
|
|
@@ -42280,7 +43101,7 @@ var ReceiptPropagator = class {
|
|
|
42280
43101
|
delegate(delegateId, options) {
|
|
42281
43102
|
const now = /* @__PURE__ */ new Date();
|
|
42282
43103
|
const receipt = {
|
|
42283
|
-
receipt_id: `del_${(0,
|
|
43104
|
+
receipt_id: `del_${(0, import_node_crypto11.randomUUID)().slice(0, 12)}`,
|
|
42284
43105
|
receipt_type: "delegation",
|
|
42285
43106
|
issuer_id: this.issuer,
|
|
42286
43107
|
event_time: now.toISOString(),
|
|
@@ -42337,7 +43158,7 @@ var ReceiptPropagator = class {
|
|
|
42337
43158
|
const currentCount = this.delegationCallCounts.get(options.delegation_receipt) || 0;
|
|
42338
43159
|
this.delegationCallCounts.set(options.delegation_receipt, currentCount + 1);
|
|
42339
43160
|
const receipt = {
|
|
42340
|
-
receipt_id: `act_${(0,
|
|
43161
|
+
receipt_id: `act_${(0, import_node_crypto11.randomUUID)().slice(0, 12)}`,
|
|
42341
43162
|
receipt_type: "execution",
|
|
42342
43163
|
issuer_id: this.issuer,
|
|
42343
43164
|
event_time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -42429,7 +43250,7 @@ function createReceiptChannel(orchestratorId) {
|
|
|
42429
43250
|
async withDelegation(delegateId, tools, fn, options) {
|
|
42430
43251
|
const delegation = propagator.delegate(delegateId, {
|
|
42431
43252
|
tools,
|
|
42432
|
-
scope: options?.scope || `task-${(0,
|
|
43253
|
+
scope: options?.scope || `task-${(0, import_node_crypto11.randomUUID)().slice(0, 8)}`,
|
|
42433
43254
|
ttl: options?.ttl || 3600,
|
|
42434
43255
|
maxCalls: options?.maxCalls
|
|
42435
43256
|
});
|
|
@@ -42579,6 +43400,48 @@ var TOOLS = [
|
|
|
42579
43400
|
},
|
|
42580
43401
|
required: ["environment"]
|
|
42581
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
|
+
}
|
|
42582
43445
|
}
|
|
42583
43446
|
];
|
|
42584
43447
|
function handleRequest(request) {
|
|
@@ -42626,6 +43489,15 @@ Contents: Hello from protect-mcp demo server!`;
|
|
|
42626
43489
|
case "deploy":
|
|
42627
43490
|
resultText = `[demo] Deployed to ${args.environment || "staging"}${args.reason ? ` (reason: ${args.reason})` : ""}`;
|
|
42628
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;
|
|
42629
43501
|
default:
|
|
42630
43502
|
resultText = `[demo] Unknown tool: ${toolName}`;
|
|
42631
43503
|
}
|
|
@@ -42698,12 +43570,38 @@ function createSandboxServer() {
|
|
|
42698
43570
|
{ environment: z.enum(["staging", "production"]).describe("Target environment"), reason: z.string().optional().describe("Deployment reason") },
|
|
42699
43571
|
async (args) => ({ content: [{ type: "text", text: `[demo] Deployed to ${args.environment}` }] })
|
|
42700
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
|
+
}));
|
|
42701
43597
|
return server;
|
|
42702
43598
|
}
|
|
42703
43599
|
// Annotate the CommonJS export names for ESM import in node:
|
|
42704
43600
|
0 && (module.exports = {
|
|
42705
43601
|
BUILTIN_PATTERNS,
|
|
43602
|
+
CONNECTOR_PILOTS,
|
|
42706
43603
|
ConfidentialGate,
|
|
43604
|
+
POLICY_PACKS,
|
|
42707
43605
|
ProtectGateway,
|
|
42708
43606
|
ReceiptPropagator,
|
|
42709
43607
|
ScopeBlindBridge,
|
|
@@ -42713,6 +43611,9 @@ function createSandboxServer() {
|
|
|
42713
43611
|
collectSignedReceipts,
|
|
42714
43612
|
computeCalibration,
|
|
42715
43613
|
confidentialInference,
|
|
43614
|
+
connectorDirectory,
|
|
43615
|
+
connectorDoctor,
|
|
43616
|
+
connectorPilotIds,
|
|
42716
43617
|
createApprovalChallenge,
|
|
42717
43618
|
createApprovalReceiptPayload,
|
|
42718
43619
|
createAttestationField,
|
|
@@ -42724,6 +43625,7 @@ function createSandboxServer() {
|
|
|
42724
43625
|
createReceiptChannel,
|
|
42725
43626
|
createSandbox,
|
|
42726
43627
|
createSandboxServer,
|
|
43628
|
+
createSelectiveDisclosurePackage,
|
|
42727
43629
|
destroySandbox,
|
|
42728
43630
|
discloseField,
|
|
42729
43631
|
ed25519ToDIDKey,
|
|
@@ -42744,6 +43646,8 @@ function createSandboxServer() {
|
|
|
42744
43646
|
generateSampleCedarPolicy,
|
|
42745
43647
|
generateSchemaStub,
|
|
42746
43648
|
generateVerifyReceiptSkill,
|
|
43649
|
+
getConnectorPilot,
|
|
43650
|
+
getPolicyPack,
|
|
42747
43651
|
getScopeBlindBridge,
|
|
42748
43652
|
getSignerInfo,
|
|
42749
43653
|
getToolPolicy,
|
|
@@ -42764,8 +43668,10 @@ function createSandboxServer() {
|
|
|
42764
43668
|
parseLogFile,
|
|
42765
43669
|
parseNotificationConfigFromEnv,
|
|
42766
43670
|
parseRateLimit,
|
|
43671
|
+
policyPackIds,
|
|
42767
43672
|
policySetFromSource,
|
|
42768
43673
|
queryExternalPDP,
|
|
43674
|
+
readInstalledConnectorPilots,
|
|
42769
43675
|
receiptToVP,
|
|
42770
43676
|
receiptsToHFRows,
|
|
42771
43677
|
redactFields,
|
|
@@ -42789,7 +43695,9 @@ function createSandboxServer() {
|
|
|
42789
43695
|
verifyApprovalAssertion,
|
|
42790
43696
|
verifyCommitment,
|
|
42791
43697
|
verifyEvidenceAttestation,
|
|
42792
|
-
verifyRekorAnchor
|
|
43698
|
+
verifyRekorAnchor,
|
|
43699
|
+
verifySelectiveDisclosurePackage,
|
|
43700
|
+
writeConnectorPilots
|
|
42793
43701
|
});
|
|
42794
43702
|
/**
|
|
42795
43703
|
* scopeblind-bridge.ts
|