protect-mcp 0.5.2 → 0.5.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/README.md CHANGED
@@ -16,6 +16,14 @@ npx protect-mcp serve
16
16
 
17
17
  Open Claude Code in the same project. Every tool call is now intercepted, evaluated, and signed.
18
18
 
19
+ To also connect to the ScopeBlind dashboard in one step:
20
+
21
+ ```bash
22
+ npx protect-mcp quickstart --connect
23
+ ```
24
+
25
+ Creates a free ScopeBlind dashboard and configures receipt upload automatically. No signup required. Free up to 20,000 receipts/month.
26
+
19
27
  ### What `init-hooks` creates
20
28
 
21
29
  | File | Purpose |
@@ -209,6 +217,7 @@ Commands:
209
217
  serve Start HTTP hook server for Claude Code (port 9377)
210
218
  init-hooks Generate Claude Code hook config + skill + sample Cedar policy
211
219
  quickstart Zero-config onboarding: init + demo + show receipts
220
+ connect Link to ScopeBlind dashboard (creates sandbox if needed)
212
221
  init Generate Ed25519 keypair + config template
213
222
  demo Start a demo server wrapped with protect-mcp
214
223
  doctor Check your setup: keys, policies, verifier, connectivity
@@ -246,13 +255,37 @@ npx protect-mcp bundle --output audit.json
246
255
 
247
256
  Self-contained offline-verifiable bundle with receipts + signing keys. Verify with `npx @veritasacta/verify`.
248
257
 
258
+ ## Dashboard
259
+
260
+ protect-mcp works fully offline. Optionally connect to the ScopeBlind dashboard for:
261
+
262
+ - Real-time receipt visualization
263
+ - Abuse alerts and anomaly detection
264
+ - Compliance export and audit bundles
265
+ - Usage analytics
266
+
267
+ Free tier: 20,000 receipts/month. No credit card required.
268
+
269
+ [scopeblind.com/pricing](https://scopeblind.com/pricing)
270
+
249
271
  ## Standards & IP
250
272
 
251
- - **IETF Internet-Draft**: [draft-farley-acta-signed-receipts-00](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/) — Signed Decision Receipts for Machine-to-Machine Access Control
273
+ - **IETF Internet-Draft**: [draft-farley-acta-signed-receipts-01](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/) — Signed Decision Receipts for Machine-to-Machine Access Control
252
274
  - **Patent Status**: 4 Australian provisional patents pending (2025-2026) covering decision receipts with configurable disclosure, tool-calling gateway, agent manifests, and portable identity
253
- - **Verification**: MIT-licensed — `npx @veritasacta/verify --self-test`
275
+ - **Verification**: Apache-2.0 — `npx @veritasacta/verify --self-test`
254
276
  - **Microsoft AGT Integration**: [PR #667](https://github.com/microsoft/agent-governance-toolkit/pull/667) — Cedar policy bridge for Agent Governance Toolkit
255
277
 
278
+ ## What's New in v0.5.3
279
+
280
+ - `quickstart --connect`: Auto-create dashboard sandbox and configure receipt upload
281
+ - `connect` subcommand: Link an existing setup to the ScopeBlind dashboard
282
+ - Anonymous install telemetry (opt-out: `PROTECT_MCP_TELEMETRY=off`)
283
+ - Improved Cedar WASM detection
284
+
285
+ ## Examples
286
+
287
+ See complete working examples at [github.com/ScopeBlind/examples](https://github.com/ScopeBlind/examples).
288
+
256
289
  ## License
257
290
 
258
291
  MIT — free to use, modify, distribute, and build upon without restriction.
@@ -7,7 +7,7 @@ import {
7
7
  parseRateLimit,
8
8
  signDecision,
9
9
  startStatusServer
10
- } from "./chunk-N4F76LTC.mjs";
10
+ } from "./chunk-YTBC72JJ.mjs";
11
11
 
12
12
  // src/evidence-store.ts
13
13
  import { readFileSync, writeFileSync, existsSync } from "fs";
@@ -11,7 +11,7 @@ import {
11
11
  loadPolicy,
12
12
  parseRateLimit,
13
13
  signDecision
14
- } from "./chunk-N4F76LTC.mjs";
14
+ } from "./chunk-YTBC72JJ.mjs";
15
15
 
16
16
  // src/hook-server.ts
17
17
  import { createServer } from "http";
@@ -700,7 +700,7 @@ async function startHookServer(options = {}) {
700
700
  `);
701
701
  process.stderr.write(`[PROTECT_MCP] \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
702
702
  `);
703
- process.stderr.write(`[PROTECT_MCP] \u2502 protect-mcp Hook Server v0.5.0 \u2502
703
+ process.stderr.write(`[PROTECT_MCP] \u2502 protect-mcp Hook Server v0.5.3 \u2502
704
704
  `);
705
705
  process.stderr.write(`[PROTECT_MCP] \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
706
706
  `);
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  meetsMinTier
3
- } from "./chunk-IWDR5WU3.mjs";
3
+ } from "./chunk-BYYWYSHM.mjs";
4
4
  import {
5
5
  checkRateLimit,
6
6
  getToolPolicy,
7
7
  parseRateLimit
8
- } from "./chunk-N4F76LTC.mjs";
8
+ } from "./chunk-YTBC72JJ.mjs";
9
9
 
10
10
  // src/simulate.ts
11
11
  import { readFileSync } from "fs";
@@ -35049,7 +35049,7 @@ function handleRequest(request) {
35049
35049
  id: request.id,
35050
35050
  result: {
35051
35051
  protocolVersion: "2024-11-05",
35052
- serverInfo: { name: "protect-mcp-demo", version: "0.2.0" },
35052
+ serverInfo: { name: "protect-mcp-demo", version: "0.5.3" },
35053
35053
  capabilities: { tools: {} }
35054
35054
  }
35055
35055
  });
@@ -272,6 +272,8 @@ context: inline
272
272
 
273
273
  # ScopeBlind Receipt Verification
274
274
 
275
+ Every AI agent tool call gets a cryptographic receipt. Verify offline. No vendor trust required.
276
+
275
277
  When the user asks to verify receipts or check the audit trail:
276
278
 
277
279
  1. **Check for receipt files:**
@@ -130,7 +130,14 @@ function signDecision(entry) {
130
130
  scope: entry.request_id,
131
131
  // request scope
132
132
  mode: entry.mode,
133
- request_id: entry.request_id
133
+ request_id: entry.request_id,
134
+ // Spec version: ties every receipt to the IETF standard
135
+ spec: "draft-farley-acta-signed-receipts-01",
136
+ // Issuer certification: distinguishes VOPRF-backed receipts from self-signed ones
137
+ // - scopeblind:verified = issued via ScopeBlind VOPRF backend (paid tier)
138
+ // - self-signed = signed with local Ed25519 key (free tier, protect-mcp default)
139
+ // - uncertified = unsigned receipt (shadow mode, no signing configured)
140
+ issuer_certification: signerState ? "self-signed" : "uncertified"
134
141
  };
135
142
  if (entry.tier) payload.tier = entry.tier;
136
143
  if (entry.credential_ref) payload.credential_ref = entry.credential_ref;
@@ -138,6 +145,12 @@ function signDecision(entry) {
138
145
  payload.rate_limit_remaining = entry.rate_limit_remaining;
139
146
  }
140
147
  if (entry.policy_engine) payload.policy_engine = entry.policy_engine;
148
+ if (entry.hook_event) payload.hook_event = entry.hook_event;
149
+ if (entry.sandbox_state) payload.sandbox_state = entry.sandbox_state;
150
+ if (entry.timing) payload.timing = entry.timing;
151
+ if (entry.swarm) payload.swarm = entry.swarm;
152
+ if (entry.payload_digest) payload.payload_digest = entry.payload_digest;
153
+ if (entry.deny_iteration) payload.deny_iteration = entry.deny_iteration;
141
154
  const result = artifactsModule.createSignedArtifact(
142
155
  artifactType,
143
156
  payload,
package/dist/cli.js CHANGED
@@ -411,7 +411,14 @@ function signDecision(entry) {
411
411
  scope: entry.request_id,
412
412
  // request scope
413
413
  mode: entry.mode,
414
- request_id: entry.request_id
414
+ request_id: entry.request_id,
415
+ // Spec version: ties every receipt to the IETF standard
416
+ spec: "draft-farley-acta-signed-receipts-01",
417
+ // Issuer certification: distinguishes VOPRF-backed receipts from self-signed ones
418
+ // - scopeblind:verified = issued via ScopeBlind VOPRF backend (paid tier)
419
+ // - self-signed = signed with local Ed25519 key (free tier, protect-mcp default)
420
+ // - uncertified = unsigned receipt (shadow mode, no signing configured)
421
+ issuer_certification: signerState ? "self-signed" : "uncertified"
415
422
  };
416
423
  if (entry.tier) payload.tier = entry.tier;
417
424
  if (entry.credential_ref) payload.credential_ref = entry.credential_ref;
@@ -419,6 +426,12 @@ function signDecision(entry) {
419
426
  payload.rate_limit_remaining = entry.rate_limit_remaining;
420
427
  }
421
428
  if (entry.policy_engine) payload.policy_engine = entry.policy_engine;
429
+ if (entry.hook_event) payload.hook_event = entry.hook_event;
430
+ if (entry.sandbox_state) payload.sandbox_state = entry.sandbox_state;
431
+ if (entry.timing) payload.timing = entry.timing;
432
+ if (entry.swarm) payload.swarm = entry.swarm;
433
+ if (entry.payload_digest) payload.payload_digest = entry.payload_digest;
434
+ if (entry.deny_iteration) payload.deny_iteration = entry.deny_iteration;
422
435
  const result = artifactsModule.createSignedArtifact(
423
436
  artifactType,
424
437
  payload,
@@ -4544,6 +4557,8 @@ context: inline
4544
4557
 
4545
4558
  # ScopeBlind Receipt Verification
4546
4559
 
4560
+ Every AI agent tool call gets a cryptographic receipt. Verify offline. No vendor trust required.
4561
+
4547
4562
  When the user asks to verify receipts or check the audit trail:
4548
4563
 
4549
4564
  1. **Check for receipt files:**
@@ -5436,7 +5451,7 @@ async function startHookServer(options = {}) {
5436
5451
  `);
5437
5452
  process.stderr.write(`[PROTECT_MCP] \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
5438
5453
  `);
5439
- process.stderr.write(`[PROTECT_MCP] \u2502 protect-mcp Hook Server v0.5.0 \u2502
5454
+ process.stderr.write(`[PROTECT_MCP] \u2502 protect-mcp Hook Server v0.5.3 \u2502
5440
5455
  `);
5441
5456
  process.stderr.write(`[PROTECT_MCP] \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
5442
5457
  `);
@@ -6051,6 +6066,7 @@ function formatSimulation(summary) {
6051
6066
 
6052
6067
  // src/cli.ts
6053
6068
  init_cedar_evaluator();
6069
+ var import_meta = {};
6054
6070
  function printHelp() {
6055
6071
  process.stderr.write(`
6056
6072
  protect-mcp \u2014 Enterprise security gateway for MCP servers & Claude Code hooks
@@ -6059,7 +6075,8 @@ Usage:
6059
6075
  protect-mcp [options] -- <command> [args...]
6060
6076
  protect-mcp serve [--port <port>] [--enforce] [--policy <path>] [--cedar <dir>]
6061
6077
  protect-mcp init-hooks [--dir <path>] [--port <port>]
6062
- protect-mcp quickstart
6078
+ protect-mcp quickstart [--connect]
6079
+ protect-mcp connect
6063
6080
  protect-mcp init [--dir <path>]
6064
6081
  protect-mcp demo
6065
6082
  protect-mcp trace <receipt_id> [--endpoint <url>] [--depth <n>]
@@ -6084,6 +6101,7 @@ Commands:
6084
6101
  serve Start HTTP hook server for Claude Code integration (port 9377)
6085
6102
  init-hooks Generate Claude Code hook config + skill + sample Cedar policy
6086
6103
  quickstart Zero-config onboarding: init + demo + show receipts in one command
6104
+ connect Create a ScopeBlind sandbox dashboard and configure receipt upload
6087
6105
  init Generate config template, Ed25519 keypair, and sample policy
6088
6106
  demo Start a demo server wrapped with protect-mcp (see receipts instantly)
6089
6107
  doctor Check your setup: keys, policies, verifier, API connectivity
@@ -6098,6 +6116,8 @@ Examples:
6098
6116
  protect-mcp serve --enforce --cedar ./cedar # Enforce Cedar policies
6099
6117
  protect-mcp init-hooks # One-command Claude Code setup
6100
6118
  protect-mcp quickstart
6119
+ protect-mcp quickstart --connect # Quickstart + create dashboard
6120
+ protect-mcp connect # Connect existing setup to dashboard
6101
6121
  protect-mcp -- node my-server.js
6102
6122
  protect-mcp init
6103
6123
  protect-mcp demo
@@ -6679,7 +6699,83 @@ ${bold("protect-mcp bundle")}
6679
6699
 
6680
6700
  `);
6681
6701
  }
6682
- async function handleQuickstart() {
6702
+ async function createSandbox() {
6703
+ const { mkdirSync, writeFileSync: writeFileSync2, existsSync: existsSync7, readFileSync: readFileSync9 } = await import("fs");
6704
+ const { join: join6 } = await import("path");
6705
+ const { homedir } = await import("os");
6706
+ let response;
6707
+ try {
6708
+ response = await fetch("https://api.scopeblind.com/sandbox/create", { method: "POST" });
6709
+ } catch {
6710
+ process.stderr.write(yellow(" \u26A0 Could not create dashboard (offline or server unavailable).\n"));
6711
+ process.stderr.write(` Run 'npx protect-mcp connect' later to set up the dashboard.
6712
+
6713
+ `);
6714
+ return null;
6715
+ }
6716
+ if (!response.ok) {
6717
+ process.stderr.write(yellow(" \u26A0 Could not create dashboard (offline or server unavailable).\n"));
6718
+ process.stderr.write(` Run 'npx protect-mcp connect' later to set up the dashboard.
6719
+
6720
+ `);
6721
+ return null;
6722
+ }
6723
+ let data;
6724
+ try {
6725
+ data = await response.json();
6726
+ } catch {
6727
+ process.stderr.write(yellow(" \u26A0 Could not create dashboard (unexpected response).\n"));
6728
+ process.stderr.write(` Run 'npx protect-mcp connect' later to set up the dashboard.
6729
+
6730
+ `);
6731
+ return null;
6732
+ }
6733
+ const dashboardUrl = `https://scopeblind.com/t/${data.slug}`;
6734
+ const configDir = join6(homedir(), ".protect-mcp");
6735
+ if (!existsSync7(configDir)) {
6736
+ mkdirSync(configDir, { recursive: true });
6737
+ }
6738
+ const configPath = join6(configDir, "config.json");
6739
+ let existing = {};
6740
+ if (existsSync7(configPath)) {
6741
+ try {
6742
+ existing = JSON.parse(readFileSync9(configPath, "utf-8"));
6743
+ } catch {
6744
+ }
6745
+ }
6746
+ writeFileSync2(configPath, JSON.stringify({
6747
+ ...existing,
6748
+ sandbox_slug: data.slug,
6749
+ dashboard_url: dashboardUrl
6750
+ }, null, 2) + "\n");
6751
+ return dashboardUrl;
6752
+ }
6753
+ async function handleConnect() {
6754
+ process.stderr.write(`
6755
+ ${bold("protect-mcp connect")}
6756
+ `);
6757
+ process.stderr.write(`${"\u2500".repeat(50)}
6758
+
6759
+ `);
6760
+ process.stderr.write(` Creating ScopeBlind sandbox dashboard...
6761
+
6762
+ `);
6763
+ const dashboardUrl = await createSandbox();
6764
+ if (dashboardUrl) {
6765
+ process.stderr.write(green(` \u2713 Dashboard created: ${dashboardUrl}
6766
+ `));
6767
+ process.stderr.write(` Receipts will be uploaded automatically.
6768
+ `);
6769
+ process.stderr.write(dim(` Free tier: 20,000 receipts/month \u2014 no credit card required.
6770
+ `));
6771
+ process.stderr.write(`
6772
+ ${"\u2500".repeat(50)}
6773
+
6774
+ `);
6775
+ }
6776
+ }
6777
+ async function handleQuickstart(argv) {
6778
+ const connectFlag = argv.includes("--connect");
6683
6779
  const { mkdtempSync, writeFileSync: writeFileSync2, existsSync: existsSync7, mkdirSync, readFileSync: readFileSync9 } = await import("fs");
6684
6780
  const { join: join6 } = await import("path");
6685
6781
  const { tmpdir } = await import("os");
@@ -6699,9 +6795,13 @@ ${bold("protect-mcp quickstart")}
6699
6795
  process.stdout.write(` 3. Start a demo MCP server with protect-mcp wrapping it
6700
6796
  `);
6701
6797
  process.stdout.write(` 4. Log signed receipts for every tool call
6702
-
6703
6798
  `);
6704
- process.stdout.write(` Working dir: ${dir}
6799
+ if (connectFlag) {
6800
+ process.stdout.write(` 5. Create a ScopeBlind dashboard for receipt viewing
6801
+ `);
6802
+ }
6803
+ process.stdout.write(`
6804
+ Working dir: ${dir}
6705
6805
 
6706
6806
  `);
6707
6807
  const keysDir = join6(dir, "keys");
@@ -6752,6 +6852,24 @@ ${bold("protect-mcp quickstart")}
6752
6852
  process.stdout.write(` \u2713 Signing enabled (Ed25519)
6753
6853
 
6754
6854
  `);
6855
+ if (connectFlag) {
6856
+ process.stdout.write(`${bold("Connecting to ScopeBlind dashboard...")}
6857
+
6858
+ `);
6859
+ const dashboardUrl = await createSandbox();
6860
+ if (dashboardUrl) {
6861
+ const updatedConfig = { ...config, dashboard_url: dashboardUrl };
6862
+ writeFileSync2(configPath, JSON.stringify(updatedConfig, null, 2) + "\n");
6863
+ process.stdout.write(green(` \u2713 Dashboard created: ${dashboardUrl}
6864
+ `));
6865
+ process.stdout.write(` Receipts will be uploaded automatically.
6866
+ `);
6867
+ process.stdout.write(dim(` Free tier: 20,000 receipts/month \u2014 no credit card required.
6868
+ `));
6869
+ process.stdout.write(`
6870
+ `);
6871
+ }
6872
+ }
6755
6873
  process.stdout.write(`${bold("Starting demo server...")}
6756
6874
 
6757
6875
  `);
@@ -7153,7 +7271,55 @@ ${bold("protect-mcp init-hooks")}
7153
7271
 
7154
7272
  `);
7155
7273
  }
7274
+ async function sendInstallTelemetry() {
7275
+ try {
7276
+ const { existsSync: existsSync7, mkdirSync, writeFileSync: writeFileSync2, readFileSync: readFileSync9 } = await import("fs");
7277
+ const { join: join6, dirname } = await import("path");
7278
+ const { homedir } = await import("os");
7279
+ const { fileURLToPath } = await import("url");
7280
+ const markerDir = join6(homedir(), ".protect-mcp");
7281
+ const markerFile = join6(markerDir, ".telemetry-sent");
7282
+ if (existsSync7(markerFile) || process.env.PROTECT_MCP_TELEMETRY === "off") {
7283
+ return;
7284
+ }
7285
+ let version = "unknown";
7286
+ try {
7287
+ const thisDir = dirname(fileURLToPath(import_meta.url));
7288
+ const pkgPath = join6(thisDir, "..", "package.json");
7289
+ if (existsSync7(pkgPath)) {
7290
+ version = JSON.parse(readFileSync9(pkgPath, "utf-8")).version;
7291
+ }
7292
+ } catch {
7293
+ }
7294
+ const controller = new AbortController();
7295
+ const timeout = setTimeout(() => controller.abort(), 3e3);
7296
+ fetch("https://api.scopeblind.com/telemetry/install", {
7297
+ method: "POST",
7298
+ headers: { "Content-Type": "application/json" },
7299
+ body: JSON.stringify({
7300
+ package: "protect-mcp",
7301
+ version,
7302
+ os: process.platform,
7303
+ arch: process.arch,
7304
+ node: process.version,
7305
+ ts: Date.now()
7306
+ }),
7307
+ signal: controller.signal
7308
+ }).catch(() => {
7309
+ }).finally(() => clearTimeout(timeout));
7310
+ if (!existsSync7(markerDir)) {
7311
+ mkdirSync(markerDir, { recursive: true });
7312
+ }
7313
+ writeFileSync2(markerFile, String(Date.now()), "utf-8");
7314
+ process.stderr.write(
7315
+ "[protect-mcp] Anonymous install telemetry sent (disable: PROTECT_MCP_TELEMETRY=off)\n"
7316
+ );
7317
+ } catch {
7318
+ }
7319
+ }
7156
7320
  async function main() {
7321
+ sendInstallTelemetry().catch(() => {
7322
+ });
7157
7323
  const args = process.argv.slice(2);
7158
7324
  if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
7159
7325
  printHelp();
@@ -7181,9 +7347,13 @@ async function main() {
7181
7347
  process.exit(0);
7182
7348
  }
7183
7349
  if (args[0] === "quickstart") {
7184
- await handleQuickstart();
7350
+ await handleQuickstart(args.slice(1));
7185
7351
  return;
7186
7352
  }
7353
+ if (args[0] === "connect") {
7354
+ await handleConnect();
7355
+ process.exit(0);
7356
+ }
7187
7357
  if (args[0] === "init") {
7188
7358
  await handleInit(args.slice(1));
7189
7359
  process.exit(0);
package/dist/cli.mjs CHANGED
@@ -3,17 +3,17 @@ import {
3
3
  formatSimulation,
4
4
  parseLogFile,
5
5
  simulate
6
- } from "./chunk-W4U5VNEC.mjs";
6
+ } from "./chunk-GQWJCHQV.mjs";
7
7
  import {
8
8
  ProtectGateway,
9
9
  validateCredentials
10
- } from "./chunk-IWDR5WU3.mjs";
10
+ } from "./chunk-BYYWYSHM.mjs";
11
11
  import {
12
12
  initSigning,
13
13
  isCedarAvailable,
14
14
  loadCedarPolicies,
15
15
  loadPolicy
16
- } from "./chunk-N4F76LTC.mjs";
16
+ } from "./chunk-YTBC72JJ.mjs";
17
17
  import "./chunk-PQJP2ZCI.mjs";
18
18
 
19
19
  // src/cli.ts
@@ -25,7 +25,8 @@ Usage:
25
25
  protect-mcp [options] -- <command> [args...]
26
26
  protect-mcp serve [--port <port>] [--enforce] [--policy <path>] [--cedar <dir>]
27
27
  protect-mcp init-hooks [--dir <path>] [--port <port>]
28
- protect-mcp quickstart
28
+ protect-mcp quickstart [--connect]
29
+ protect-mcp connect
29
30
  protect-mcp init [--dir <path>]
30
31
  protect-mcp demo
31
32
  protect-mcp trace <receipt_id> [--endpoint <url>] [--depth <n>]
@@ -50,6 +51,7 @@ Commands:
50
51
  serve Start HTTP hook server for Claude Code integration (port 9377)
51
52
  init-hooks Generate Claude Code hook config + skill + sample Cedar policy
52
53
  quickstart Zero-config onboarding: init + demo + show receipts in one command
54
+ connect Create a ScopeBlind sandbox dashboard and configure receipt upload
53
55
  init Generate config template, Ed25519 keypair, and sample policy
54
56
  demo Start a demo server wrapped with protect-mcp (see receipts instantly)
55
57
  doctor Check your setup: keys, policies, verifier, API connectivity
@@ -64,6 +66,8 @@ Examples:
64
66
  protect-mcp serve --enforce --cedar ./cedar # Enforce Cedar policies
65
67
  protect-mcp init-hooks # One-command Claude Code setup
66
68
  protect-mcp quickstart
69
+ protect-mcp quickstart --connect # Quickstart + create dashboard
70
+ protect-mcp connect # Connect existing setup to dashboard
67
71
  protect-mcp -- node my-server.js
68
72
  protect-mcp init
69
73
  protect-mcp demo
@@ -645,7 +649,83 @@ ${bold("protect-mcp bundle")}
645
649
 
646
650
  `);
647
651
  }
648
- async function handleQuickstart() {
652
+ async function createSandbox() {
653
+ const { mkdirSync, writeFileSync, existsSync, readFileSync } = await import("fs");
654
+ const { join } = await import("path");
655
+ const { homedir } = await import("os");
656
+ let response;
657
+ try {
658
+ response = await fetch("https://api.scopeblind.com/sandbox/create", { method: "POST" });
659
+ } catch {
660
+ process.stderr.write(yellow(" \u26A0 Could not create dashboard (offline or server unavailable).\n"));
661
+ process.stderr.write(` Run 'npx protect-mcp connect' later to set up the dashboard.
662
+
663
+ `);
664
+ return null;
665
+ }
666
+ if (!response.ok) {
667
+ process.stderr.write(yellow(" \u26A0 Could not create dashboard (offline or server unavailable).\n"));
668
+ process.stderr.write(` Run 'npx protect-mcp connect' later to set up the dashboard.
669
+
670
+ `);
671
+ return null;
672
+ }
673
+ let data;
674
+ try {
675
+ data = await response.json();
676
+ } catch {
677
+ process.stderr.write(yellow(" \u26A0 Could not create dashboard (unexpected response).\n"));
678
+ process.stderr.write(` Run 'npx protect-mcp connect' later to set up the dashboard.
679
+
680
+ `);
681
+ return null;
682
+ }
683
+ const dashboardUrl = `https://scopeblind.com/t/${data.slug}`;
684
+ const configDir = join(homedir(), ".protect-mcp");
685
+ if (!existsSync(configDir)) {
686
+ mkdirSync(configDir, { recursive: true });
687
+ }
688
+ const configPath = join(configDir, "config.json");
689
+ let existing = {};
690
+ if (existsSync(configPath)) {
691
+ try {
692
+ existing = JSON.parse(readFileSync(configPath, "utf-8"));
693
+ } catch {
694
+ }
695
+ }
696
+ writeFileSync(configPath, JSON.stringify({
697
+ ...existing,
698
+ sandbox_slug: data.slug,
699
+ dashboard_url: dashboardUrl
700
+ }, null, 2) + "\n");
701
+ return dashboardUrl;
702
+ }
703
+ async function handleConnect() {
704
+ process.stderr.write(`
705
+ ${bold("protect-mcp connect")}
706
+ `);
707
+ process.stderr.write(`${"\u2500".repeat(50)}
708
+
709
+ `);
710
+ process.stderr.write(` Creating ScopeBlind sandbox dashboard...
711
+
712
+ `);
713
+ const dashboardUrl = await createSandbox();
714
+ if (dashboardUrl) {
715
+ process.stderr.write(green(` \u2713 Dashboard created: ${dashboardUrl}
716
+ `));
717
+ process.stderr.write(` Receipts will be uploaded automatically.
718
+ `);
719
+ process.stderr.write(dim(` Free tier: 20,000 receipts/month \u2014 no credit card required.
720
+ `));
721
+ process.stderr.write(`
722
+ ${"\u2500".repeat(50)}
723
+
724
+ `);
725
+ }
726
+ }
727
+ async function handleQuickstart(argv) {
728
+ const connectFlag = argv.includes("--connect");
649
729
  const { mkdtempSync, writeFileSync, existsSync, mkdirSync, readFileSync } = await import("fs");
650
730
  const { join } = await import("path");
651
731
  const { tmpdir } = await import("os");
@@ -665,9 +745,13 @@ ${bold("protect-mcp quickstart")}
665
745
  process.stdout.write(` 3. Start a demo MCP server with protect-mcp wrapping it
666
746
  `);
667
747
  process.stdout.write(` 4. Log signed receipts for every tool call
668
-
669
748
  `);
670
- process.stdout.write(` Working dir: ${dir}
749
+ if (connectFlag) {
750
+ process.stdout.write(` 5. Create a ScopeBlind dashboard for receipt viewing
751
+ `);
752
+ }
753
+ process.stdout.write(`
754
+ Working dir: ${dir}
671
755
 
672
756
  `);
673
757
  const keysDir = join(dir, "keys");
@@ -718,6 +802,24 @@ ${bold("protect-mcp quickstart")}
718
802
  process.stdout.write(` \u2713 Signing enabled (Ed25519)
719
803
 
720
804
  `);
805
+ if (connectFlag) {
806
+ process.stdout.write(`${bold("Connecting to ScopeBlind dashboard...")}
807
+
808
+ `);
809
+ const dashboardUrl = await createSandbox();
810
+ if (dashboardUrl) {
811
+ const updatedConfig = { ...config, dashboard_url: dashboardUrl };
812
+ writeFileSync(configPath, JSON.stringify(updatedConfig, null, 2) + "\n");
813
+ process.stdout.write(green(` \u2713 Dashboard created: ${dashboardUrl}
814
+ `));
815
+ process.stdout.write(` Receipts will be uploaded automatically.
816
+ `);
817
+ process.stdout.write(dim(` Free tier: 20,000 receipts/month \u2014 no credit card required.
818
+ `));
819
+ process.stdout.write(`
820
+ `);
821
+ }
822
+ }
721
823
  process.stdout.write(`${bold("Starting demo server...")}
722
824
 
723
825
  `);
@@ -1119,7 +1221,55 @@ ${bold("protect-mcp init-hooks")}
1119
1221
 
1120
1222
  `);
1121
1223
  }
1224
+ async function sendInstallTelemetry() {
1225
+ try {
1226
+ const { existsSync, mkdirSync, writeFileSync, readFileSync } = await import("fs");
1227
+ const { join, dirname } = await import("path");
1228
+ const { homedir } = await import("os");
1229
+ const { fileURLToPath } = await import("url");
1230
+ const markerDir = join(homedir(), ".protect-mcp");
1231
+ const markerFile = join(markerDir, ".telemetry-sent");
1232
+ if (existsSync(markerFile) || process.env.PROTECT_MCP_TELEMETRY === "off") {
1233
+ return;
1234
+ }
1235
+ let version = "unknown";
1236
+ try {
1237
+ const thisDir = dirname(fileURLToPath(import.meta.url));
1238
+ const pkgPath = join(thisDir, "..", "package.json");
1239
+ if (existsSync(pkgPath)) {
1240
+ version = JSON.parse(readFileSync(pkgPath, "utf-8")).version;
1241
+ }
1242
+ } catch {
1243
+ }
1244
+ const controller = new AbortController();
1245
+ const timeout = setTimeout(() => controller.abort(), 3e3);
1246
+ fetch("https://api.scopeblind.com/telemetry/install", {
1247
+ method: "POST",
1248
+ headers: { "Content-Type": "application/json" },
1249
+ body: JSON.stringify({
1250
+ package: "protect-mcp",
1251
+ version,
1252
+ os: process.platform,
1253
+ arch: process.arch,
1254
+ node: process.version,
1255
+ ts: Date.now()
1256
+ }),
1257
+ signal: controller.signal
1258
+ }).catch(() => {
1259
+ }).finally(() => clearTimeout(timeout));
1260
+ if (!existsSync(markerDir)) {
1261
+ mkdirSync(markerDir, { recursive: true });
1262
+ }
1263
+ writeFileSync(markerFile, String(Date.now()), "utf-8");
1264
+ process.stderr.write(
1265
+ "[protect-mcp] Anonymous install telemetry sent (disable: PROTECT_MCP_TELEMETRY=off)\n"
1266
+ );
1267
+ } catch {
1268
+ }
1269
+ }
1122
1270
  async function main() {
1271
+ sendInstallTelemetry().catch(() => {
1272
+ });
1123
1273
  const args = process.argv.slice(2);
1124
1274
  if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
1125
1275
  printHelp();
@@ -1147,9 +1297,13 @@ async function main() {
1147
1297
  process.exit(0);
1148
1298
  }
1149
1299
  if (args[0] === "quickstart") {
1150
- await handleQuickstart();
1300
+ await handleQuickstart(args.slice(1));
1151
1301
  return;
1152
1302
  }
1303
+ if (args[0] === "connect") {
1304
+ await handleConnect();
1305
+ process.exit(0);
1306
+ }
1153
1307
  if (args[0] === "init") {
1154
1308
  await handleInit(args.slice(1));
1155
1309
  process.exit(0);
@@ -1282,7 +1436,7 @@ async function main() {
1282
1436
  if (useHttp) {
1283
1437
  const portIdx = args.indexOf("--port");
1284
1438
  const httpPort = portIdx >= 0 && args[portIdx + 1] ? parseInt(args[portIdx + 1]) : 3e3;
1285
- const { startHttpTransport } = await import("./http-transport-PWCK7JHZ.mjs");
1439
+ const { startHttpTransport } = await import("./http-transport-LNBENGXD.mjs");
1286
1440
  startHttpTransport({ port: httpPort, config, serverCommand: childCommand });
1287
1441
  return;
1288
1442
  }
@@ -35073,7 +35073,7 @@ function handleRequest(request) {
35073
35073
  id: request.id,
35074
35074
  result: {
35075
35075
  protocolVersion: "2024-11-05",
35076
- serverInfo: { name: "protect-mcp-demo", version: "0.2.0" },
35076
+ serverInfo: { name: "protect-mcp-demo", version: "0.5.3" },
35077
35077
  capabilities: { tools: {} }
35078
35078
  }
35079
35079
  });
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createSandboxServer
4
- } from "./chunk-SU2FZH7U.mjs";
4
+ } from "./chunk-J6L4XCTE.mjs";
5
5
  import "./chunk-PQJP2ZCI.mjs";
6
6
  export {
7
7
  createSandboxServer
@@ -299,6 +299,8 @@ context: inline
299
299
 
300
300
  # ScopeBlind Receipt Verification
301
301
 
302
+ Every AI agent tool call gets a cryptographic receipt. Verify offline. No vendor trust required.
303
+
302
304
  When the user asks to verify receipts or check the audit trail:
303
305
 
304
306
  1. **Check for receipt files:**
@@ -3,7 +3,7 @@ import {
3
3
  generateHookSettings,
4
4
  generateSampleCedarPolicy,
5
5
  generateVerifyReceiptSkill
6
- } from "./chunk-UEHLYOJY.mjs";
6
+ } from "./chunk-NMZPXXL3.mjs";
7
7
  import "./chunk-PQJP2ZCI.mjs";
8
8
  export {
9
9
  BUILTIN_PATTERNS,
@@ -257,7 +257,14 @@ function signDecision(entry) {
257
257
  scope: entry.request_id,
258
258
  // request scope
259
259
  mode: entry.mode,
260
- request_id: entry.request_id
260
+ request_id: entry.request_id,
261
+ // Spec version: ties every receipt to the IETF standard
262
+ spec: "draft-farley-acta-signed-receipts-01",
263
+ // Issuer certification: distinguishes VOPRF-backed receipts from self-signed ones
264
+ // - scopeblind:verified = issued via ScopeBlind VOPRF backend (paid tier)
265
+ // - self-signed = signed with local Ed25519 key (free tier, protect-mcp default)
266
+ // - uncertified = unsigned receipt (shadow mode, no signing configured)
267
+ issuer_certification: signerState ? "self-signed" : "uncertified"
261
268
  };
262
269
  if (entry.tier) payload.tier = entry.tier;
263
270
  if (entry.credential_ref) payload.credential_ref = entry.credential_ref;
@@ -265,6 +272,12 @@ function signDecision(entry) {
265
272
  payload.rate_limit_remaining = entry.rate_limit_remaining;
266
273
  }
267
274
  if (entry.policy_engine) payload.policy_engine = entry.policy_engine;
275
+ if (entry.hook_event) payload.hook_event = entry.hook_event;
276
+ if (entry.sandbox_state) payload.sandbox_state = entry.sandbox_state;
277
+ if (entry.timing) payload.timing = entry.timing;
278
+ if (entry.swarm) payload.swarm = entry.swarm;
279
+ if (entry.payload_digest) payload.payload_digest = entry.payload_digest;
280
+ if (entry.deny_iteration) payload.deny_iteration = entry.deny_iteration;
268
281
  const result = artifactsModule.createSignedArtifact(
269
282
  artifactType,
270
283
  payload,
@@ -1088,7 +1101,7 @@ async function startHookServer(options = {}) {
1088
1101
  `);
1089
1102
  process.stderr.write(`[PROTECT_MCP] \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
1090
1103
  `);
1091
- process.stderr.write(`[PROTECT_MCP] \u2502 protect-mcp Hook Server v0.5.0 \u2502
1104
+ process.stderr.write(`[PROTECT_MCP] \u2502 protect-mcp Hook Server v0.5.3 \u2502
1092
1105
  `);
1093
1106
  process.stderr.write(`[PROTECT_MCP] \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
1094
1107
  `);
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  startHookServer
3
- } from "./chunk-Q4KOQUKV.mjs";
4
- import "./chunk-N4F76LTC.mjs";
3
+ } from "./chunk-FAELTNS7.mjs";
4
+ import "./chunk-YTBC72JJ.mjs";
5
5
  import "./chunk-PQJP2ZCI.mjs";
6
6
  export {
7
7
  startHookServer
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ProtectGateway
3
- } from "./chunk-IWDR5WU3.mjs";
4
- import "./chunk-N4F76LTC.mjs";
3
+ } from "./chunk-BYYWYSHM.mjs";
4
+ import "./chunk-YTBC72JJ.mjs";
5
5
  import "./chunk-PQJP2ZCI.mjs";
6
6
 
7
7
  // src/http-transport.ts
package/dist/index.d.mts CHANGED
@@ -167,6 +167,13 @@ interface DecisionLog {
167
167
  plan_receipt_id?: string;
168
168
  /** Hook event that triggered this log entry */
169
169
  hook_event?: HookEventName;
170
+ /** IETF specification version — ties every receipt to the standard */
171
+ spec?: string;
172
+ /** Issuer certification level:
173
+ * - "scopeblind:verified" = VOPRF-backed issuance (paid tier)
174
+ * - "self-signed" = local Ed25519 key (free tier, protect-mcp default)
175
+ * - "uncertified" = unsigned receipt (shadow mode) */
176
+ issuer_certification?: 'scopeblind:verified' | 'self-signed' | 'uncertified';
170
177
  }
171
178
  interface SwarmContext {
172
179
  /** Team name from CLAUDE_CODE_TEAM_NAME env var */
@@ -2282,7 +2289,7 @@ declare function createAttestationField(attestation: EvidenceAttestation): {
2282
2289
  * // Create a C2PA manifest from an Acta receipt chain
2283
2290
  * const manifest = createC2PAManifest(receipts, {
2284
2291
  * title: 'AI-generated report',
2285
- * generator: 'protect-mcp v0.3.3',
2292
+ * generator: 'protect-mcp v0.5.3',
2286
2293
  * });
2287
2294
  *
2288
2295
  * // The manifest can be embedded in images, PDFs, or documents
package/dist/index.d.ts CHANGED
@@ -167,6 +167,13 @@ interface DecisionLog {
167
167
  plan_receipt_id?: string;
168
168
  /** Hook event that triggered this log entry */
169
169
  hook_event?: HookEventName;
170
+ /** IETF specification version — ties every receipt to the standard */
171
+ spec?: string;
172
+ /** Issuer certification level:
173
+ * - "scopeblind:verified" = VOPRF-backed issuance (paid tier)
174
+ * - "self-signed" = local Ed25519 key (free tier, protect-mcp default)
175
+ * - "uncertified" = unsigned receipt (shadow mode) */
176
+ issuer_certification?: 'scopeblind:verified' | 'self-signed' | 'uncertified';
170
177
  }
171
178
  interface SwarmContext {
172
179
  /** Team name from CLAUDE_CODE_TEAM_NAME env var */
@@ -2282,7 +2289,7 @@ declare function createAttestationField(attestation: EvidenceAttestation): {
2282
2289
  * // Create a C2PA manifest from an Acta receipt chain
2283
2290
  * const manifest = createC2PAManifest(receipts, {
2284
2291
  * title: 'AI-generated report',
2285
- * generator: 'protect-mcp v0.3.3',
2292
+ * generator: 'protect-mcp v0.5.3',
2286
2293
  * });
2287
2294
  *
2288
2295
  * // The manifest can be embedded in images, PDFs, or documents
package/dist/index.js CHANGED
@@ -35474,7 +35474,14 @@ function signDecision(entry) {
35474
35474
  scope: entry.request_id,
35475
35475
  // request scope
35476
35476
  mode: entry.mode,
35477
- request_id: entry.request_id
35477
+ request_id: entry.request_id,
35478
+ // Spec version: ties every receipt to the IETF standard
35479
+ spec: "draft-farley-acta-signed-receipts-01",
35480
+ // Issuer certification: distinguishes VOPRF-backed receipts from self-signed ones
35481
+ // - scopeblind:verified = issued via ScopeBlind VOPRF backend (paid tier)
35482
+ // - self-signed = signed with local Ed25519 key (free tier, protect-mcp default)
35483
+ // - uncertified = unsigned receipt (shadow mode, no signing configured)
35484
+ issuer_certification: signerState ? "self-signed" : "uncertified"
35478
35485
  };
35479
35486
  if (entry.tier) payload.tier = entry.tier;
35480
35487
  if (entry.credential_ref) payload.credential_ref = entry.credential_ref;
@@ -35482,6 +35489,12 @@ function signDecision(entry) {
35482
35489
  payload.rate_limit_remaining = entry.rate_limit_remaining;
35483
35490
  }
35484
35491
  if (entry.policy_engine) payload.policy_engine = entry.policy_engine;
35492
+ if (entry.hook_event) payload.hook_event = entry.hook_event;
35493
+ if (entry.sandbox_state) payload.sandbox_state = entry.sandbox_state;
35494
+ if (entry.timing) payload.timing = entry.timing;
35495
+ if (entry.swarm) payload.swarm = entry.swarm;
35496
+ if (entry.payload_digest) payload.payload_digest = entry.payload_digest;
35497
+ if (entry.deny_iteration) payload.deny_iteration = entry.deny_iteration;
35485
35498
  const result = artifactsModule.createSignedArtifact(
35486
35499
  artifactType,
35487
35500
  payload,
@@ -37953,7 +37966,7 @@ async function startHookServer(options = {}) {
37953
37966
  `);
37954
37967
  process.stderr.write(`[PROTECT_MCP] \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
37955
37968
  `);
37956
- process.stderr.write(`[PROTECT_MCP] \u2502 protect-mcp Hook Server v0.5.0 \u2502
37969
+ process.stderr.write(`[PROTECT_MCP] \u2502 protect-mcp Hook Server v0.5.3 \u2502
37957
37970
  `);
37958
37971
  process.stderr.write(`[PROTECT_MCP] \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
37959
37972
  `);
@@ -38349,6 +38362,8 @@ context: inline
38349
38362
 
38350
38363
  # ScopeBlind Receipt Verification
38351
38364
 
38365
+ Every AI agent tool call gets a cryptographic receipt. Verify offline. No vendor trust required.
38366
+
38352
38367
  When the user asks to verify receipts or check the audit trail:
38353
38368
 
38354
38369
  1. **Check for receipt files:**
@@ -39946,7 +39961,7 @@ function handleRequest(request) {
39946
39961
  id: request.id,
39947
39962
  result: {
39948
39963
  protocolVersion: "2024-11-05",
39949
- serverInfo: { name: "protect-mcp-demo", version: "0.2.0" },
39964
+ serverInfo: { name: "protect-mcp-demo", version: "0.5.3" },
39950
39965
  capabilities: { tools: {} }
39951
39966
  }
39952
39967
  });
package/dist/index.mjs CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  formatSimulation,
7
7
  parseLogFile,
8
8
  simulate
9
- } from "./chunk-W4U5VNEC.mjs";
9
+ } from "./chunk-GQWJCHQV.mjs";
10
10
  import {
11
11
  ProtectGateway,
12
12
  buildDecisionContext,
@@ -18,19 +18,19 @@ import {
18
18
  resolveCredential,
19
19
  sendApprovalNotification,
20
20
  validateCredentials
21
- } from "./chunk-IWDR5WU3.mjs";
21
+ } from "./chunk-BYYWYSHM.mjs";
22
22
  import {
23
23
  createSandboxServer
24
- } from "./chunk-SU2FZH7U.mjs";
24
+ } from "./chunk-J6L4XCTE.mjs";
25
25
  import {
26
26
  BUILTIN_PATTERNS,
27
27
  generateHookSettings,
28
28
  generateSampleCedarPolicy,
29
29
  generateVerifyReceiptSkill
30
- } from "./chunk-UEHLYOJY.mjs";
30
+ } from "./chunk-NMZPXXL3.mjs";
31
31
  import {
32
32
  startHookServer
33
- } from "./chunk-Q4KOQUKV.mjs";
33
+ } from "./chunk-FAELTNS7.mjs";
34
34
  import {
35
35
  checkRateLimit,
36
36
  evaluateCedar,
@@ -43,7 +43,7 @@ import {
43
43
  loadPolicy,
44
44
  parseRateLimit,
45
45
  signDecision
46
- } from "./chunk-N4F76LTC.mjs";
46
+ } from "./chunk-YTBC72JJ.mjs";
47
47
  import {
48
48
  collectSignedReceipts,
49
49
  createAuditBundle
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "protect-mcp",
3
- "version": "0.5.2",
4
- "mcpName": "io.github.tomjwxf/protect-mcp",
3
+ "version": "0.5.4",
4
+ "mcpName": "com.scopeblind/protect-mcp",
5
5
  "description": "Enterprise security gateway for MCP servers and Claude Code hooks. Cedar policies, Ed25519-signed receipts, swarm tracking, and tamper detection. Shadow or enforce mode.",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -53,7 +53,7 @@
53
53
  ],
54
54
  "author": "Tom Farley <tommy@scopeblind.com>",
55
55
  "license": "MIT",
56
- "homepage": "https://www.scopeblind.com",
56
+ "homepage": "https://scopeblind.com",
57
57
  "repository": {
58
58
  "type": "git",
59
59
  "url": "git+https://github.com/scopeblind/scopeblind-gateway.git"
@@ -0,0 +1,113 @@
1
+ // ============================================================
2
+ // Secrets & Credential Protection — Cedar Edition
3
+ //
4
+ // Prevents AI agents from reading, writing, or exfiltrating
5
+ // sensitive files (SSH keys, env files, credentials, tokens).
6
+ //
7
+ // Controls: SOC2-CC6.1, SOC2-CC6.7, OWASP-A01, OWASP-A07
8
+ // MCP-01, MCP-04, NIST-GOVERN-1.1
9
+ // ============================================================
10
+
11
+ // Block reading SSH private keys
12
+ @id("sb-secrets-001")
13
+ forbid (
14
+ principal,
15
+ action == Action::"MCP::Tool::call",
16
+ resource == Tool::"read_file"
17
+ )
18
+ when {
19
+ context has "input" &&
20
+ context.input has "path" &&
21
+ context.input.path like "*/.ssh/*"
22
+ };
23
+
24
+ // Block reading .env files
25
+ @id("sb-secrets-002")
26
+ forbid (
27
+ principal,
28
+ action == Action::"MCP::Tool::call",
29
+ resource == Tool::"read_file"
30
+ )
31
+ when {
32
+ context has "input" &&
33
+ context.input has "path" &&
34
+ context.input.path like "*/.env*"
35
+ };
36
+
37
+ // Block reading credential files
38
+ @id("sb-secrets-003")
39
+ forbid (
40
+ principal,
41
+ action == Action::"MCP::Tool::call",
42
+ resource == Tool::"read_file"
43
+ )
44
+ when {
45
+ context has "input" &&
46
+ context.input has "path" &&
47
+ context.input.path like "*/credentials*"
48
+ };
49
+
50
+ // Block reading token files
51
+ @id("sb-secrets-004")
52
+ forbid (
53
+ principal,
54
+ action == Action::"MCP::Tool::call",
55
+ resource == Tool::"read_file"
56
+ )
57
+ when {
58
+ context has "input" &&
59
+ context.input has "path" &&
60
+ context.input.path like "*/.token*"
61
+ };
62
+
63
+ // Block reading AWS credentials
64
+ @id("sb-secrets-005")
65
+ forbid (
66
+ principal,
67
+ action == Action::"MCP::Tool::call",
68
+ resource == Tool::"read_file"
69
+ )
70
+ when {
71
+ context has "input" &&
72
+ context.input has "path" &&
73
+ context.input.path like "*/.aws/*"
74
+ };
75
+
76
+ // Block writing to sensitive directories
77
+ @id("sb-secrets-006")
78
+ forbid (
79
+ principal,
80
+ action == Action::"MCP::Tool::call",
81
+ resource == Tool::"write_file"
82
+ )
83
+ when {
84
+ context has "input" &&
85
+ context.input has "path" &&
86
+ context.input.path like "*/.ssh/*"
87
+ };
88
+
89
+ // Block exfiltrating files via curl upload
90
+ @id("sb-secrets-007")
91
+ forbid (
92
+ principal,
93
+ action == Action::"MCP::Tool::call",
94
+ resource == Tool::"bash"
95
+ )
96
+ when {
97
+ context has "input" &&
98
+ context.input has "command" &&
99
+ context.input.command like "*--upload-file*"
100
+ };
101
+
102
+ // Block piping secrets to network
103
+ @id("sb-secrets-008")
104
+ forbid (
105
+ principal,
106
+ action == Action::"MCP::Tool::call",
107
+ resource == Tool::"bash"
108
+ )
109
+ when {
110
+ context has "input" &&
111
+ context.input has "command" &&
112
+ context.input.command like "*| curl*"
113
+ };