protect-mcp 0.4.1 → 0.4.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 +1 -1
- package/dist/{chunk-7HBHIKLN.mjs → chunk-VF3OCG4D.mjs} +422 -9
- package/dist/{chunk-VWUN6AI6.mjs → chunk-VIA2B65K.mjs} +1 -1
- package/dist/cli.js +697 -94
- package/dist/cli.mjs +190 -8
- package/dist/{http-transport-RIVV2RVQ.mjs → http-transport-VLIPOPIC.mjs} +1 -1
- package/dist/index.d.mts +1429 -2
- package/dist/index.d.ts +1429 -2
- package/dist/index.js +1728 -25
- package/dist/index.mjs +1273 -3
- package/package.json +4 -3
- package/policies/cedar/clinejection.cedar +50 -0
- package/policies/cedar/terraform-destroy.cedar +44 -0
- package/policies/clinejection.json +6 -0
- package/policies/data-exfiltration.json +6 -0
- package/policies/financial-safe.json +8 -0
- package/policies/github-mcp-hijack.json +6 -0
- package/policies/terraform-destroy.json +6 -0
package/dist/cli.mjs
CHANGED
|
@@ -3,13 +3,15 @@ import {
|
|
|
3
3
|
formatSimulation,
|
|
4
4
|
parseLogFile,
|
|
5
5
|
simulate
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-VIA2B65K.mjs";
|
|
7
7
|
import {
|
|
8
8
|
ProtectGateway,
|
|
9
9
|
initSigning,
|
|
10
|
+
isCedarAvailable,
|
|
11
|
+
loadCedarPolicies,
|
|
10
12
|
loadPolicy,
|
|
11
13
|
validateCredentials
|
|
12
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-VF3OCG4D.mjs";
|
|
13
15
|
|
|
14
16
|
// src/cli.ts
|
|
15
17
|
function printHelp() {
|
|
@@ -31,6 +33,7 @@ Usage:
|
|
|
31
33
|
|
|
32
34
|
Options:
|
|
33
35
|
--policy <path> Policy/config JSON file (default: allow-all)
|
|
36
|
+
--cedar <dir> Cedar policy directory (alternative to --policy, evaluates locally via WASM)
|
|
34
37
|
--slug <slug> ScopeBlind tenant slug (optional)
|
|
35
38
|
--enforce Enable enforcement mode (default: shadow mode)
|
|
36
39
|
--http Start HTTP/SSE server instead of stdio proxy
|
|
@@ -42,6 +45,7 @@ Commands:
|
|
|
42
45
|
quickstart Zero-config onboarding: init + demo + show receipts in one command
|
|
43
46
|
init Generate config template, Ed25519 keypair, and sample policy
|
|
44
47
|
demo Start a demo server wrapped with protect-mcp (see receipts instantly)
|
|
48
|
+
doctor Check your setup: keys, policies, verifier, API connectivity
|
|
45
49
|
trace <id> Visualize the receipt DAG from a given receipt_id (ASCII tree)
|
|
46
50
|
status Show tool call statistics from the local decision log
|
|
47
51
|
digest Generate a human-readable summary of agent activity
|
|
@@ -64,6 +68,7 @@ Examples:
|
|
|
64
68
|
}
|
|
65
69
|
function parseArgs(argv) {
|
|
66
70
|
let policyPath;
|
|
71
|
+
let cedarDir;
|
|
67
72
|
let slug;
|
|
68
73
|
let enforce = false;
|
|
69
74
|
let verbose = false;
|
|
@@ -88,6 +93,8 @@ function parseArgs(argv) {
|
|
|
88
93
|
process.exit(0);
|
|
89
94
|
} else if (arg === "--policy" && i + 1 < options.length) {
|
|
90
95
|
policyPath = options[++i];
|
|
96
|
+
} else if (arg === "--cedar" && i + 1 < options.length) {
|
|
97
|
+
cedarDir = options[++i];
|
|
91
98
|
} else if (arg === "--slug" && i + 1 < options.length) {
|
|
92
99
|
slug = options[++i];
|
|
93
100
|
} else if (arg === "--enforce") {
|
|
@@ -99,7 +106,7 @@ function parseArgs(argv) {
|
|
|
99
106
|
`);
|
|
100
107
|
}
|
|
101
108
|
}
|
|
102
|
-
return { policyPath, slug, enforce, verbose, childCommand };
|
|
109
|
+
return { policyPath, cedarDir, slug, enforce, verbose, childCommand };
|
|
103
110
|
}
|
|
104
111
|
async function handleInit(argv) {
|
|
105
112
|
const { writeFileSync, existsSync, mkdirSync } = await import("fs");
|
|
@@ -217,8 +224,14 @@ Add --enforce when ready to block policy violations.
|
|
|
217
224
|
async function handleDemo() {
|
|
218
225
|
const { existsSync } = await import("fs");
|
|
219
226
|
const { join, dirname, resolve } = await import("path");
|
|
227
|
+
const { realpathSync } = await import("fs");
|
|
220
228
|
const cliPath = resolve(process.argv[1] || "dist/cli.js");
|
|
221
|
-
|
|
229
|
+
let cliDir;
|
|
230
|
+
try {
|
|
231
|
+
cliDir = dirname(realpathSync(cliPath));
|
|
232
|
+
} catch {
|
|
233
|
+
cliDir = dirname(cliPath);
|
|
234
|
+
}
|
|
222
235
|
const demoServerPath = join(cliDir, "demo-server.js");
|
|
223
236
|
const configPath = join(process.cwd(), "protect-mcp.json");
|
|
224
237
|
const hasConfig = existsSync(configPath);
|
|
@@ -727,7 +740,7 @@ async function handleTrace(argv) {
|
|
|
727
740
|
process.stderr.write("[PROTECT_MCP] Usage: protect-mcp trace <receipt_id> [--endpoint <url>] [--depth <n>]\n");
|
|
728
741
|
process.exit(1);
|
|
729
742
|
}
|
|
730
|
-
let endpoint = "https://
|
|
743
|
+
let endpoint = "https://api.scopeblind.com/evidence";
|
|
731
744
|
let depth = 3;
|
|
732
745
|
for (let i = 1; i < argv.length; i++) {
|
|
733
746
|
if (argv[i] === "--endpoint" && argv[i + 1]) {
|
|
@@ -977,12 +990,57 @@ async function main() {
|
|
|
977
990
|
await handleReport(args.slice(1));
|
|
978
991
|
process.exit(0);
|
|
979
992
|
}
|
|
980
|
-
|
|
993
|
+
if (args[0] === "doctor") {
|
|
994
|
+
await handleDoctor();
|
|
995
|
+
process.exit(0);
|
|
996
|
+
}
|
|
997
|
+
const { policyPath, cedarDir, slug, enforce, verbose, childCommand } = parseArgs(args);
|
|
981
998
|
let policy = null;
|
|
982
999
|
let policyDigest = "none";
|
|
983
1000
|
let credentials;
|
|
984
1001
|
let signing;
|
|
985
|
-
|
|
1002
|
+
let cedarPolicySet = null;
|
|
1003
|
+
let effectiveCedarDir = cedarDir;
|
|
1004
|
+
if (!effectiveCedarDir && !policyPath) {
|
|
1005
|
+
const { existsSync, readdirSync } = await import("fs");
|
|
1006
|
+
for (const candidate of ["cedar", "policies", "."]) {
|
|
1007
|
+
try {
|
|
1008
|
+
if (existsSync(candidate) && readdirSync(candidate).some((f) => f.endsWith(".cedar"))) {
|
|
1009
|
+
effectiveCedarDir = candidate;
|
|
1010
|
+
process.stderr.write(`[PROTECT_MCP] Auto-detected Cedar policies in ./${candidate}/
|
|
1011
|
+
`);
|
|
1012
|
+
break;
|
|
1013
|
+
}
|
|
1014
|
+
} catch {
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
if (effectiveCedarDir) {
|
|
1019
|
+
try {
|
|
1020
|
+
const cedarAvailable = await isCedarAvailable();
|
|
1021
|
+
if (!cedarAvailable) {
|
|
1022
|
+
process.stderr.write("[PROTECT_MCP] Warning: @cedar-policy/cedar-wasm not installed. Install with: npm install @cedar-policy/cedar-wasm\n");
|
|
1023
|
+
process.stderr.write("[PROTECT_MCP] Cedar policies will be loaded but evaluated with fallback (allow-all).\n");
|
|
1024
|
+
}
|
|
1025
|
+
cedarPolicySet = loadCedarPolicies(effectiveCedarDir);
|
|
1026
|
+
policyDigest = cedarPolicySet.digest;
|
|
1027
|
+
policy = {
|
|
1028
|
+
tools: { "*": { require: "any" } },
|
|
1029
|
+
policy_engine: "cedar",
|
|
1030
|
+
cedar_dir: effectiveCedarDir
|
|
1031
|
+
};
|
|
1032
|
+
process.stderr.write(`[PROTECT_MCP] Cedar policy engine: loaded ${cedarPolicySet.fileCount} policies from ${effectiveCedarDir} (digest: ${policyDigest})
|
|
1033
|
+
`);
|
|
1034
|
+
if (verbose) {
|
|
1035
|
+
process.stderr.write(`[PROTECT_MCP] Cedar files: ${cedarPolicySet.files.join(", ")}
|
|
1036
|
+
`);
|
|
1037
|
+
}
|
|
1038
|
+
} catch (err) {
|
|
1039
|
+
process.stderr.write(`[PROTECT_MCP] Error loading Cedar policies: ${err instanceof Error ? err.message : err}
|
|
1040
|
+
`);
|
|
1041
|
+
process.exit(1);
|
|
1042
|
+
}
|
|
1043
|
+
} else if (policyPath) {
|
|
986
1044
|
try {
|
|
987
1045
|
const loaded = loadPolicy(policyPath);
|
|
988
1046
|
policy = loaded.policy;
|
|
@@ -1028,11 +1086,14 @@ async function main() {
|
|
|
1028
1086
|
if (useHttp) {
|
|
1029
1087
|
const portIdx = args.indexOf("--port");
|
|
1030
1088
|
const httpPort = portIdx >= 0 && args[portIdx + 1] ? parseInt(args[portIdx + 1]) : 3e3;
|
|
1031
|
-
const { startHttpTransport } = await import("./http-transport-
|
|
1089
|
+
const { startHttpTransport } = await import("./http-transport-VLIPOPIC.mjs");
|
|
1032
1090
|
startHttpTransport({ port: httpPort, config, serverCommand: childCommand });
|
|
1033
1091
|
return;
|
|
1034
1092
|
}
|
|
1035
1093
|
const gateway = new ProtectGateway(config);
|
|
1094
|
+
if (cedarPolicySet) {
|
|
1095
|
+
gateway.setCedarPolicies(cedarPolicySet);
|
|
1096
|
+
}
|
|
1036
1097
|
await gateway.start();
|
|
1037
1098
|
}
|
|
1038
1099
|
async function handleSimulate(args) {
|
|
@@ -1077,6 +1138,127 @@ async function handleSimulate(args) {
|
|
|
1077
1138
|
process.stdout.write(formatSimulation(summary) + "\n");
|
|
1078
1139
|
}
|
|
1079
1140
|
}
|
|
1141
|
+
async function handleDoctor() {
|
|
1142
|
+
const { existsSync, readFileSync, readdirSync } = await import("fs");
|
|
1143
|
+
const { join } = await import("path");
|
|
1144
|
+
const { execSync } = await import("child_process");
|
|
1145
|
+
const green2 = (s) => `\x1B[32m\u2713\x1B[0m ${s}`;
|
|
1146
|
+
const red2 = (s) => `\x1B[31m\u2717\x1B[0m ${s}`;
|
|
1147
|
+
const yellow2 = (s) => `\x1B[33m\u26A0\x1B[0m ${s}`;
|
|
1148
|
+
const dim2 = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
1149
|
+
process.stdout.write("\n\x1B[1mprotect-mcp doctor\x1B[0m\n");
|
|
1150
|
+
process.stdout.write(dim2("Checking your ScopeBlind setup...\n\n"));
|
|
1151
|
+
let issues = 0;
|
|
1152
|
+
const nodeVersion = process.version;
|
|
1153
|
+
const major = parseInt(nodeVersion.slice(1));
|
|
1154
|
+
if (major >= 18) {
|
|
1155
|
+
process.stdout.write(green2(`Node.js ${nodeVersion}
|
|
1156
|
+
`));
|
|
1157
|
+
} else {
|
|
1158
|
+
process.stdout.write(red2(`Node.js ${nodeVersion} \u2014 requires >= 18
|
|
1159
|
+
`));
|
|
1160
|
+
issues++;
|
|
1161
|
+
}
|
|
1162
|
+
const configPath = join(process.cwd(), "scopeblind.config.json");
|
|
1163
|
+
if (existsSync(configPath)) {
|
|
1164
|
+
try {
|
|
1165
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1166
|
+
if (config.signing?.private_key || config.signing?.key_file) {
|
|
1167
|
+
process.stdout.write(green2("Signing keys configured\n"));
|
|
1168
|
+
} else {
|
|
1169
|
+
process.stdout.write(yellow2("Config found but no signing keys \u2014 run: protect-mcp init\n"));
|
|
1170
|
+
issues++;
|
|
1171
|
+
}
|
|
1172
|
+
} catch {
|
|
1173
|
+
process.stdout.write(red2("Invalid scopeblind.config.json\n"));
|
|
1174
|
+
issues++;
|
|
1175
|
+
}
|
|
1176
|
+
} else {
|
|
1177
|
+
process.stdout.write(yellow2("No scopeblind.config.json \u2014 run: protect-mcp init\n"));
|
|
1178
|
+
}
|
|
1179
|
+
let policyFound = false;
|
|
1180
|
+
for (const dir of ["cedar", "policies", "."]) {
|
|
1181
|
+
try {
|
|
1182
|
+
if (existsSync(dir) && readdirSync(dir).some((f) => f.endsWith(".cedar"))) {
|
|
1183
|
+
process.stdout.write(green2(`Cedar policies found in ./${dir}/
|
|
1184
|
+
`));
|
|
1185
|
+
policyFound = true;
|
|
1186
|
+
break;
|
|
1187
|
+
}
|
|
1188
|
+
} catch {
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
if (!policyFound) {
|
|
1192
|
+
for (const name of ["policy.json", "protect-mcp.policy.json", "scopeblind-policy.json"]) {
|
|
1193
|
+
if (existsSync(name)) {
|
|
1194
|
+
process.stdout.write(green2(`JSON policy found: ${name}
|
|
1195
|
+
`));
|
|
1196
|
+
policyFound = true;
|
|
1197
|
+
break;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
if (!policyFound) {
|
|
1202
|
+
process.stdout.write(yellow2("No policy files found \u2014 running in shadow mode (allow all)\n"));
|
|
1203
|
+
}
|
|
1204
|
+
try {
|
|
1205
|
+
const cedarAvailable = await isCedarAvailable();
|
|
1206
|
+
if (cedarAvailable) {
|
|
1207
|
+
process.stdout.write(green2("Cedar WASM engine available\n"));
|
|
1208
|
+
} else {
|
|
1209
|
+
process.stdout.write(dim2(" Cedar WASM not installed \u2014 install: npm install @cedar-policy/cedar-wasm\n"));
|
|
1210
|
+
}
|
|
1211
|
+
} catch {
|
|
1212
|
+
process.stdout.write(dim2(" Cedar WASM not installed\n"));
|
|
1213
|
+
}
|
|
1214
|
+
const logFile = join(process.cwd(), "protect-mcp-decisions.jsonl");
|
|
1215
|
+
const receiptFile = join(process.cwd(), "protect-mcp-receipts.jsonl");
|
|
1216
|
+
if (existsSync(logFile)) {
|
|
1217
|
+
try {
|
|
1218
|
+
const lines = readFileSync(logFile, "utf-8").trim().split("\n").length;
|
|
1219
|
+
process.stdout.write(green2(`Decision log: ${lines} entries
|
|
1220
|
+
`));
|
|
1221
|
+
} catch {
|
|
1222
|
+
process.stdout.write(green2("Decision log exists\n"));
|
|
1223
|
+
}
|
|
1224
|
+
} else {
|
|
1225
|
+
process.stdout.write(dim2(" No decision log yet \u2014 will be created on first tool call\n"));
|
|
1226
|
+
}
|
|
1227
|
+
if (existsSync(receiptFile)) {
|
|
1228
|
+
try {
|
|
1229
|
+
const lines = readFileSync(receiptFile, "utf-8").trim().split("\n").length;
|
|
1230
|
+
process.stdout.write(green2(`Receipt file: ${lines} signed receipts
|
|
1231
|
+
`));
|
|
1232
|
+
} catch {
|
|
1233
|
+
process.stdout.write(green2("Receipt file exists\n"));
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
try {
|
|
1237
|
+
execSync("npx @veritasacta/verify --version 2>/dev/null", { stdio: "pipe", timeout: 1e4 });
|
|
1238
|
+
process.stdout.write(green2("Verifier available: @veritasacta/verify\n"));
|
|
1239
|
+
} catch {
|
|
1240
|
+
process.stdout.write(dim2(" Verifier not cached \u2014 install: npm install -g @veritasacta/verify\n"));
|
|
1241
|
+
}
|
|
1242
|
+
try {
|
|
1243
|
+
const res = await fetch("https://api.scopeblind.com/health", { signal: AbortSignal.timeout(5e3) });
|
|
1244
|
+
if (res.ok) {
|
|
1245
|
+
process.stdout.write(green2("ScopeBlind API reachable\n"));
|
|
1246
|
+
} else {
|
|
1247
|
+
process.stdout.write(yellow2("ScopeBlind API returned non-200 \u2014 receipts will be stored locally\n"));
|
|
1248
|
+
}
|
|
1249
|
+
} catch {
|
|
1250
|
+
process.stdout.write(dim2(" ScopeBlind API not reachable \u2014 offline mode (receipts stored locally)\n"));
|
|
1251
|
+
}
|
|
1252
|
+
process.stdout.write("\n");
|
|
1253
|
+
if (issues === 0) {
|
|
1254
|
+
process.stdout.write("\x1B[32m\x1B[1mAll checks passed.\x1B[0m Ready to wrap MCP servers.\n");
|
|
1255
|
+
process.stdout.write(dim2("\n npx protect-mcp -- node your-server.js\n\n"));
|
|
1256
|
+
} else {
|
|
1257
|
+
process.stdout.write(`\x1B[33m\x1B[1m${issues} issue(s) found.\x1B[0m Fix them and run doctor again.
|
|
1258
|
+
|
|
1259
|
+
`);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1080
1262
|
async function handleReport(args) {
|
|
1081
1263
|
let period = 30;
|
|
1082
1264
|
let format = "json";
|