protect-mcp 0.5.2 → 0.5.3
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 +2 -2
- package/dist/{chunk-IWDR5WU3.mjs → chunk-BYYWYSHM.mjs} +1 -1
- package/dist/{chunk-W4U5VNEC.mjs → chunk-GQWJCHQV.mjs} +2 -2
- package/dist/{chunk-Q4KOQUKV.mjs → chunk-HFM6K7E4.mjs} +1 -1
- package/dist/{chunk-UEHLYOJY.mjs → chunk-NMZPXXL3.mjs} +2 -0
- package/dist/{chunk-N4F76LTC.mjs → chunk-YTBC72JJ.mjs} +14 -1
- package/dist/cli.js +176 -6
- package/dist/cli.mjs +163 -9
- package/dist/hook-patterns.js +2 -0
- package/dist/hook-patterns.mjs +1 -1
- package/dist/hook-server.js +14 -1
- package/dist/hook-server.mjs +2 -2
- package/dist/{http-transport-PWCK7JHZ.mjs → http-transport-LNBENGXD.mjs} +2 -2
- package/dist/index.d.mts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +16 -1
- package/dist/index.mjs +5 -5
- package/package.json +2 -2
- package/policies/cedar/secrets-detection.cedar +113 -0
package/README.md
CHANGED
|
@@ -248,9 +248,9 @@ Self-contained offline-verifiable bundle with receipts + signing keys. Verify wi
|
|
|
248
248
|
|
|
249
249
|
## Standards & IP
|
|
250
250
|
|
|
251
|
-
- **IETF Internet-Draft**: [draft-farley-acta-signed-receipts-
|
|
251
|
+
- **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
252
|
- **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**:
|
|
253
|
+
- **Verification**: Apache-2.0 — `npx @veritasacta/verify --self-test`
|
|
254
254
|
- **Microsoft AGT Integration**: [PR #667](https://github.com/microsoft/agent-governance-toolkit/pull/667) — Cedar policy bridge for Agent Governance Toolkit
|
|
255
255
|
|
|
256
256
|
## License
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
meetsMinTier
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-BYYWYSHM.mjs";
|
|
4
4
|
import {
|
|
5
5
|
checkRateLimit,
|
|
6
6
|
getToolPolicy,
|
|
7
7
|
parseRateLimit
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-YTBC72JJ.mjs";
|
|
9
9
|
|
|
10
10
|
// src/simulate.ts
|
|
11
11
|
import { readFileSync } from "fs";
|
|
@@ -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:**
|
|
@@ -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
|
|
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
|
-
|
|
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-
|
|
6
|
+
} from "./chunk-GQWJCHQV.mjs";
|
|
7
7
|
import {
|
|
8
8
|
ProtectGateway,
|
|
9
9
|
validateCredentials
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-BYYWYSHM.mjs";
|
|
11
11
|
import {
|
|
12
12
|
initSigning,
|
|
13
13
|
isCedarAvailable,
|
|
14
14
|
loadCedarPolicies,
|
|
15
15
|
loadPolicy
|
|
16
|
-
} from "./chunk-
|
|
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
|
|
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
|
-
|
|
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-
|
|
1439
|
+
const { startHttpTransport } = await import("./http-transport-LNBENGXD.mjs");
|
|
1286
1440
|
startHttpTransport({ port: httpPort, config, serverCommand: childCommand });
|
|
1287
1441
|
return;
|
|
1288
1442
|
}
|
package/dist/hook-patterns.js
CHANGED
|
@@ -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:**
|
package/dist/hook-patterns.mjs
CHANGED
package/dist/hook-server.js
CHANGED
|
@@ -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,
|
package/dist/hook-server.mjs
CHANGED
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 */
|
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 */
|
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,
|
|
@@ -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:**
|
package/dist/index.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
formatSimulation,
|
|
7
7
|
parseLogFile,
|
|
8
8
|
simulate
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-GQWJCHQV.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-
|
|
21
|
+
} from "./chunk-BYYWYSHM.mjs";
|
|
22
22
|
import {
|
|
23
23
|
createSandboxServer
|
|
24
24
|
} from "./chunk-SU2FZH7U.mjs";
|
|
@@ -27,10 +27,10 @@ import {
|
|
|
27
27
|
generateHookSettings,
|
|
28
28
|
generateSampleCedarPolicy,
|
|
29
29
|
generateVerifyReceiptSkill
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-NMZPXXL3.mjs";
|
|
31
31
|
import {
|
|
32
32
|
startHookServer
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-HFM6K7E4.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-
|
|
46
|
+
} from "./chunk-YTBC72JJ.mjs";
|
|
47
47
|
import {
|
|
48
48
|
collectSignedReceipts,
|
|
49
49
|
createAuditBundle
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "protect-mcp",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"mcpName": "io.github.tomjwxf/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",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
],
|
|
54
54
|
"author": "Tom Farley <tommy@scopeblind.com>",
|
|
55
55
|
"license": "MIT",
|
|
56
|
-
"homepage": "https://
|
|
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
|
+
};
|