protect-mcp 0.6.2 → 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.js CHANGED
@@ -35080,12 +35080,14 @@ __export(index_exports, {
35080
35080
  parseLogFile: () => parseLogFile,
35081
35081
  parseNotificationConfigFromEnv: () => parseNotificationConfigFromEnv,
35082
35082
  parseRateLimit: () => parseRateLimit,
35083
+ policySetFromSource: () => policySetFromSource,
35083
35084
  queryExternalPDP: () => queryExternalPDP,
35084
35085
  receiptToVP: () => receiptToVP,
35085
35086
  receiptsToHFRows: () => receiptsToHFRows,
35086
35087
  redactFields: () => redactFields,
35087
35088
  resolveCredential: () => resolveCredential,
35088
35089
  revealField: () => revealField,
35090
+ runEvaluatorSelfTest: () => runEvaluatorSelfTest,
35089
35091
  runInSandbox: () => runInSandbox,
35090
35092
  sendApprovalNotification: () => sendApprovalNotification,
35091
35093
  signCommittedDecision: () => signCommittedDecision,
@@ -35798,14 +35800,18 @@ function buildEntities(req) {
35798
35800
  }
35799
35801
  ];
35800
35802
  }
35801
- async function evaluateCedar(policySet, req, schema) {
35803
+ function onEvalError(reason, failClosed, extra) {
35804
+ return {
35805
+ allowed: !failClosed,
35806
+ reason: failClosed ? reason : `${reason} (observe mode; would DENY under enforcement)`,
35807
+ metadata: { error: true, fail_closed: failClosed, would_deny: true, ...extra || {} }
35808
+ };
35809
+ }
35810
+ async function evaluateCedar(policySet, req, schema, options) {
35811
+ const failClosed = options?.failClosed ?? true;
35802
35812
  const available = await ensureCedarWasm();
35803
35813
  if (!available) {
35804
- return {
35805
- allowed: true,
35806
- reason: "cedar_wasm_not_available",
35807
- metadata: { fallback: true }
35808
- };
35814
+ return onEvalError("cedar_wasm_not_available", failClosed, { fallback: true });
35809
35815
  }
35810
35816
  try {
35811
35817
  const agentId = req.agentId || req.tier;
@@ -35827,7 +35833,7 @@ async function evaluateCedar(policySet, req, schema) {
35827
35833
  let result;
35828
35834
  if (typeof cedarWasm.isAuthorized === "function") {
35829
35835
  result = cedarWasm.isAuthorized({
35830
- policies: policySet.source,
35836
+ policies: { staticPolicies: policySet.source },
35831
35837
  entities,
35832
35838
  principal: authRequest.principal,
35833
35839
  action: authRequest.action,
@@ -35845,7 +35851,7 @@ async function evaluateCedar(policySet, req, schema) {
35845
35851
  const cedarEngine = cedarWasm.default || cedarWasm;
35846
35852
  if (typeof cedarEngine.isAuthorized === "function") {
35847
35853
  result = cedarEngine.isAuthorized({
35848
- policies: policySet.source,
35854
+ policies: { staticPolicies: policySet.source },
35849
35855
  entities,
35850
35856
  principal: authRequest.principal,
35851
35857
  action: authRequest.action,
@@ -35854,61 +35860,87 @@ async function evaluateCedar(policySet, req, schema) {
35854
35860
  schema: cedarSchema
35855
35861
  });
35856
35862
  } else {
35857
- return {
35858
- allowed: true,
35859
- reason: "cedar_wasm_api_unsupported",
35860
- metadata: { fallback: true, exports: Object.keys(cedarWasm) }
35861
- };
35863
+ return onEvalError("cedar_wasm_api_unsupported", failClosed, { exports: Object.keys(cedarWasm) });
35862
35864
  }
35863
35865
  }
35864
- const decision = parseWasmResult(result);
35866
+ const parsed = parseWasmResult(result);
35867
+ const policyErrors = extractPolicyErrors(result);
35868
+ if (parsed.kind === "error") {
35869
+ return onEvalError(`cedar_unparseable_result: ${parsed.diagnostics}`, failClosed);
35870
+ }
35871
+ if (policyErrors.length > 0) {
35872
+ return onEvalError(
35873
+ `cedar_policy_errored: ${policyErrors.length} policy error(s); decision is unsound`,
35874
+ failClosed,
35875
+ { policy_errors: policyErrors.slice(0, 5), policy_digest: policySet.digest }
35876
+ );
35877
+ }
35865
35878
  return {
35866
- allowed: decision.allowed,
35867
- reason: decision.allowed ? void 0 : `cedar_deny${decision.diagnostics ? ": " + decision.diagnostics : ""}`,
35879
+ allowed: parsed.kind === "allow",
35880
+ reason: parsed.kind === "allow" ? void 0 : `cedar_deny${parsed.diagnostics ? ": " + parsed.diagnostics : ""}`,
35868
35881
  metadata: {
35869
35882
  policy_digest: policySet.digest,
35870
- ...decision.matchedPolicies ? { matched_policies: decision.matchedPolicies } : {}
35883
+ ...parsed.matchedPolicies ? { matched_policies: parsed.matchedPolicies } : {}
35871
35884
  }
35872
35885
  };
35873
35886
  } catch (err) {
35874
- return {
35875
- allowed: true,
35876
- reason: `cedar_eval_error: ${err instanceof Error ? err.message : "unknown"}`,
35877
- metadata: { fallback: true, error: true }
35878
- };
35887
+ return onEvalError(`cedar_eval_error: ${err instanceof Error ? err.message : "unknown"}`, failClosed);
35879
35888
  }
35880
35889
  }
35881
35890
  function parseWasmResult(result) {
35882
- if (!result) {
35883
- return { allowed: true, diagnostics: "null result from Cedar WASM" };
35884
- }
35885
- if (result.type === "allow" || result.type === "Allow") {
35886
- return { allowed: true };
35887
- }
35888
- if (result.type === "deny" || result.type === "Deny") {
35889
- return {
35890
- allowed: false,
35891
- diagnostics: result.diagnostics ? JSON.stringify(result.diagnostics) : void 0,
35892
- matchedPolicies: result.diagnostics?.reasons
35893
- };
35894
- }
35895
- if (result.decision === "Allow") {
35896
- return { allowed: true };
35897
- }
35898
- if (result.decision === "Deny") {
35899
- return {
35900
- allowed: false,
35901
- diagnostics: result.diagnostics ? JSON.stringify(result.diagnostics) : void 0
35902
- };
35903
- }
35904
- if (typeof result === "boolean") {
35905
- return { allowed: result };
35906
- }
35907
- return { allowed: true, diagnostics: `unknown result format: ${JSON.stringify(result)}` };
35891
+ if (!result) return { kind: "error", diagnostics: "null result from Cedar WASM" };
35892
+ if (result.type === "failure") {
35893
+ return { kind: "error", diagnostics: `cedar failure: ${JSON.stringify(result.errors ?? [])}` };
35894
+ }
35895
+ if (result.type === "success" && result.response) {
35896
+ const dec = result.response.decision;
35897
+ const reasons = result.response.diagnostics?.reason;
35898
+ if (dec === "allow" || dec === "Allow") return { kind: "allow", matchedPolicies: reasons };
35899
+ if (dec === "deny" || dec === "Deny") {
35900
+ return { kind: "deny", diagnostics: result.response.diagnostics ? JSON.stringify(result.response.diagnostics) : void 0, matchedPolicies: reasons };
35901
+ }
35902
+ }
35903
+ if (result.type === "allow" || result.decision === "Allow") return { kind: "allow" };
35904
+ if (result.type === "deny" || result.decision === "Deny") return { kind: "deny" };
35905
+ if (typeof result === "boolean") return result ? { kind: "allow" } : { kind: "deny" };
35906
+ return { kind: "error", diagnostics: `unknown result format: ${JSON.stringify(result)}` };
35907
+ }
35908
+ function extractPolicyErrors(result) {
35909
+ if (!result || typeof result !== "object") return [];
35910
+ const raw = result.errors ?? result.response?.diagnostics?.errors ?? result.diagnostics?.errors ?? [];
35911
+ if (!Array.isArray(raw)) return [];
35912
+ return raw.map((e) => typeof e === "string" ? e : e?.message ?? e?.error ?? JSON.stringify(e)).filter(Boolean);
35908
35913
  }
35909
35914
  async function isCedarAvailable() {
35910
35915
  return ensureCedarWasm();
35911
35916
  }
35917
+ function policySetFromSource(source, name = "inline") {
35918
+ const digest = (0, import_node_crypto2.createHash)("sha256").update(source).digest("hex").slice(0, 16);
35919
+ return { source, digest, fileCount: 1, files: [name] };
35920
+ }
35921
+ async function runEvaluatorSelfTest() {
35922
+ const wasmAvailable = await isCedarAvailable();
35923
+ const cases = [];
35924
+ const run = async (name, expected, policy, context) => {
35925
+ const d = await evaluateCedar(policy, { tool: "Bash", tier: "unknown", context }, void 0, { failClosed: true });
35926
+ const actual = d.allowed ? "ALLOW" : "DENY";
35927
+ cases.push({ name, expected, actual, pass: actual === expected, reason: d.reason });
35928
+ };
35929
+ if (!wasmAvailable) {
35930
+ await run("engine unavailable denies", "DENY", policySetFromSource("permit(principal, action, resource);"), {});
35931
+ return { wasmAvailable, passed: cases.every((c) => c.pass), cases };
35932
+ }
35933
+ const correct = policySetFromSource(
35934
+ 'forbid(principal, action, resource) when { ["rm", "dd", "mkfs"].contains(context.command) };\npermit(principal, action, resource);'
35935
+ );
35936
+ await run("forbid denies rm", "DENY", correct, { command: "rm" });
35937
+ await run("permit allows ls", "ALLOW", correct, { command: "ls" });
35938
+ const broken = policySetFromSource(
35939
+ 'forbid(principal, action, resource) when { context.command in ["rm", "dd"] };\npermit(principal, action, resource);'
35940
+ );
35941
+ await run("in-on-String forbid does not permit-all", "DENY", broken, { command: "rm" });
35942
+ return { wasmAvailable, passed: cases.every((c) => c.pass), cases };
35943
+ }
35912
35944
 
35913
35945
  // src/notifications.ts
35914
35946
  async function sendApprovalNotification(config, notification) {
@@ -42732,12 +42764,14 @@ function createSandboxServer() {
42732
42764
  parseLogFile,
42733
42765
  parseNotificationConfigFromEnv,
42734
42766
  parseRateLimit,
42767
+ policySetFromSource,
42735
42768
  queryExternalPDP,
42736
42769
  receiptToVP,
42737
42770
  receiptsToHFRows,
42738
42771
  redactFields,
42739
42772
  resolveCredential,
42740
42773
  revealField,
42774
+ runEvaluatorSelfTest,
42741
42775
  runInSandbox,
42742
42776
  sendApprovalNotification,
42743
42777
  signCommittedDecision,
package/dist/index.mjs CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  formatSimulation,
7
7
  parseLogFile,
8
8
  simulate
9
- } from "./chunk-S4ICHNSP.mjs";
9
+ } from "./chunk-ZBKJANP7.mjs";
10
10
  import {
11
11
  ProtectGateway,
12
12
  buildDecisionContext,
@@ -18,7 +18,7 @@ import {
18
18
  resolveCredential,
19
19
  sendApprovalNotification,
20
20
  validateCredentials
21
- } from "./chunk-PLKRTBDR.mjs";
21
+ } from "./chunk-OHUTUFTC.mjs";
22
22
  import {
23
23
  createSandboxServer
24
24
  } from "./chunk-J6L4XCTE.mjs";
@@ -33,7 +33,7 @@ import {
33
33
  forwardReceipt,
34
34
  getScopeBlindBridge,
35
35
  startHookServer
36
- } from "./chunk-3YCKR72H.mjs";
36
+ } from "./chunk-X63ELMU4.mjs";
37
37
  import {
38
38
  checkRateLimit,
39
39
  evaluateCedar,
@@ -45,8 +45,10 @@ 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
53
  ed25519,
52
54
  sha256
@@ -1995,12 +1997,14 @@ export {
1995
1997
  parseLogFile,
1996
1998
  parseNotificationConfigFromEnv,
1997
1999
  parseRateLimit,
2000
+ policySetFromSource,
1998
2001
  queryExternalPDP,
1999
2002
  receiptToVP,
2000
2003
  receiptsToHFRows,
2001
2004
  redactFields,
2002
2005
  resolveCredential,
2003
2006
  revealField,
2007
+ runEvaluatorSelfTest,
2004
2008
  runInSandbox,
2005
2009
  sendApprovalNotification,
2006
2010
  signCommittedDecision,
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "protect-mcp",
3
- "version": "0.6.2",
3
+ "version": "0.7.0",
4
4
  "mcpName": "com.scopeblind/protect-mcp",
5
- "description": "Cedar policy + signed receipts for AI agent decisions. Same primitive shipping in ScopeBlind cold-chain hardware. scopeblind.com/cold-chain",
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",