protect-mcp 0.6.3 → 0.7.0

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/dist/index.mjs CHANGED
@@ -1,8 +1,24 @@
1
+ import {
2
+ formatReportMarkdown,
3
+ generateReport
4
+ } from "./chunk-JQDVKZBN.mjs";
1
5
  import {
2
6
  formatSimulation,
3
7
  parseLogFile,
4
8
  simulate
5
- } from "./chunk-S4ICHNSP.mjs";
9
+ } from "./chunk-ZBKJANP7.mjs";
10
+ import {
11
+ ProtectGateway,
12
+ buildDecisionContext,
13
+ evaluateTier,
14
+ listCredentialLabels,
15
+ meetsMinTier,
16
+ parseNotificationConfigFromEnv,
17
+ queryExternalPDP,
18
+ resolveCredential,
19
+ sendApprovalNotification,
20
+ validateCredentials
21
+ } from "./chunk-OHUTUFTC.mjs";
6
22
  import {
7
23
  createSandboxServer
8
24
  } from "./chunk-J6L4XCTE.mjs";
@@ -17,23 +33,7 @@ import {
17
33
  forwardReceipt,
18
34
  getScopeBlindBridge,
19
35
  startHookServer
20
- } from "./chunk-3YCKR72H.mjs";
21
- import {
22
- collectSignedReceipts,
23
- createAuditBundle
24
- } from "./chunk-5JXFV37Y.mjs";
25
- import {
26
- ProtectGateway,
27
- buildDecisionContext,
28
- evaluateTier,
29
- listCredentialLabels,
30
- meetsMinTier,
31
- parseNotificationConfigFromEnv,
32
- queryExternalPDP,
33
- resolveCredential,
34
- sendApprovalNotification,
35
- validateCredentials
36
- } from "./chunk-PLKRTBDR.mjs";
36
+ } from "./chunk-X63ELMU4.mjs";
37
37
  import {
38
38
  checkRateLimit,
39
39
  evaluateCedar,
@@ -45,36 +45,43 @@ import {
45
45
  loadCedarPolicies,
46
46
  loadPolicy,
47
47
  parseRateLimit,
48
+ policySetFromSource,
49
+ runEvaluatorSelfTest,
48
50
  signDecision
49
- } from "./chunk-UV53U6D4.mjs";
51
+ } from "./chunk-546U3A7R.mjs";
50
52
  import {
51
- formatReportMarkdown,
52
- generateReport
53
- } from "./chunk-JQDVKZBN.mjs";
53
+ ed25519,
54
+ sha256
55
+ } from "./chunk-LYKNULYU.mjs";
56
+ import {
57
+ bytesToHex,
58
+ hexToBytes,
59
+ randomBytes
60
+ } from "./chunk-D733KAPG.mjs";
61
+ import {
62
+ collectSignedReceipts,
63
+ createAuditBundle
64
+ } from "./chunk-5JXFV37Y.mjs";
54
65
  import "./chunk-PQJP2ZCI.mjs";
55
66
 
56
- // src/signing-committed.ts
57
- import { ed25519 } from "@noble/curves/ed25519";
58
- import { sha256 as sha2563 } from "@noble/hashes/sha256";
59
- import { bytesToHex as bytesToHex3, hexToBytes as hexToBytes3, randomBytes as randomBytes2 } from "@noble/hashes/utils";
67
+ // node_modules/@noble/hashes/esm/sha256.js
68
+ var sha2562 = sha256;
60
69
 
61
70
  // src/commitments/merkle.ts
62
- import { sha256 } from "@noble/hashes/sha256";
63
- import { bytesToHex, hexToBytes } from "@noble/hashes/utils";
64
71
  var DOMAIN_LEAF = 0;
65
72
  var DOMAIN_INTERNAL = 1;
66
73
  function hashLeaf(leafBytes) {
67
74
  const buf = new Uint8Array(leafBytes.length + 1);
68
75
  buf[0] = DOMAIN_LEAF;
69
76
  buf.set(leafBytes, 1);
70
- return sha256(buf);
77
+ return sha2562(buf);
71
78
  }
72
79
  function hashInternal(left, right) {
73
80
  const buf = new Uint8Array(left.length + right.length + 1);
74
81
  buf[0] = DOMAIN_INTERNAL;
75
82
  buf.set(left, 1);
76
83
  buf.set(right, 1 + left.length);
77
- return sha256(buf);
84
+ return sha2562(buf);
78
85
  }
79
86
  function merkleRoot(leafHashes) {
80
87
  if (leafHashes.length === 0) {
@@ -128,9 +135,6 @@ function largestPowerOfTwoLessThan(n) {
128
135
  }
129
136
 
130
137
  // src/commitments/primitives.ts
131
- import { sha256 as sha2562 } from "@noble/hashes/sha256";
132
- import { hmac } from "@noble/hashes/hmac";
133
- import { bytesToHex as bytesToHex2, hexToBytes as hexToBytes2, randomBytes } from "@noble/hashes/utils";
134
138
  function jcs(value) {
135
139
  if (value === null || value === void 0) return "null";
136
140
  if (typeof value === "boolean" || typeof value === "number")
@@ -181,7 +185,7 @@ function leavesFromFields(fields) {
181
185
 
182
186
  // src/signing-committed.ts
183
187
  function freshSalt() {
184
- return randomBytes2(32);
188
+ return randomBytes(32);
185
189
  }
186
190
  function signCommittedDecision(entry, committedFieldNames, signingKey, publicKey, kid, issuer) {
187
191
  const allFields = {
@@ -221,7 +225,7 @@ function signCommittedDecision(entry, committedFieldNames, signingKey, publicKey
221
225
  const { sorted, leafBytes } = leavesFromFields(committedFields);
222
226
  const leafHashes = leafBytes.map(hashLeaf);
223
227
  const root = merkleRoot(leafHashes);
224
- committedFieldsRoot = bytesToHex3(root);
228
+ committedFieldsRoot = bytesToHex(root);
225
229
  sorted.forEach((f, i) => {
226
230
  openings[f.name] = { name: f.name, value: f.value, salt: f.salt, index: i };
227
231
  });
@@ -238,8 +242,8 @@ function signCommittedDecision(entry, committedFieldNames, signingKey, publicKey
238
242
  payload.committed_field_names = committedFields.map((f) => f.name);
239
243
  }
240
244
  const canonical = jcs(payload);
241
- const messageHash = sha2563(new TextEncoder().encode(canonical));
242
- const signatureBytes = ed25519.sign(messageHash, hexToBytes3(signingKey));
245
+ const messageHash = sha2562(new TextEncoder().encode(canonical));
246
+ const signatureBytes = ed25519.sign(messageHash, hexToBytes(signingKey));
243
247
  const signedReceipt = {
244
248
  ...payload,
245
249
  signature: {
@@ -252,7 +256,7 @@ function signCommittedDecision(entry, committedFieldNames, signingKey, publicKey
252
256
  }
253
257
  };
254
258
  const signedJson = JSON.stringify(signedReceipt);
255
- const receiptHash = bytesToHex3(sha2563(new TextEncoder().encode(jcs(signedReceipt))));
259
+ const receiptHash = bytesToHex(sha2562(new TextEncoder().encode(jcs(signedReceipt))));
256
260
  return {
257
261
  signed: signedJson,
258
262
  artifact_type: "decision_receipt_committed_v1",
@@ -787,7 +791,7 @@ function createLogAnchorField(anchor) {
787
791
  }
788
792
 
789
793
  // src/selective-disclosure.ts
790
- import { createHash as createHash2, randomBytes as randomBytes3 } from "crypto";
794
+ import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
791
795
  function redactFields(receipt, fieldsToRedact) {
792
796
  const redacted = JSON.parse(JSON.stringify(receipt));
793
797
  const salts = [];
@@ -803,7 +807,7 @@ function redactFields(receipt, fieldsToRedact) {
803
807
  if (i === parts.length - 1) {
804
808
  if (key in current) {
805
809
  const originalValue = current[key];
806
- const salt = randomBytes3(16).toString("hex");
810
+ const salt = randomBytes2(16).toString("hex");
807
811
  const commitment = computeCommitment(salt, originalValue);
808
812
  salts.push({ field: fieldPath, salt, originalValue });
809
813
  current[key] = `sha256(salt + ${typeof originalValue === "string" ? "..." : JSON.stringify(originalValue).slice(0, 20) + "..."})`;
@@ -1020,13 +1024,9 @@ MIT
1020
1024
  }
1021
1025
 
1022
1026
  // src/webauthn-approval.ts
1023
- import { createHash as createHash3, randomBytes as randomBytes4, timingSafeEqual } from "crypto";
1024
- import { p256 } from "@noble/curves/p256";
1025
- import { ed25519 as ed255192 } from "@noble/curves/ed25519";
1026
- import { sha256 as sha2564 } from "@noble/hashes/sha256";
1027
- import { hexToBytes as hexToBytes4 } from "@noble/hashes/utils";
1027
+ import { createHash as createHash3, randomBytes as randomBytes3 } from "crypto";
1028
1028
  function createApprovalChallenge(requestId, toolName, agentId, rpId = "scopeblind.com", timeoutSeconds = 300) {
1029
- const challengeBytes = randomBytes4(32);
1029
+ const challengeBytes = randomBytes3(32);
1030
1030
  const contextHash = createHash3("sha256").update(JSON.stringify({ requestId, toolName, agentId, timestamp: Date.now() })).digest("hex");
1031
1031
  return {
1032
1032
  challenge: base64urlEncode(challengeBytes),
@@ -1056,72 +1056,43 @@ function toCredentialRequestOptions(challenge, allowCredentials) {
1056
1056
  }
1057
1057
  };
1058
1058
  }
1059
- function verifyApprovalAssertion(challenge, assertion, credentialPublicKey, opts = {}) {
1060
- const now = opts.now ?? Date.now();
1061
- const fail = (reason, partial = {}) => ({
1062
- valid: false,
1063
- reason,
1064
- credentialId: assertion.credentialId,
1065
- authenticatorType: "unknown",
1066
- userVerified: false,
1067
- signCount: 0,
1068
- contextHash: challenge.contextHash,
1069
- approvedAt: new Date(now).toISOString(),
1070
- ...partial
1071
- });
1059
+ function verifyApprovalAssertion(challenge, assertion) {
1072
1060
  const createdAt = new Date(challenge.createdAt).getTime();
1073
- if (now - createdAt > challenge.timeoutSeconds * 1e3) return fail("challenge_expired");
1074
- if (!credentialPublicKey?.publicKeyHex) return fail("missing_credential_public_key");
1075
- const clientDataBytes = base64urlDecode(assertion.clientDataJSON);
1076
- let clientData;
1077
- try {
1078
- clientData = JSON.parse(Buffer.from(clientDataBytes).toString("utf8"));
1079
- } catch {
1080
- return fail("client_data_parse_error");
1061
+ const now = Date.now();
1062
+ if (now - createdAt > challenge.timeoutSeconds * 1e3) {
1063
+ return {
1064
+ valid: false,
1065
+ credentialId: assertion.credentialId,
1066
+ authenticatorType: "unknown",
1067
+ userVerified: false,
1068
+ signCount: 0,
1069
+ contextHash: challenge.contextHash,
1070
+ approvedAt: (/* @__PURE__ */ new Date()).toISOString()
1071
+ };
1081
1072
  }
1082
- if (clientData.type !== "webauthn.get") return fail("wrong_client_data_type");
1083
- if (!constantTimeStrEqual(clientData.challenge ?? "", challenge.challenge)) return fail("challenge_mismatch");
1084
- const allowedOrigins = opts.expectedOrigin ? Array.isArray(opts.expectedOrigin) ? opts.expectedOrigin : [opts.expectedOrigin] : [`https://${challenge.rpId}`];
1085
- if (!clientData.origin || !allowedOrigins.includes(clientData.origin)) return fail("origin_mismatch");
1086
1073
  const authData = base64urlDecode(assertion.authenticatorData);
1087
- if (authData.length < 37) return fail("authenticator_data_too_short");
1088
- const rpIdHash = authData.slice(0, 32);
1089
- const expectedRpIdHash = sha2564(new TextEncoder().encode(challenge.rpId));
1090
- if (!bytesEqual(rpIdHash, expectedRpIdHash)) return fail("rp_id_hash_mismatch");
1091
1074
  const flags = authData[32];
1092
1075
  const userPresent = !!(flags & 1);
1093
1076
  const userVerified = !!(flags & 4);
1094
- if (!userPresent) return fail("user_not_present");
1095
- if ((opts.requireUserVerification ?? true) && !userVerified) return fail("user_verification_required", { userVerified });
1096
- const signCount = authData[33] << 24 | authData[34] << 16 | authData[35] << 8 | authData[36];
1097
- if (typeof opts.prevSignCount === "number" && signCount !== 0 && signCount <= opts.prevSignCount) {
1098
- return fail("sign_count_regression", { userVerified, signCount });
1099
- }
1100
- const signedData = concatBytes(authData, sha2564(clientDataBytes));
1101
- const sigBytes = base64urlDecode(assertion.signature);
1102
- let sigOk = false;
1077
+ const attestedCredData = !!(flags & 64);
1078
+ const signCount = authData.length >= 37 ? authData[33] << 24 | authData[34] << 16 | authData[35] << 8 | authData[36] : 0;
1079
+ let authenticatorType = "unknown";
1103
1080
  try {
1104
- if (credentialPublicKey.alg === -7) {
1105
- sigOk = p256.verify(sigBytes, sha2564(signedData), hexToBytes4(credentialPublicKey.publicKeyHex), { format: "der" });
1106
- } else if (credentialPublicKey.alg === -8) {
1107
- sigOk = ed255192.verify(sigBytes, signedData, hexToBytes4(credentialPublicKey.publicKeyHex));
1108
- } else {
1109
- return fail("unsupported_algorithm", { userVerified, signCount });
1081
+ const clientData = JSON.parse(Buffer.from(base64urlDecode(assertion.clientDataJSON)).toString());
1082
+ if (clientData.type === "webauthn.get") {
1083
+ authenticatorType = "platform";
1110
1084
  }
1111
1085
  } catch {
1112
- sigOk = false;
1113
1086
  }
1114
- if (!sigOk) return fail("invalid_signature", { userVerified, signCount });
1115
1087
  return {
1116
- valid: true,
1088
+ valid: userPresent,
1089
+ // At minimum, user must be present
1117
1090
  credentialId: assertion.credentialId,
1118
- // Heuristic: platform authenticators (TouchID/FaceID/Hello) report UV; roaming
1119
- // keys without a PIN are UP-only. Attachment is authoritative only at registration.
1120
- authenticatorType: userVerified ? "platform" : "cross-platform",
1091
+ authenticatorType,
1121
1092
  userVerified,
1122
1093
  signCount,
1123
1094
  contextHash: challenge.contextHash,
1124
- approvedAt: new Date(now).toISOString()
1095
+ approvedAt: (/* @__PURE__ */ new Date()).toISOString()
1125
1096
  };
1126
1097
  }
1127
1098
  function createApprovalReceiptPayload(challenge, result) {
@@ -1147,22 +1118,6 @@ function base64urlDecode(str) {
1147
1118
  const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
1148
1119
  return new Uint8Array(Buffer.from(padded, "base64"));
1149
1120
  }
1150
- function concatBytes(a, b) {
1151
- const out = new Uint8Array(a.length + b.length);
1152
- out.set(a, 0);
1153
- out.set(b, a.length);
1154
- return out;
1155
- }
1156
- function bytesEqual(a, b) {
1157
- if (a.length !== b.length) return false;
1158
- return timingSafeEqual(Buffer.from(a), Buffer.from(b));
1159
- }
1160
- function constantTimeStrEqual(a, b) {
1161
- const ab = Buffer.from(a, "utf8");
1162
- const bb = Buffer.from(b, "utf8");
1163
- if (ab.length !== bb.length) return false;
1164
- return timingSafeEqual(ab, bb);
1165
- }
1166
1121
 
1167
1122
  // src/did-vc.ts
1168
1123
  function ed25519ToDIDKey(publicKeyHex) {
@@ -2042,12 +1997,14 @@ export {
2042
1997
  parseLogFile,
2043
1998
  parseNotificationConfigFromEnv,
2044
1999
  parseRateLimit,
2000
+ policySetFromSource,
2045
2001
  queryExternalPDP,
2046
2002
  receiptToVP,
2047
2003
  receiptsToHFRows,
2048
2004
  redactFields,
2049
2005
  resolveCredential,
2050
2006
  revealField,
2007
+ runEvaluatorSelfTest,
2051
2008
  runInSandbox,
2052
2009
  sendApprovalNotification,
2053
2010
  signCommittedDecision,
@@ -0,0 +1,77 @@
1
+ import {
2
+ Hash,
3
+ abytes,
4
+ aexists,
5
+ ahash,
6
+ anumber,
7
+ aoutput,
8
+ asyncLoop,
9
+ byteSwap,
10
+ byteSwap32,
11
+ byteSwapIfBE,
12
+ bytesToHex,
13
+ bytesToUtf8,
14
+ checkOpts,
15
+ clean,
16
+ concatBytes,
17
+ createHasher,
18
+ createOptHasher,
19
+ createView,
20
+ createXOFer,
21
+ hexToBytes,
22
+ isBytes,
23
+ isLE,
24
+ kdfInputToBytes,
25
+ nextTick,
26
+ randomBytes,
27
+ rotl,
28
+ rotr,
29
+ swap32IfBE,
30
+ swap8IfBE,
31
+ toBytes,
32
+ u32,
33
+ u8,
34
+ utf8ToBytes,
35
+ wrapConstructor,
36
+ wrapConstructorWithOpts,
37
+ wrapXOFConstructorWithOpts
38
+ } from "./chunk-D733KAPG.mjs";
39
+ import "./chunk-PQJP2ZCI.mjs";
40
+ export {
41
+ Hash,
42
+ abytes,
43
+ aexists,
44
+ ahash,
45
+ anumber,
46
+ aoutput,
47
+ asyncLoop,
48
+ byteSwap,
49
+ byteSwap32,
50
+ byteSwapIfBE,
51
+ bytesToHex,
52
+ bytesToUtf8,
53
+ checkOpts,
54
+ clean,
55
+ concatBytes,
56
+ createHasher,
57
+ createOptHasher,
58
+ createView,
59
+ createXOFer,
60
+ hexToBytes,
61
+ isBytes,
62
+ isLE,
63
+ kdfInputToBytes,
64
+ nextTick,
65
+ randomBytes,
66
+ rotl,
67
+ rotr,
68
+ swap32IfBE,
69
+ swap8IfBE,
70
+ toBytes,
71
+ u32,
72
+ u8,
73
+ utf8ToBytes,
74
+ wrapConstructor,
75
+ wrapConstructorWithOpts,
76
+ wrapXOFConstructorWithOpts
77
+ };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "protect-mcp",
3
- "version": "0.6.3",
3
+ "version": "0.7.0",
4
4
  "mcpName": "com.scopeblind/protect-mcp",
5
- "description": "Cedar policy + Ed25519 signed receipts for AI agent decisions. The open gate behind Legate by ScopeBlind: enforce before action, verify offline. scopeblind.com",
5
+ "description": "Fail-closed Cedar policy gate + signed receipts for AI agent tool calls. Blocks what breaks the rules before it runs, denies on any policy error, and proves the gate is live with a startup self-test.",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "module": "dist/index.mjs",
@@ -24,7 +24,8 @@
24
24
  "files": [
25
25
  "dist",
26
26
  "policies",
27
- "README.md"
27
+ "README.md",
28
+ "CHANGELOG.md"
28
29
  ],
29
30
  "keywords": [
30
31
  "scopeblind",
@@ -62,8 +63,7 @@
62
63
  "url": "https://github.com/scopeblind/scopeblind-gateway/issues"
63
64
  },
64
65
  "dependencies": {
65
- "@noble/curves": "^1.8.0",
66
- "@noble/hashes": "^1.7.0",
66
+ "@veritasacta/artifacts": "^0.2.2",
67
67
  "@veritasacta/protocol": "^0.1.0"
68
68
  },
69
69
  "optionalDependencies": {