protect-mcp 0.7.3 → 0.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/README.md +155 -7
- package/dist/{bundle-XTR3YMPO.mjs → bundle-YUUHAG7O.mjs} +1 -1
- package/dist/{chunk-G67NUBML.mjs → chunk-6E2DHBAR.mjs} +21 -6
- package/dist/chunk-JCMDLN5I.mjs +1053 -0
- package/dist/{chunk-LYKNULYU.mjs → chunk-LJQOALYR.mjs} +152 -0
- package/dist/{chunk-5JXFV37Y.mjs → chunk-PM2ZO57M.mjs} +10 -2
- package/dist/{chunk-J6L4XCTE.mjs → chunk-SETXVE2K.mjs} +77 -2
- package/dist/{chunk-OHUTUFTC.mjs → chunk-VTPZ4G5I.mjs} +19 -15
- package/dist/{chunk-546U3A7R.mjs → chunk-WIPWNWMJ.mjs} +93 -2
- package/dist/chunk-WV4DKYE4.mjs +486 -0
- package/dist/cli.js +4609 -546
- package/dist/cli.mjs +2215 -40
- package/dist/demo-server.d.mts +3 -0
- package/dist/demo-server.d.ts +3 -0
- package/dist/demo-server.js +77 -2
- package/dist/demo-server.mjs +1 -1
- package/dist/{ed25519-DZMMNNVE.mjs → ed25519-BSHMMVNX.mjs} +1 -1
- package/dist/hook-server.js +128 -24
- package/dist/hook-server.mjs +2 -2
- package/dist/{http-transport-R5AO7X6D.mjs → http-transport-JBORN27G.mjs} +3 -3
- package/dist/index.d.mts +158 -10
- package/dist/index.d.ts +158 -10
- package/dist/index.js +2704 -108
- package/dist/index.mjs +1208 -261
- package/dist/receipt-registry-6CAOY6RP.mjs +279 -0
- package/dist/signing-committed-QXCW24RF.mjs +15 -0
- package/package.json +2 -2
- package/dist/chunk-ZBKJANP7.mjs +0 -145
|
@@ -136,6 +136,24 @@ var SHA256_IV = /* @__PURE__ */ Uint32Array.from([
|
|
|
136
136
|
528734635,
|
|
137
137
|
1541459225
|
|
138
138
|
]);
|
|
139
|
+
var SHA384_IV = /* @__PURE__ */ Uint32Array.from([
|
|
140
|
+
3418070365,
|
|
141
|
+
3238371032,
|
|
142
|
+
1654270250,
|
|
143
|
+
914150663,
|
|
144
|
+
2438529370,
|
|
145
|
+
812702999,
|
|
146
|
+
355462360,
|
|
147
|
+
4144912697,
|
|
148
|
+
1731405415,
|
|
149
|
+
4290775857,
|
|
150
|
+
2394180231,
|
|
151
|
+
1750603025,
|
|
152
|
+
3675008525,
|
|
153
|
+
1694076839,
|
|
154
|
+
1203062813,
|
|
155
|
+
3204075428
|
|
156
|
+
]);
|
|
139
157
|
var SHA512_IV = /* @__PURE__ */ Uint32Array.from([
|
|
140
158
|
1779033703,
|
|
141
159
|
4089235720,
|
|
@@ -525,8 +543,30 @@ var SHA512 = class extends HashMD {
|
|
|
525
543
|
this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
|
526
544
|
}
|
|
527
545
|
};
|
|
546
|
+
var SHA384 = class extends SHA512 {
|
|
547
|
+
constructor() {
|
|
548
|
+
super(48);
|
|
549
|
+
this.Ah = SHA384_IV[0] | 0;
|
|
550
|
+
this.Al = SHA384_IV[1] | 0;
|
|
551
|
+
this.Bh = SHA384_IV[2] | 0;
|
|
552
|
+
this.Bl = SHA384_IV[3] | 0;
|
|
553
|
+
this.Ch = SHA384_IV[4] | 0;
|
|
554
|
+
this.Cl = SHA384_IV[5] | 0;
|
|
555
|
+
this.Dh = SHA384_IV[6] | 0;
|
|
556
|
+
this.Dl = SHA384_IV[7] | 0;
|
|
557
|
+
this.Eh = SHA384_IV[8] | 0;
|
|
558
|
+
this.El = SHA384_IV[9] | 0;
|
|
559
|
+
this.Fh = SHA384_IV[10] | 0;
|
|
560
|
+
this.Fl = SHA384_IV[11] | 0;
|
|
561
|
+
this.Gh = SHA384_IV[12] | 0;
|
|
562
|
+
this.Gl = SHA384_IV[13] | 0;
|
|
563
|
+
this.Hh = SHA384_IV[14] | 0;
|
|
564
|
+
this.Hl = SHA384_IV[15] | 0;
|
|
565
|
+
}
|
|
566
|
+
};
|
|
528
567
|
var sha256 = /* @__PURE__ */ createHasher(() => new SHA256());
|
|
529
568
|
var sha512 = /* @__PURE__ */ createHasher(() => new SHA512());
|
|
569
|
+
var sha384 = /* @__PURE__ */ createHasher(() => new SHA384());
|
|
530
570
|
|
|
531
571
|
// node_modules/@noble/curves/esm/utils.js
|
|
532
572
|
var _0n = /* @__PURE__ */ BigInt(0);
|
|
@@ -550,6 +590,10 @@ function _abytes2(value, length, title = "") {
|
|
|
550
590
|
}
|
|
551
591
|
return value;
|
|
552
592
|
}
|
|
593
|
+
function numberToHexUnpadded(num) {
|
|
594
|
+
const hex = num.toString(16);
|
|
595
|
+
return hex.length & 1 ? "0" + hex : hex;
|
|
596
|
+
}
|
|
553
597
|
function hexToNumber(hex) {
|
|
554
598
|
if (typeof hex !== "string")
|
|
555
599
|
throw new Error("hex string expected, got " + typeof hex);
|
|
@@ -612,6 +656,56 @@ function bitLen(n) {
|
|
|
612
656
|
return len;
|
|
613
657
|
}
|
|
614
658
|
var bitMask = (n) => (_1n << BigInt(n)) - _1n;
|
|
659
|
+
function createHmacDrbg(hashLen, qByteLen, hmacFn) {
|
|
660
|
+
if (typeof hashLen !== "number" || hashLen < 2)
|
|
661
|
+
throw new Error("hashLen must be a number");
|
|
662
|
+
if (typeof qByteLen !== "number" || qByteLen < 2)
|
|
663
|
+
throw new Error("qByteLen must be a number");
|
|
664
|
+
if (typeof hmacFn !== "function")
|
|
665
|
+
throw new Error("hmacFn must be a function");
|
|
666
|
+
const u8n = (len) => new Uint8Array(len);
|
|
667
|
+
const u8of = (byte) => Uint8Array.of(byte);
|
|
668
|
+
let v = u8n(hashLen);
|
|
669
|
+
let k = u8n(hashLen);
|
|
670
|
+
let i = 0;
|
|
671
|
+
const reset = () => {
|
|
672
|
+
v.fill(1);
|
|
673
|
+
k.fill(0);
|
|
674
|
+
i = 0;
|
|
675
|
+
};
|
|
676
|
+
const h = (...b) => hmacFn(k, v, ...b);
|
|
677
|
+
const reseed = (seed = u8n(0)) => {
|
|
678
|
+
k = h(u8of(0), seed);
|
|
679
|
+
v = h();
|
|
680
|
+
if (seed.length === 0)
|
|
681
|
+
return;
|
|
682
|
+
k = h(u8of(1), seed);
|
|
683
|
+
v = h();
|
|
684
|
+
};
|
|
685
|
+
const gen = () => {
|
|
686
|
+
if (i++ >= 1e3)
|
|
687
|
+
throw new Error("drbg: tried 1000 values");
|
|
688
|
+
let len = 0;
|
|
689
|
+
const out = [];
|
|
690
|
+
while (len < qByteLen) {
|
|
691
|
+
v = h();
|
|
692
|
+
const sl = v.slice();
|
|
693
|
+
out.push(sl);
|
|
694
|
+
len += v.length;
|
|
695
|
+
}
|
|
696
|
+
return concatBytes(...out);
|
|
697
|
+
};
|
|
698
|
+
const genUntil = (seed, pred) => {
|
|
699
|
+
reset();
|
|
700
|
+
reseed(seed);
|
|
701
|
+
let res = void 0;
|
|
702
|
+
while (!(res = pred(gen())))
|
|
703
|
+
reseed();
|
|
704
|
+
reset();
|
|
705
|
+
return res;
|
|
706
|
+
};
|
|
707
|
+
return genUntil;
|
|
708
|
+
}
|
|
615
709
|
function isHash(val) {
|
|
616
710
|
return typeof val === "function" && Number.isSafeInteger(val.outputLen);
|
|
617
711
|
}
|
|
@@ -975,6 +1069,26 @@ function FpSqrtEven(Fp2, elm) {
|
|
|
975
1069
|
const root = Fp2.sqrt(elm);
|
|
976
1070
|
return Fp2.isOdd(root) ? Fp2.neg(root) : root;
|
|
977
1071
|
}
|
|
1072
|
+
function getFieldBytesLength(fieldOrder) {
|
|
1073
|
+
if (typeof fieldOrder !== "bigint")
|
|
1074
|
+
throw new Error("field order must be bigint");
|
|
1075
|
+
const bitLength = fieldOrder.toString(2).length;
|
|
1076
|
+
return Math.ceil(bitLength / 8);
|
|
1077
|
+
}
|
|
1078
|
+
function getMinHashLength(fieldOrder) {
|
|
1079
|
+
const length = getFieldBytesLength(fieldOrder);
|
|
1080
|
+
return length + Math.ceil(length / 2);
|
|
1081
|
+
}
|
|
1082
|
+
function mapHashToField(key, fieldOrder, isLE = false) {
|
|
1083
|
+
const len = key.length;
|
|
1084
|
+
const fieldLen = getFieldBytesLength(fieldOrder);
|
|
1085
|
+
const minLen = getMinHashLength(fieldOrder);
|
|
1086
|
+
if (len < 16 || len < minLen || len > 1024)
|
|
1087
|
+
throw new Error("expected " + minLen + "-1024 bytes of input, got " + len);
|
|
1088
|
+
const num = isLE ? bytesToNumberLE(key) : bytesToNumberBE(key);
|
|
1089
|
+
const reduced = mod(num, fieldOrder - _1n2) + _1n2;
|
|
1090
|
+
return isLE ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen);
|
|
1091
|
+
}
|
|
978
1092
|
|
|
979
1093
|
// node_modules/@noble/curves/esm/abstract/curve.js
|
|
980
1094
|
var _0n3 = BigInt(0);
|
|
@@ -1168,6 +1282,21 @@ var wNAF = class {
|
|
|
1168
1282
|
return getW(elm) !== 1;
|
|
1169
1283
|
}
|
|
1170
1284
|
};
|
|
1285
|
+
function mulEndoUnsafe(Point, point, k1, k2) {
|
|
1286
|
+
let acc = point;
|
|
1287
|
+
let p1 = Point.ZERO;
|
|
1288
|
+
let p2 = Point.ZERO;
|
|
1289
|
+
while (k1 > _0n3 || k2 > _0n3) {
|
|
1290
|
+
if (k1 & _1n3)
|
|
1291
|
+
p1 = p1.add(acc);
|
|
1292
|
+
if (k2 & _1n3)
|
|
1293
|
+
p2 = p2.add(acc);
|
|
1294
|
+
acc = acc.double();
|
|
1295
|
+
k1 >>= _1n3;
|
|
1296
|
+
k2 >>= _1n3;
|
|
1297
|
+
}
|
|
1298
|
+
return { p1, p2 };
|
|
1299
|
+
}
|
|
1171
1300
|
function pippenger(c, fieldN, points, scalars) {
|
|
1172
1301
|
validateMSMPoints(points, c);
|
|
1173
1302
|
validateMSMScalars(scalars, fieldN);
|
|
@@ -2417,6 +2546,29 @@ var hash_to_ristretto255 = /* @__PURE__ */ (() => ristretto255_hasher.hashToCurv
|
|
|
2417
2546
|
|
|
2418
2547
|
export {
|
|
2419
2548
|
sha256,
|
|
2549
|
+
sha512,
|
|
2550
|
+
sha384,
|
|
2551
|
+
_abool2,
|
|
2552
|
+
_abytes2,
|
|
2553
|
+
numberToHexUnpadded,
|
|
2554
|
+
bytesToNumberBE,
|
|
2555
|
+
ensureBytes,
|
|
2556
|
+
aInRange,
|
|
2557
|
+
bitLen,
|
|
2558
|
+
bitMask,
|
|
2559
|
+
createHmacDrbg,
|
|
2560
|
+
_validateObject,
|
|
2561
|
+
memoized,
|
|
2562
|
+
nLength,
|
|
2563
|
+
Field,
|
|
2564
|
+
getMinHashLength,
|
|
2565
|
+
mapHashToField,
|
|
2566
|
+
negateCt,
|
|
2567
|
+
normalizeZ,
|
|
2568
|
+
wNAF,
|
|
2569
|
+
mulEndoUnsafe,
|
|
2570
|
+
pippenger,
|
|
2571
|
+
_createCurveFields,
|
|
2420
2572
|
ed25519,
|
|
2421
2573
|
ed25519ctx,
|
|
2422
2574
|
ed25519ph,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/bundle.ts
|
|
2
2
|
function createAuditBundle(opts) {
|
|
3
3
|
const receipts = opts.receipts.filter(
|
|
4
|
-
(r) => r && typeof r === "object" && typeof r.signature === "string"
|
|
4
|
+
(r) => r && typeof r === "object" && (typeof r.signature === "string" || r.signature !== null && typeof r.signature === "object")
|
|
5
5
|
);
|
|
6
6
|
if (receipts.length === 0) {
|
|
7
7
|
throw new Error("Audit bundle requires at least one signed receipt");
|
|
@@ -30,10 +30,18 @@ function createAuditBundle(opts) {
|
|
|
30
30
|
time_range: timeRange,
|
|
31
31
|
receipts,
|
|
32
32
|
anchors: opts.anchors || [],
|
|
33
|
+
selective_disclosures: opts.selectiveDisclosures || [],
|
|
34
|
+
privacy: {
|
|
35
|
+
selective_disclosure: {
|
|
36
|
+
supported: true,
|
|
37
|
+
model: "salted_commitments_merkle_v0",
|
|
38
|
+
statement: "Committed receipts may disclose selected fields with salted Merkle openings. Undisclosed committed fields remain hidden while staying bound to the signed commitment root."
|
|
39
|
+
}
|
|
40
|
+
},
|
|
33
41
|
verification: {
|
|
34
42
|
algorithm: "ed25519",
|
|
35
43
|
signing_keys: Array.from(keyMap.values()),
|
|
36
|
-
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`
|
|
44
|
+
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`
|
|
37
45
|
}
|
|
38
46
|
};
|
|
39
47
|
}
|
|
@@ -35040,6 +35040,48 @@ var TOOLS = [
|
|
|
35040
35040
|
},
|
|
35041
35041
|
required: ["environment"]
|
|
35042
35042
|
}
|
|
35043
|
+
},
|
|
35044
|
+
{
|
|
35045
|
+
name: "github_create_pr",
|
|
35046
|
+
description: "Create a GitHub pull request",
|
|
35047
|
+
inputSchema: {
|
|
35048
|
+
type: "object",
|
|
35049
|
+
properties: {
|
|
35050
|
+
repo: { type: "string", description: "Repository name" },
|
|
35051
|
+
branch: { type: "string", description: "Source branch" },
|
|
35052
|
+
title: { type: "string", description: "Pull request title" }
|
|
35053
|
+
},
|
|
35054
|
+
required: ["repo", "branch", "title"]
|
|
35055
|
+
}
|
|
35056
|
+
},
|
|
35057
|
+
{
|
|
35058
|
+
name: "send_email",
|
|
35059
|
+
description: "Send an email",
|
|
35060
|
+
inputSchema: {
|
|
35061
|
+
type: "object",
|
|
35062
|
+
properties: {
|
|
35063
|
+
to: { type: "string", description: "Recipient email address" },
|
|
35064
|
+
subject: { type: "string", description: "Subject line" },
|
|
35065
|
+
body: { type: "string", description: "Message body" }
|
|
35066
|
+
},
|
|
35067
|
+
required: ["to", "subject"]
|
|
35068
|
+
}
|
|
35069
|
+
},
|
|
35070
|
+
{
|
|
35071
|
+
name: "pms_book_fill",
|
|
35072
|
+
description: "Book a fill into the mock portfolio management system",
|
|
35073
|
+
inputSchema: {
|
|
35074
|
+
type: "object",
|
|
35075
|
+
properties: {
|
|
35076
|
+
account: { type: "string", description: "Portfolio or fund account" },
|
|
35077
|
+
symbol: { type: "string", description: "Instrument symbol" },
|
|
35078
|
+
side: { type: "string", enum: ["BUY", "SELL"] },
|
|
35079
|
+
quantity: { type: "number" },
|
|
35080
|
+
price: { type: "number" },
|
|
35081
|
+
strategy: { type: "string" }
|
|
35082
|
+
},
|
|
35083
|
+
required: ["account", "symbol", "side", "quantity", "price"]
|
|
35084
|
+
}
|
|
35043
35085
|
}
|
|
35044
35086
|
];
|
|
35045
35087
|
function handleRequest(request) {
|
|
@@ -35049,7 +35091,7 @@ function handleRequest(request) {
|
|
|
35049
35091
|
id: request.id,
|
|
35050
35092
|
result: {
|
|
35051
35093
|
protocolVersion: "2024-11-05",
|
|
35052
|
-
serverInfo: { name: "protect-mcp-demo", version: "0.5.3" },
|
|
35094
|
+
serverInfo: { name: "protect-mcp-demo", version: process.env.PROTECT_MCP_VERSION || "0.5.3" },
|
|
35053
35095
|
capabilities: { tools: {} }
|
|
35054
35096
|
}
|
|
35055
35097
|
});
|
|
@@ -35087,6 +35129,15 @@ Contents: Hello from protect-mcp demo server!`;
|
|
|
35087
35129
|
case "deploy":
|
|
35088
35130
|
resultText = `[demo] Deployed to ${args.environment || "staging"}${args.reason ? ` (reason: ${args.reason})` : ""}`;
|
|
35089
35131
|
break;
|
|
35132
|
+
case "github_create_pr":
|
|
35133
|
+
resultText = `[demo] Created PR in ${args.repo || "scopeblind/demo"} from ${args.branch || "agent/demo"}: ${args.title || "Agent change"}`;
|
|
35134
|
+
break;
|
|
35135
|
+
case "send_email":
|
|
35136
|
+
resultText = `[demo] Drafted email to ${args.to || "pm@example.com"}: ${args.subject || "Agent update"}`;
|
|
35137
|
+
break;
|
|
35138
|
+
case "pms_book_fill":
|
|
35139
|
+
resultText = `[demo] Booked ${args.side || "BUY"} ${args.quantity || 0} ${args.symbol || "AAPL"} @ ${args.price || 0} into ${args.account || "Demo Fund"} (${args.strategy || "Unassigned"})`;
|
|
35140
|
+
break;
|
|
35090
35141
|
default:
|
|
35091
35142
|
resultText = `[demo] Unknown tool: ${toolName}`;
|
|
35092
35143
|
}
|
|
@@ -35126,7 +35177,7 @@ function createSandboxServer() {
|
|
|
35126
35177
|
const { z } = require_zod();
|
|
35127
35178
|
const server = new McpServer({
|
|
35128
35179
|
name: "protect-mcp",
|
|
35129
|
-
version: "0.4.5",
|
|
35180
|
+
version: process.env.PROTECT_MCP_VERSION || "0.4.5",
|
|
35130
35181
|
description: "Security gateway for MCP servers. Per-tool policies, Ed25519-signed receipts, human approval gates, trust tiers."
|
|
35131
35182
|
});
|
|
35132
35183
|
server.tool(
|
|
@@ -35159,6 +35210,30 @@ function createSandboxServer() {
|
|
|
35159
35210
|
{ environment: z.enum(["staging", "production"]).describe("Target environment"), reason: z.string().optional().describe("Deployment reason") },
|
|
35160
35211
|
async (args) => ({ content: [{ type: "text", text: `[demo] Deployed to ${args.environment}` }] })
|
|
35161
35212
|
);
|
|
35213
|
+
server.tool("github_create_pr", "Create a GitHub pull request", {
|
|
35214
|
+
repo: z.string(),
|
|
35215
|
+
branch: z.string(),
|
|
35216
|
+
title: z.string()
|
|
35217
|
+
}, async (args) => ({
|
|
35218
|
+
content: [{ type: "text", text: `[demo] PR created in ${args.repo} from ${args.branch}: ${args.title}` }]
|
|
35219
|
+
}));
|
|
35220
|
+
server.tool("send_email", "Send an email", {
|
|
35221
|
+
to: z.string(),
|
|
35222
|
+
subject: z.string(),
|
|
35223
|
+
body: z.string().optional()
|
|
35224
|
+
}, async (args) => ({
|
|
35225
|
+
content: [{ type: "text", text: `[demo] Email prepared for ${args.to}: ${args.subject}` }]
|
|
35226
|
+
}));
|
|
35227
|
+
server.tool("pms_book_fill", "Book a fill into a mock PMS", {
|
|
35228
|
+
account: z.string(),
|
|
35229
|
+
symbol: z.string(),
|
|
35230
|
+
side: z.enum(["BUY", "SELL"]),
|
|
35231
|
+
quantity: z.number(),
|
|
35232
|
+
price: z.number(),
|
|
35233
|
+
strategy: z.string().optional()
|
|
35234
|
+
}, async (args) => ({
|
|
35235
|
+
content: [{ type: "text", text: `[demo] Booked ${args.side} ${args.quantity} ${args.symbol} @ ${args.price} into ${args.account}` }]
|
|
35236
|
+
}));
|
|
35162
35237
|
return server;
|
|
35163
35238
|
}
|
|
35164
35239
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ReceiptBuffer,
|
|
3
|
+
buildActionReadback,
|
|
3
4
|
checkRateLimit,
|
|
4
5
|
evaluateCedar,
|
|
5
6
|
getToolPolicy,
|
|
@@ -7,7 +8,7 @@ import {
|
|
|
7
8
|
parseRateLimit,
|
|
8
9
|
signDecision,
|
|
9
10
|
startStatusServer
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-WIPWNWMJ.mjs";
|
|
11
12
|
|
|
12
13
|
// src/evidence-store.ts
|
|
13
14
|
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
@@ -758,6 +759,8 @@ var ProtectGateway = class {
|
|
|
758
759
|
const toolName = request.params?.name || "unknown";
|
|
759
760
|
const requestId = randomUUID().slice(0, 12);
|
|
760
761
|
const mode = this.config.enforce ? "enforce" : "shadow";
|
|
762
|
+
const toolInput = request.params?.arguments && typeof request.params.arguments === "object" ? request.params.arguments : request.params || {};
|
|
763
|
+
const actionReadback = buildActionReadback(toolName, toolInput);
|
|
761
764
|
let resolvedAgentKid = this.admissionResult?.agent_id;
|
|
762
765
|
let effectiveToolPolicy;
|
|
763
766
|
if (this.config.multiAgent?.enabled) {
|
|
@@ -767,7 +770,7 @@ var ProtectGateway = class {
|
|
|
767
770
|
if (agentOverrides && agentOverrides[toolName]) {
|
|
768
771
|
effectiveToolPolicy = { ...getToolPolicy(toolName, this.config.policy), ...agentOverrides[toolName] };
|
|
769
772
|
} else if (!resolvedAgentKid && this.config.multiAgent.unknownAgentPolicy === "deny") {
|
|
770
|
-
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "unknown_agent_denied", request_id: requestId, tier: this.currentTier });
|
|
773
|
+
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "unknown_agent_denied", request_id: requestId, tier: this.currentTier, action_readback: actionReadback });
|
|
771
774
|
if (this.config.enforce) {
|
|
772
775
|
return this.makeErrorResponse(request.id, -32600, `Tool "${toolName}" denied: unidentified agent`);
|
|
773
776
|
}
|
|
@@ -788,7 +791,7 @@ var ProtectGateway = class {
|
|
|
788
791
|
if (cred.resolved) {
|
|
789
792
|
credentialRef = cred.label;
|
|
790
793
|
} else if (cred.error && !cred.error.includes("not configured")) {
|
|
791
|
-
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "credential_error", request_id: requestId, tier: this.currentTier, credential_ref: toolName });
|
|
794
|
+
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "credential_error", request_id: requestId, tier: this.currentTier, credential_ref: toolName, action_readback: actionReadback });
|
|
792
795
|
if (this.config.enforce) {
|
|
793
796
|
return this.makeErrorResponse(request.id, -32600, `Credential error for tool "${toolName}"`);
|
|
794
797
|
}
|
|
@@ -803,13 +806,13 @@ var ProtectGateway = class {
|
|
|
803
806
|
});
|
|
804
807
|
if (!cedarDecision.allowed) {
|
|
805
808
|
const reason = cedarDecision.reason || "cedar_deny";
|
|
806
|
-
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: reason, request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
|
|
809
|
+
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: reason, request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
|
|
807
810
|
if (this.config.enforce) {
|
|
808
811
|
return this.makeErrorResponse(request.id, -32600, `Tool "${toolName}" denied by Cedar policy`);
|
|
809
812
|
}
|
|
810
813
|
return null;
|
|
811
814
|
}
|
|
812
|
-
this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: "cedar_allow", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
|
|
815
|
+
this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: "cedar_allow", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
|
|
813
816
|
return null;
|
|
814
817
|
} catch (err) {
|
|
815
818
|
if (this.config.verbose) this.log(`Cedar evaluation error: ${err instanceof Error ? err.message : err}`);
|
|
@@ -827,7 +830,7 @@ var ProtectGateway = class {
|
|
|
827
830
|
const externalDecision = await queryExternalPDP(ctx, this.config.policy.external);
|
|
828
831
|
if (!externalDecision.allowed) {
|
|
829
832
|
const reason = `external_pdp_deny${externalDecision.reason ? ": " + externalDecision.reason : ""}`;
|
|
830
|
-
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: reason, request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
|
|
833
|
+
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: reason, request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
|
|
831
834
|
if (this.config.enforce) {
|
|
832
835
|
return this.makeErrorResponse(request.id, -32600, `Tool "${toolName}" denied by external policy engine`);
|
|
833
836
|
}
|
|
@@ -839,7 +842,7 @@ var ProtectGateway = class {
|
|
|
839
842
|
}
|
|
840
843
|
if (toolPolicy.min_tier) {
|
|
841
844
|
if (!meetsMinTier(this.currentTier, toolPolicy.min_tier)) {
|
|
842
|
-
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "tier_insufficient", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
|
|
845
|
+
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "tier_insufficient", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
|
|
843
846
|
if (this.config.enforce) {
|
|
844
847
|
return this.makeErrorResponse(request.id, -32600, `Tool "${toolName}" requires tier "${toolPolicy.min_tier}"`);
|
|
845
848
|
}
|
|
@@ -847,7 +850,7 @@ var ProtectGateway = class {
|
|
|
847
850
|
}
|
|
848
851
|
}
|
|
849
852
|
if (toolPolicy.block) {
|
|
850
|
-
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "policy_block", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
|
|
853
|
+
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "policy_block", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
|
|
851
854
|
if (this.config.enforce) {
|
|
852
855
|
return this.makeErrorResponse(request.id, -32600, `Tool "${toolName}" is blocked by policy`);
|
|
853
856
|
}
|
|
@@ -858,10 +861,10 @@ var ProtectGateway = class {
|
|
|
858
861
|
const alwaysGrant = this.approvalStore.get(`always:${toolName}`);
|
|
859
862
|
if (grant && Date.now() < grant.expires_at || alwaysGrant && Date.now() < alwaysGrant.expires_at) {
|
|
860
863
|
if (grant && grant.mode === "once") this.approvalStore.delete(requestId);
|
|
861
|
-
this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: "approval_granted", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
|
|
864
|
+
this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: "approval_granted", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
|
|
862
865
|
return null;
|
|
863
866
|
}
|
|
864
|
-
this.emitDecisionLog({ tool: toolName, decision: "require_approval", reason_code: "requires_human_approval", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
|
|
867
|
+
this.emitDecisionLog({ tool: toolName, decision: "require_approval", reason_code: "requires_human_approval", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
|
|
865
868
|
if (this.notificationConfig) {
|
|
866
869
|
sendApprovalNotification(this.notificationConfig, {
|
|
867
870
|
requestId,
|
|
@@ -884,7 +887,7 @@ var ProtectGateway = class {
|
|
|
884
887
|
content: [
|
|
885
888
|
{
|
|
886
889
|
type: "text",
|
|
887
|
-
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.`
|
|
890
|
+
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.`
|
|
888
891
|
}
|
|
889
892
|
],
|
|
890
893
|
isError: true
|
|
@@ -900,19 +903,19 @@ var ProtectGateway = class {
|
|
|
900
903
|
const key = `tool:${toolName}:${this.currentTier}`;
|
|
901
904
|
const { allowed, remaining } = checkRateLimit(key, limit, this.rateLimitStore);
|
|
902
905
|
if (!allowed) {
|
|
903
|
-
this.emitDecisionLog({ tool: toolName, decision: "deny", reason_code: "rate_limit_exceeded", request_id: requestId, rate_limit_remaining: 0, tier: this.currentTier, credential_ref: credentialRef });
|
|
906
|
+
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 });
|
|
904
907
|
if (this.config.enforce) {
|
|
905
908
|
return this.makeErrorResponse(request.id, -32600, `Tool "${toolName}" rate limit exceeded (${rateSpec})`);
|
|
906
909
|
}
|
|
907
910
|
return null;
|
|
908
911
|
}
|
|
909
|
-
this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: "policy_allow", request_id: requestId, rate_limit_remaining: remaining, tier: this.currentTier, credential_ref: credentialRef });
|
|
912
|
+
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 });
|
|
910
913
|
} catch {
|
|
911
|
-
this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: "default_allow", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
|
|
914
|
+
this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: "default_allow", request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
|
|
912
915
|
}
|
|
913
916
|
} else {
|
|
914
917
|
const reasonCode = this.config.enforce ? "policy_allow" : "observe_mode";
|
|
915
|
-
this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: reasonCode, request_id: requestId, tier: this.currentTier, credential_ref: credentialRef });
|
|
918
|
+
this.emitDecisionLog({ tool: toolName, decision: "allow", reason_code: reasonCode, request_id: requestId, tier: this.currentTier, credential_ref: credentialRef, action_readback: actionReadback });
|
|
916
919
|
}
|
|
917
920
|
return null;
|
|
918
921
|
}
|
|
@@ -949,6 +952,7 @@ var ProtectGateway = class {
|
|
|
949
952
|
...entry.rate_limit_remaining !== void 0 && { rate_limit_remaining: entry.rate_limit_remaining },
|
|
950
953
|
...entry.tier && { tier: entry.tier },
|
|
951
954
|
...entry.credential_ref && { credential_ref: entry.credential_ref },
|
|
955
|
+
...entry.action_readback && { action_readback: entry.action_readback },
|
|
952
956
|
otel_trace_id: otelTraceId,
|
|
953
957
|
otel_span_id: otelSpanId
|
|
954
958
|
};
|
|
@@ -190,6 +190,7 @@ function signDecision(entry) {
|
|
|
190
190
|
if (entry.timing) payload.timing = entry.timing;
|
|
191
191
|
if (entry.swarm) payload.swarm = entry.swarm;
|
|
192
192
|
if (entry.payload_digest) payload.payload_digest = entry.payload_digest;
|
|
193
|
+
if (entry.action_readback) payload.action_readback = entry.action_readback;
|
|
193
194
|
if (entry.deny_iteration) payload.deny_iteration = entry.deny_iteration;
|
|
194
195
|
const result = artifactsModule.createSignedArtifact(
|
|
195
196
|
artifactType,
|
|
@@ -523,7 +524,7 @@ function handleHealth(res, startTime, config) {
|
|
|
523
524
|
status: "ok",
|
|
524
525
|
uptime_ms: Date.now() - startTime,
|
|
525
526
|
mode: config.mode,
|
|
526
|
-
version:
|
|
527
|
+
version: process.env.PROTECT_MCP_VERSION || "unknown"
|
|
527
528
|
}));
|
|
528
529
|
}
|
|
529
530
|
function handleStatus(res, logDir) {
|
|
@@ -656,6 +657,95 @@ function handleListApprovals(res, approvalStore) {
|
|
|
656
657
|
res.end(JSON.stringify({ grants }));
|
|
657
658
|
}
|
|
658
659
|
|
|
660
|
+
// src/action-readback.ts
|
|
661
|
+
import { createHash as createHash3 } from "crypto";
|
|
662
|
+
var SECRET_KEY_RE = /(api[_-]?key|authorization|bearer|credential|password|secret|session|token|private[_-]?key)/i;
|
|
663
|
+
var DESTINATION_KEYS = [
|
|
664
|
+
"path",
|
|
665
|
+
"file_path",
|
|
666
|
+
"filePath",
|
|
667
|
+
"url",
|
|
668
|
+
"uri",
|
|
669
|
+
"endpoint",
|
|
670
|
+
"host",
|
|
671
|
+
"hostname",
|
|
672
|
+
"repo",
|
|
673
|
+
"repository",
|
|
674
|
+
"branch",
|
|
675
|
+
"channel",
|
|
676
|
+
"to",
|
|
677
|
+
"recipient",
|
|
678
|
+
"symbol",
|
|
679
|
+
"account",
|
|
680
|
+
"bucket",
|
|
681
|
+
"database",
|
|
682
|
+
"table",
|
|
683
|
+
"service"
|
|
684
|
+
];
|
|
685
|
+
function stableStringify(value) {
|
|
686
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
687
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
688
|
+
const obj = value;
|
|
689
|
+
return `{${Object.keys(obj).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(obj[key])}`).join(",")}}`;
|
|
690
|
+
}
|
|
691
|
+
function redact(value, path = [], redacted = [], disclosed = [], depth = 0) {
|
|
692
|
+
if (depth > 4) return "[truncated-depth]";
|
|
693
|
+
if (value === null || value === void 0) return value;
|
|
694
|
+
if (typeof value !== "object") {
|
|
695
|
+
if (path.length > 0) disclosed.push(path.join("."));
|
|
696
|
+
if (typeof value === "string" && value.length > 240) return `${value.slice(0, 240)}...`;
|
|
697
|
+
return value;
|
|
698
|
+
}
|
|
699
|
+
if (Array.isArray(value)) {
|
|
700
|
+
return value.slice(0, 20).map((item, idx) => redact(item, [...path, String(idx)], redacted, disclosed, depth + 1));
|
|
701
|
+
}
|
|
702
|
+
const out = {};
|
|
703
|
+
for (const [key, child] of Object.entries(value)) {
|
|
704
|
+
const childPath = [...path, key];
|
|
705
|
+
if (SECRET_KEY_RE.test(key)) {
|
|
706
|
+
redacted.push(childPath.join("."));
|
|
707
|
+
out[key] = "[redacted]";
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
out[key] = redact(child, childPath, redacted, disclosed, depth + 1);
|
|
711
|
+
}
|
|
712
|
+
return out;
|
|
713
|
+
}
|
|
714
|
+
function firstStringValue(input, keys) {
|
|
715
|
+
for (const key of keys) {
|
|
716
|
+
const value = input[key];
|
|
717
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
718
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
719
|
+
}
|
|
720
|
+
return void 0;
|
|
721
|
+
}
|
|
722
|
+
function actionFor(tool, input) {
|
|
723
|
+
const explicit = firstStringValue(input, ["action", "operation", "method", "verb", "command"]);
|
|
724
|
+
if (explicit) return explicit.length > 90 ? `${explicit.slice(0, 90)}...` : explicit;
|
|
725
|
+
return tool;
|
|
726
|
+
}
|
|
727
|
+
function buildActionReadback(tool, input) {
|
|
728
|
+
const normalized = input && typeof input === "object" && !Array.isArray(input) ? input : { value: input };
|
|
729
|
+
const canonical = stableStringify(normalized);
|
|
730
|
+
const redactedFields = [];
|
|
731
|
+
const disclosedFields = [];
|
|
732
|
+
const payloadPreview = redact(normalized, [], redactedFields, disclosedFields);
|
|
733
|
+
const action = actionFor(tool, normalized);
|
|
734
|
+
const destination = firstStringValue(normalized, DESTINATION_KEYS);
|
|
735
|
+
const summary = destination ? `${tool} -> ${destination}` : `${tool} request`;
|
|
736
|
+
return {
|
|
737
|
+
tool,
|
|
738
|
+
action,
|
|
739
|
+
destination,
|
|
740
|
+
payload_preview: payloadPreview,
|
|
741
|
+
payload_hash: createHash3("sha256").update(canonical).digest("hex"),
|
|
742
|
+
payload_bytes: Buffer.byteLength(canonical, "utf-8"),
|
|
743
|
+
disclosed_fields: [...new Set(disclosedFields)].slice(0, 80),
|
|
744
|
+
redacted_fields: [...new Set(redactedFields)].slice(0, 80),
|
|
745
|
+
summary
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
|
|
659
749
|
export {
|
|
660
750
|
loadPolicy,
|
|
661
751
|
getToolPolicy,
|
|
@@ -671,5 +761,6 @@ export {
|
|
|
671
761
|
policySetFromSource,
|
|
672
762
|
runEvaluatorSelfTest,
|
|
673
763
|
ReceiptBuffer,
|
|
674
|
-
startStatusServer
|
|
764
|
+
startStatusServer,
|
|
765
|
+
buildActionReadback
|
|
675
766
|
};
|