protect-mcp 0.4.2 → 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 +690 -93
- package/dist/cli.mjs +183 -7
- 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");
|
|
@@ -733,7 +740,7 @@ async function handleTrace(argv) {
|
|
|
733
740
|
process.stderr.write("[PROTECT_MCP] Usage: protect-mcp trace <receipt_id> [--endpoint <url>] [--depth <n>]\n");
|
|
734
741
|
process.exit(1);
|
|
735
742
|
}
|
|
736
|
-
let endpoint = "https://
|
|
743
|
+
let endpoint = "https://api.scopeblind.com/evidence";
|
|
737
744
|
let depth = 3;
|
|
738
745
|
for (let i = 1; i < argv.length; i++) {
|
|
739
746
|
if (argv[i] === "--endpoint" && argv[i + 1]) {
|
|
@@ -983,12 +990,57 @@ async function main() {
|
|
|
983
990
|
await handleReport(args.slice(1));
|
|
984
991
|
process.exit(0);
|
|
985
992
|
}
|
|
986
|
-
|
|
993
|
+
if (args[0] === "doctor") {
|
|
994
|
+
await handleDoctor();
|
|
995
|
+
process.exit(0);
|
|
996
|
+
}
|
|
997
|
+
const { policyPath, cedarDir, slug, enforce, verbose, childCommand } = parseArgs(args);
|
|
987
998
|
let policy = null;
|
|
988
999
|
let policyDigest = "none";
|
|
989
1000
|
let credentials;
|
|
990
1001
|
let signing;
|
|
991
|
-
|
|
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) {
|
|
992
1044
|
try {
|
|
993
1045
|
const loaded = loadPolicy(policyPath);
|
|
994
1046
|
policy = loaded.policy;
|
|
@@ -1034,11 +1086,14 @@ async function main() {
|
|
|
1034
1086
|
if (useHttp) {
|
|
1035
1087
|
const portIdx = args.indexOf("--port");
|
|
1036
1088
|
const httpPort = portIdx >= 0 && args[portIdx + 1] ? parseInt(args[portIdx + 1]) : 3e3;
|
|
1037
|
-
const { startHttpTransport } = await import("./http-transport-
|
|
1089
|
+
const { startHttpTransport } = await import("./http-transport-VLIPOPIC.mjs");
|
|
1038
1090
|
startHttpTransport({ port: httpPort, config, serverCommand: childCommand });
|
|
1039
1091
|
return;
|
|
1040
1092
|
}
|
|
1041
1093
|
const gateway = new ProtectGateway(config);
|
|
1094
|
+
if (cedarPolicySet) {
|
|
1095
|
+
gateway.setCedarPolicies(cedarPolicySet);
|
|
1096
|
+
}
|
|
1042
1097
|
await gateway.start();
|
|
1043
1098
|
}
|
|
1044
1099
|
async function handleSimulate(args) {
|
|
@@ -1083,6 +1138,127 @@ async function handleSimulate(args) {
|
|
|
1083
1138
|
process.stdout.write(formatSimulation(summary) + "\n");
|
|
1084
1139
|
}
|
|
1085
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
|
+
}
|
|
1086
1262
|
async function handleReport(args) {
|
|
1087
1263
|
let period = 30;
|
|
1088
1264
|
let format = "json";
|