protect-mcp 0.7.4 → 0.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-NVJHGXXG.mjs → chunk-6E2DHBAR.mjs} +12 -5
- package/dist/{chunk-36UID5WY.mjs → chunk-JCMDLN5I.mjs} +399 -8
- package/dist/{chunk-LYKNULYU.mjs → chunk-LJQOALYR.mjs} +152 -0
- package/dist/{chunk-KPSICBAJ.mjs → chunk-SETXVE2K.mjs} +2 -2
- package/dist/{chunk-UBZJ3VI2.mjs → chunk-VTPZ4G5I.mjs} +1 -1
- package/dist/{chunk-D2RDY2JR.mjs → chunk-WIPWNWMJ.mjs} +1 -1
- package/dist/{chunk-F2FKQ4XN.mjs → chunk-WV4DKYE4.mjs} +78 -2
- package/dist/cli.js +649 -44
- package/dist/cli.mjs +247 -39
- package/dist/demo-server.js +2 -2
- package/dist/demo-server.mjs +1 -1
- package/dist/{ed25519-DZMMNNVE.mjs → ed25519-BSHMMVNX.mjs} +1 -1
- package/dist/hook-server.js +11 -4
- package/dist/hook-server.mjs +2 -2
- package/dist/{http-transport-4GMMRPW7.mjs → http-transport-JBORN27G.mjs} +3 -3
- package/dist/index.d.mts +46 -12
- package/dist/index.d.ts +46 -12
- package/dist/index.js +1747 -37
- package/dist/index.mjs +1182 -34
- package/dist/{signing-committed-MMLJ6OGG.mjs → signing-committed-QXCW24RF.mjs} +2 -2
- package/package.json +2 -2
- package/policies/agent.cedar +50 -0
package/dist/cli.mjs
CHANGED
|
@@ -11,11 +11,11 @@ import {
|
|
|
11
11
|
readInstalledConnectorPilots,
|
|
12
12
|
simulate,
|
|
13
13
|
writeConnectorPilots
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-JCMDLN5I.mjs";
|
|
15
15
|
import {
|
|
16
16
|
ProtectGateway,
|
|
17
17
|
validateCredentials
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-VTPZ4G5I.mjs";
|
|
19
19
|
import {
|
|
20
20
|
buildActionReadback,
|
|
21
21
|
evaluateCedar,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
policySetFromSource,
|
|
27
27
|
runEvaluatorSelfTest,
|
|
28
28
|
signDecision
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-WIPWNWMJ.mjs";
|
|
30
30
|
import "./chunk-PQJP2ZCI.mjs";
|
|
31
31
|
|
|
32
32
|
// src/cli.ts
|
|
@@ -36,7 +36,7 @@ import { basename as basenameCli, dirname as dirnameCli, join as joinCli, resolv
|
|
|
36
36
|
import { homedir as homedirCli } from "os";
|
|
37
37
|
function printHelp() {
|
|
38
38
|
process.stderr.write(`
|
|
39
|
-
protect-mcp
|
|
39
|
+
protect-mcp: Enterprise security gateway for MCP servers & Claude Code hooks
|
|
40
40
|
|
|
41
41
|
Usage:
|
|
42
42
|
protect-mcp [options] -- <command> [args...]
|
|
@@ -59,6 +59,7 @@ Usage:
|
|
|
59
59
|
protect-mcp status [--dir <path>]
|
|
60
60
|
protect-mcp digest [--today] [--dir <path>]
|
|
61
61
|
protect-mcp receipts [--last <n>] [--dir <path>]
|
|
62
|
+
protect-mcp record [--dir <path>] [--live] [--no-open]
|
|
62
63
|
protect-mcp bundle [--output <path>] [--dir <path>]
|
|
63
64
|
protect-mcp simulate --policy <path> [--log <path>] [--tier <tier>] [--json]
|
|
64
65
|
protect-mcp report [--period <days>d] [--format md|json] [--output <path>] [--dir <path>]
|
|
@@ -96,6 +97,7 @@ Commands:
|
|
|
96
97
|
status Show tool call statistics from the local decision log
|
|
97
98
|
digest Generate a human-readable summary of agent activity
|
|
98
99
|
receipts Show recent persisted signed receipts
|
|
100
|
+
record Open a local, searchable view of your record in the browser
|
|
99
101
|
bundle Export an offline-verifiable audit bundle
|
|
100
102
|
|
|
101
103
|
Examples:
|
|
@@ -194,7 +196,7 @@ async function handleInit(argv) {
|
|
|
194
196
|
let keypair;
|
|
195
197
|
{
|
|
196
198
|
const { randomBytes } = await import("crypto");
|
|
197
|
-
const { ed25519 } = await import("./ed25519-
|
|
199
|
+
const { ed25519 } = await import("./ed25519-BSHMMVNX.mjs");
|
|
198
200
|
const { bytesToHex } = await import("./utils-6AYZFE5A.mjs");
|
|
199
201
|
const privateKey = randomBytes(32);
|
|
200
202
|
const publicKey = ed25519.getPublicKey(privateKey);
|
|
@@ -1942,6 +1944,214 @@ ${bold("\u{1F6E1}\uFE0F Recent Receipts")} (last ${recent.length})
|
|
|
1942
1944
|
process.stdout.write(`
|
|
1943
1945
|
`);
|
|
1944
1946
|
}
|
|
1947
|
+
var _pkgV = null;
|
|
1948
|
+
async function pkgVersion() {
|
|
1949
|
+
if (_pkgV) return _pkgV;
|
|
1950
|
+
let v = "0.0.0";
|
|
1951
|
+
try {
|
|
1952
|
+
const { readFileSync, existsSync, realpathSync } = await import("fs");
|
|
1953
|
+
const { dirname, join, resolve } = await import("path");
|
|
1954
|
+
let base = "";
|
|
1955
|
+
try {
|
|
1956
|
+
base = dirname(realpathSync(resolve(process.argv[1] || "")));
|
|
1957
|
+
} catch {
|
|
1958
|
+
}
|
|
1959
|
+
const candidates = [
|
|
1960
|
+
base ? join(base, "..", "package.json") : "",
|
|
1961
|
+
base ? join(base, "package.json") : ""
|
|
1962
|
+
].filter(Boolean);
|
|
1963
|
+
for (const p of candidates) {
|
|
1964
|
+
if (existsSync(p)) {
|
|
1965
|
+
const parsed = JSON.parse(readFileSync(p, "utf-8"));
|
|
1966
|
+
if (parsed && parsed.name === "protect-mcp" && parsed.version) {
|
|
1967
|
+
v = parsed.version;
|
|
1968
|
+
break;
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
} catch {
|
|
1973
|
+
}
|
|
1974
|
+
_pkgV = v;
|
|
1975
|
+
return v;
|
|
1976
|
+
}
|
|
1977
|
+
function mapRecordEntry(e) {
|
|
1978
|
+
const p = e && e.payload && typeof e.payload === "object" ? e.payload : e;
|
|
1979
|
+
const dec = String(p.decision || e.decision || "").toLowerCase();
|
|
1980
|
+
const verdict = /den|block|reject|refus/.test(dec) ? "blocked" : /ask|approv|hold|escal|review|pending/.test(dec) ? "held" : "allowed";
|
|
1981
|
+
const tsRaw = e.issued_at || e.timestamp || p.timestamp || p.issued_at;
|
|
1982
|
+
const ms = typeof tsRaw === "number" ? tsRaw : typeof tsRaw === "string" ? Date.parse(tsRaw) : NaN;
|
|
1983
|
+
const ts = isFinite(ms) ? new Date(ms).toISOString() : "";
|
|
1984
|
+
const tool = String(p.tool || e.tool || "action");
|
|
1985
|
+
const reason = String(p.reason_code || e.reason_code || p.policy_engine || "signed");
|
|
1986
|
+
const hook = String(p.hook_event || e.hook_event || "");
|
|
1987
|
+
const signed = !!(e.signature || e.sig || e.receipt_hash || typeof e.type === "string" && e.type.indexOf("receipt") >= 0);
|
|
1988
|
+
let digest = "";
|
|
1989
|
+
if (e.receipt_hash) digest = String(e.receipt_hash);
|
|
1990
|
+
else if (e.digest) digest = String(e.digest);
|
|
1991
|
+
else if (p.payload_digest && p.payload_digest.output_hash) digest = String(p.payload_digest.output_hash);
|
|
1992
|
+
return { ts, tool, verdict, reason, hook, signed, id: String(e.request_id || p.request_id || ""), digest };
|
|
1993
|
+
}
|
|
1994
|
+
async function handleRecord(argv) {
|
|
1995
|
+
const { readFileSync, existsSync, writeFileSync } = await import("fs");
|
|
1996
|
+
const { join } = await import("path");
|
|
1997
|
+
const osMod = await import("os");
|
|
1998
|
+
const cp = await import("child_process");
|
|
1999
|
+
let dir = process.cwd();
|
|
2000
|
+
const di = argv.indexOf("--dir");
|
|
2001
|
+
if (di !== -1 && argv[di + 1]) dir = argv[di + 1];
|
|
2002
|
+
const recPath = join(dir, ".protect-mcp-receipts.jsonl");
|
|
2003
|
+
const logPath = join(dir, ".protect-mcp-log.jsonl");
|
|
2004
|
+
const pick = () => existsSync(recPath) ? recPath : existsSync(logPath) ? logPath : null;
|
|
2005
|
+
const chosen = pick();
|
|
2006
|
+
if (!chosen) {
|
|
2007
|
+
process.stderr.write(`
|
|
2008
|
+
${bold("protect-mcp record")}
|
|
2009
|
+
|
|
2010
|
+
No record found in ${dir}.
|
|
2011
|
+
Start the gate with ${bold("npx protect-mcp serve")}, use your agent, then run this again.
|
|
2012
|
+
`);
|
|
2013
|
+
process.stderr.write(`Tip: run this in the directory where your gate is signing (where .protect-mcp-receipts.jsonl lives), or pass ${bold("--dir <path>")}.
|
|
2014
|
+
|
|
2015
|
+
`);
|
|
2016
|
+
process.exit(0);
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
const readRecs = (file) => readFileSync(file, "utf-8").split(/\r?\n/).map((l) => l.trim()).filter(Boolean).map((l) => {
|
|
2020
|
+
try {
|
|
2021
|
+
return JSON.parse(l);
|
|
2022
|
+
} catch {
|
|
2023
|
+
return null;
|
|
2024
|
+
}
|
|
2025
|
+
}).filter((x) => x !== null).map(mapRecordEntry);
|
|
2026
|
+
const openTarget = (target) => {
|
|
2027
|
+
if (argv.includes("--no-open")) return;
|
|
2028
|
+
const platform = process.platform;
|
|
2029
|
+
const opener = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
|
|
2030
|
+
const openArgs = platform === "win32" ? ["/c", "start", "", target] : [target];
|
|
2031
|
+
try {
|
|
2032
|
+
const child = cp.spawn(opener, openArgs, { stdio: "ignore", detached: true });
|
|
2033
|
+
child.unref();
|
|
2034
|
+
} catch {
|
|
2035
|
+
}
|
|
2036
|
+
};
|
|
2037
|
+
if (argv.includes("--live") || argv.includes("--watch")) {
|
|
2038
|
+
const http = await import("http");
|
|
2039
|
+
const pi = argv.indexOf("--port");
|
|
2040
|
+
const port = pi !== -1 && argv[pi + 1] ? parseInt(argv[pi + 1], 10) : 9378;
|
|
2041
|
+
const server = http.createServer((req, res) => {
|
|
2042
|
+
if (req.url && req.url.indexOf("/data") === 0) {
|
|
2043
|
+
let recs2 = [];
|
|
2044
|
+
const f = pick();
|
|
2045
|
+
try {
|
|
2046
|
+
if (f) recs2 = readRecs(f);
|
|
2047
|
+
} catch {
|
|
2048
|
+
}
|
|
2049
|
+
res.writeHead(200, { "content-type": "application/json", "cache-control": "no-store" });
|
|
2050
|
+
res.end(JSON.stringify({ recs: recs2, signed: f === recPath }));
|
|
2051
|
+
return;
|
|
2052
|
+
}
|
|
2053
|
+
const meta2 = { file: chosen, signed: pick() === recPath, count: 0, live: true };
|
|
2054
|
+
const page = RECORD_HTML.replace("__DATA__", () => "[]").replace("__META__", () => JSON.stringify(meta2));
|
|
2055
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
2056
|
+
res.end(page);
|
|
2057
|
+
});
|
|
2058
|
+
server.on("error", (e) => {
|
|
2059
|
+
process.stderr.write(`
|
|
2060
|
+
protect-mcp record --live: could not start on port ${port}${e && e.code ? ` (${e.code})` : ""}. Try ${bold("--port <n>")}.
|
|
2061
|
+
|
|
2062
|
+
`);
|
|
2063
|
+
process.exit(1);
|
|
2064
|
+
});
|
|
2065
|
+
server.listen(port, "127.0.0.1", () => {
|
|
2066
|
+
const url = `http://127.0.0.1:${port}`;
|
|
2067
|
+
openTarget(url);
|
|
2068
|
+
process.stdout.write(`
|
|
2069
|
+
${bold("\u{1F6E1}\uFE0F Your record")} ${dim("\xB7")} live at ${url}
|
|
2070
|
+
`);
|
|
2071
|
+
process.stdout.write(` Updates as your agent runs. All local, nothing uploaded. ${dim("Ctrl-C to stop.")}
|
|
2072
|
+
|
|
2073
|
+
`);
|
|
2074
|
+
});
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
2077
|
+
const recs = readRecs(chosen);
|
|
2078
|
+
const meta = { file: chosen, signed: chosen === recPath, count: recs.length, live: false };
|
|
2079
|
+
const html = RECORD_HTML.replace("__DATA__", () => JSON.stringify(recs)).replace("__META__", () => JSON.stringify(meta));
|
|
2080
|
+
const out = join(osMod.tmpdir(), "protect-mcp-record-" + Date.now() + ".html");
|
|
2081
|
+
writeFileSync(out, html);
|
|
2082
|
+
openTarget(out);
|
|
2083
|
+
process.stdout.write(`
|
|
2084
|
+
${bold("\u{1F6E1}\uFE0F Your record")} ${dim("\xB7")} ${recs.length} decision${recs.length === 1 ? "" : "s"}, all on this machine
|
|
2085
|
+
`);
|
|
2086
|
+
if (!meta.signed) process.stdout.write(` ${dim("(decision log; signed receipts appear in .protect-mcp-receipts.jsonl once signing is on)")}
|
|
2087
|
+
`);
|
|
2088
|
+
const fileUrl = "file://" + encodeURI(out);
|
|
2089
|
+
if (process.stdout.isTTY) {
|
|
2090
|
+
process.stdout.write(` Opened in your browser. If it did not open, click: \x1B]8;;${fileUrl}\x1B\\${bold("your record")}\x1B]8;;\x1B\\
|
|
2091
|
+
`);
|
|
2092
|
+
} else {
|
|
2093
|
+
process.stdout.write(` Opened in your browser. If it did not open, open: ${out}
|
|
2094
|
+
`);
|
|
2095
|
+
}
|
|
2096
|
+
process.stdout.write(` ${dim("Want it to update live as your agent runs? npx protect-mcp record --live")}
|
|
2097
|
+
|
|
2098
|
+
`);
|
|
2099
|
+
process.exit(0);
|
|
2100
|
+
}
|
|
2101
|
+
var RECORD_HTML = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>protect-mcp record</title>
|
|
2102
|
+
<style>
|
|
2103
|
+
:root{--paper:#f6f4ef;--ink:#1b1815;--soft:#524d46;--faint:#8a837a;--line:#ddd7c9;--g:#3f6146;--gb:#e7eee3;--a:#8f6216;--ab:#f2e8d3;--r:#7d3535;--rb:#f2e0dc}
|
|
2104
|
+
*{box-sizing:border-box}
|
|
2105
|
+
body{margin:0;background:var(--paper);color:var(--ink);font:15px/1.5 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;-webkit-font-smoothing:antialiased}
|
|
2106
|
+
.wrap{max-width:1000px;margin:0 auto;padding:26px 22px 60px}
|
|
2107
|
+
h1{font-size:24px;margin:0 0 4px;letter-spacing:-.012em}
|
|
2108
|
+
.meta{color:var(--faint);font-size:12.5px;font-family:ui-monospace,Menlo,Consolas,monospace}
|
|
2109
|
+
.bar{margin:18px 0 12px}
|
|
2110
|
+
input{width:100%;padding:10px 13px;border:1px solid var(--line);border-radius:9px;background:#fff;font:inherit}
|
|
2111
|
+
.chips{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:14px}
|
|
2112
|
+
.chip{cursor:pointer;font-size:12px;padding:3px 10px;border-radius:100px;border:1px solid var(--line);background:#fff;color:var(--soft)}
|
|
2113
|
+
.chip.on{background:var(--ink);color:var(--paper);border-color:var(--ink)}
|
|
2114
|
+
.count{color:var(--faint);font-size:12px;font-family:ui-monospace,Menlo,monospace;margin-bottom:8px}
|
|
2115
|
+
.row{border:1px solid var(--line);border-radius:9px;background:#fcfbf7;padding:11px 13px;margin-bottom:8px;cursor:pointer}
|
|
2116
|
+
.top{display:flex;gap:9px;align-items:center;flex-wrap:wrap}
|
|
2117
|
+
.pill{font-size:11px;font-weight:600;padding:2px 9px;border-radius:100px}
|
|
2118
|
+
.pill.allowed{background:var(--gb);color:var(--g)}.pill.held{background:var(--ab);color:var(--a)}.pill.blocked{background:var(--rb);color:var(--r)}
|
|
2119
|
+
.tag{font-size:11px;padding:1px 7px;border-radius:100px;background:var(--paper);border:1px solid var(--line);color:var(--faint)}
|
|
2120
|
+
.when{margin-left:auto;font-size:12px;color:var(--faint);font-family:ui-monospace,Menlo,monospace}
|
|
2121
|
+
.det{margin-top:8px;padding-top:8px;border-top:1px solid var(--line);font-size:12px;color:var(--soft);font-family:ui-monospace,Menlo,monospace;white-space:pre-wrap;word-break:break-all;display:none}
|
|
2122
|
+
.row.open .det{display:block}
|
|
2123
|
+
.foot{margin-top:22px;color:var(--faint);font-size:12px;line-height:1.6;border-top:1px solid var(--line);padding-top:14px}
|
|
2124
|
+
.foot b{color:var(--soft)}
|
|
2125
|
+
</style></head><body><div class="wrap">
|
|
2126
|
+
<h1>Your record</h1>
|
|
2127
|
+
<div class="meta" id="meta"></div>
|
|
2128
|
+
<div class="bar"><input id="q" placeholder="Search your record: tool, reason, verdict"></div>
|
|
2129
|
+
<div class="chips" id="chips"></div>
|
|
2130
|
+
<div class="count" id="count"></div>
|
|
2131
|
+
<div id="list"></div>
|
|
2132
|
+
<div class="foot">Signed decisions from your own gate. This page is local and nothing was uploaded. Verify authoritatively with <b>npx protect-mcp receipts</b> or the open verifier. protect-mcp governs proposed actions before they run.</div>
|
|
2133
|
+
</div>
|
|
2134
|
+
<script>
|
|
2135
|
+
var RECORDS=__DATA__;var META=__META__;var Q="",ACT={};
|
|
2136
|
+
function esc(s){return String(s).replace(/[&<>"]/g,function(c){return{"&":"&","<":"<",">":">",'"':"""}[c]})}
|
|
2137
|
+
function vlabel(v){return v==="allowed"?"Allowed":v==="held"?"Held":"Blocked"}
|
|
2138
|
+
function when(ts){if(!ts)return"";var d=new Date(ts);return d.toLocaleDateString(undefined,{month:"short",day:"numeric"})+" "+d.toLocaleTimeString(undefined,{hour:"2-digit",minute:"2-digit"})}
|
|
2139
|
+
function fvals(key){var m={};RECORDS.forEach(function(r){var v=key==="Decision"?vlabel(r.verdict):r[key.toLowerCase()];if(v){m[v]=(m[v]||0)+1}});return Object.keys(m).sort(function(a,b){return m[b]-m[a]}).slice(0,8).map(function(v){return[v,m[v]]})}
|
|
2140
|
+
function match(r){if(ACT.Decision&&vlabel(r.verdict)!==ACT.Decision)return false;if(ACT.Tool&&r.tool!==ACT.Tool)return false;if(ACT.Reason&&r.reason!==ACT.Reason)return false;if(Q){var h=(r.tool+" "+r.reason+" "+vlabel(r.verdict)+" "+r.hook).toLowerCase();if(h.indexOf(Q)<0)return false}return true}
|
|
2141
|
+
function render(){
|
|
2142
|
+
document.getElementById("meta").textContent=META.count+" decisions from "+META.file+(META.signed?" (signed)":" (decision log)")+" - all local"+(META.live?" \xB7 live, updating":"");
|
|
2143
|
+
var chips="";["Decision","Tool","Reason"].forEach(function(key){fvals(key).forEach(function(p){var on=ACT[key]===p[0];chips+='<span class="chip'+(on?" on":"")+'" data-k="'+key+'" data-v="'+esc(p[0])+'">'+esc(p[0])+" "+p[1]+"</span>"})});
|
|
2144
|
+
document.getElementById("chips").innerHTML=chips;
|
|
2145
|
+
var rows=RECORDS.filter(match);
|
|
2146
|
+
document.getElementById("count").textContent=rows.length+" of "+RECORDS.length+" records";
|
|
2147
|
+
var html="";rows.slice(0,800).forEach(function(r){html+='<div class="row"><div class="top"><span class="pill '+r.verdict+'">'+vlabel(r.verdict)+"</span><b>"+esc(r.tool)+'</b><span class="tag">'+esc(r.reason)+"</span>"+(r.hook?'<span class="tag">'+esc(r.hook)+"</span>":"")+'<span class="tag">'+(r.signed?"signed":"log")+'</span><span class="when">'+esc(when(r.ts))+'</span></div><div class="det">'+esc(JSON.stringify(r,null,2))+"</div></div>"});
|
|
2148
|
+
document.getElementById("list").innerHTML=html||'<p style="color:#8a837a">No records match.</p>'}
|
|
2149
|
+
document.getElementById("q").addEventListener("input",function(e){Q=e.target.value.toLowerCase().trim();render()});
|
|
2150
|
+
document.getElementById("chips").addEventListener("click",function(e){var c=e.target.closest(".chip");if(!c)return;var k=c.getAttribute("data-k"),v=c.getAttribute("data-v");ACT[k]=ACT[k]===v?undefined:v;render()});
|
|
2151
|
+
document.getElementById("list").addEventListener("click",function(e){var row=e.target.closest(".row");if(row)row.classList.toggle("open")});
|
|
2152
|
+
render();
|
|
2153
|
+
if(META.live){var poll=function(){fetch('/data',{cache:'no-store'}).then(function(r){return r.json()}).then(function(d){RECORDS=d.recs||[];META.count=RECORDS.length;if(typeof d.signed==='boolean')META.signed=d.signed;render()}).catch(function(){})};poll();setInterval(poll,2000);}
|
|
2154
|
+
</script></body></html>`;
|
|
1945
2155
|
async function handleBundle(argv) {
|
|
1946
2156
|
const { readFileSync, writeFileSync, existsSync } = await import("fs");
|
|
1947
2157
|
const { join } = await import("path");
|
|
@@ -2063,7 +2273,7 @@ ${bold("protect-mcp connect")}
|
|
|
2063
2273
|
`));
|
|
2064
2274
|
process.stderr.write(` Receipts will be uploaded automatically.
|
|
2065
2275
|
`);
|
|
2066
|
-
process.stderr.write(dim(` Free tier: 20,000 receipts/month
|
|
2276
|
+
process.stderr.write(dim(` Free tier: 20,000 receipts/month, no credit card required.
|
|
2067
2277
|
`));
|
|
2068
2278
|
process.stderr.write(`
|
|
2069
2279
|
${"\u2500".repeat(50)}
|
|
@@ -2106,7 +2316,7 @@ ${bold("protect-mcp quickstart")}
|
|
|
2106
2316
|
const { randomBytes } = await import("crypto");
|
|
2107
2317
|
let keypair;
|
|
2108
2318
|
try {
|
|
2109
|
-
const { ed25519 } = await import("./ed25519-
|
|
2319
|
+
const { ed25519 } = await import("./ed25519-BSHMMVNX.mjs");
|
|
2110
2320
|
const { bytesToHex } = await import("./utils-6AYZFE5A.mjs");
|
|
2111
2321
|
const privateKey = randomBytes(32);
|
|
2112
2322
|
const publicKey = ed25519.getPublicKey(privateKey);
|
|
@@ -2161,7 +2371,7 @@ ${bold("protect-mcp quickstart")}
|
|
|
2161
2371
|
`));
|
|
2162
2372
|
process.stdout.write(` Receipts will be uploaded automatically.
|
|
2163
2373
|
`);
|
|
2164
|
-
process.stdout.write(dim(` Free tier: 20,000 receipts/month
|
|
2374
|
+
process.stdout.write(dim(` Free tier: 20,000 receipts/month, no credit card required.
|
|
2165
2375
|
`));
|
|
2166
2376
|
process.stdout.write(`
|
|
2167
2377
|
`);
|
|
@@ -2321,7 +2531,7 @@ ${bold("protect-mcp registry status")}
|
|
|
2321
2531
|
async function handleKillerDemo(argv) {
|
|
2322
2532
|
const { mkdtempSync } = await import("fs");
|
|
2323
2533
|
const { tmpdir } = await import("os");
|
|
2324
|
-
const { ed25519 } = await import("./ed25519-
|
|
2534
|
+
const { ed25519 } = await import("./ed25519-BSHMMVNX.mjs");
|
|
2325
2535
|
const { bytesToHex } = await import("./utils-6AYZFE5A.mjs");
|
|
2326
2536
|
const { randomBytes } = await import("crypto");
|
|
2327
2537
|
const artifacts = await import("@veritasacta/artifacts");
|
|
@@ -2329,7 +2539,7 @@ async function handleKillerDemo(argv) {
|
|
|
2329
2539
|
createSelectiveDisclosurePackage,
|
|
2330
2540
|
signCommittedDecision,
|
|
2331
2541
|
verifySelectiveDisclosurePackage
|
|
2332
|
-
} = await import("./signing-committed-
|
|
2542
|
+
} = await import("./signing-committed-QXCW24RF.mjs");
|
|
2333
2543
|
const registryMod = await import("./receipt-registry-6CAOY6RP.mjs");
|
|
2334
2544
|
const dir = resolveCli(flagValue(argv, "--dir") || mkdtempSync(joinCli(tmpdir(), "scopeblind-killer-demo-")));
|
|
2335
2545
|
mkdirSyncCli(dir, { recursive: true });
|
|
@@ -2598,7 +2808,7 @@ async function handleVerifyDisclosure(argv) {
|
|
|
2598
2808
|
process.stderr.write("Usage: protect-mcp verify-disclosure --receipt <committed-receipt.json> --disclosure <selective-disclosure.json>\\n");
|
|
2599
2809
|
process.exit(1);
|
|
2600
2810
|
}
|
|
2601
|
-
const { verifySelectiveDisclosurePackage } = await import("./signing-committed-
|
|
2811
|
+
const { verifySelectiveDisclosurePackage } = await import("./signing-committed-QXCW24RF.mjs");
|
|
2602
2812
|
const receipt = JSON.parse(readFileSyncCli(resolveCli(receiptPath), "utf-8"));
|
|
2603
2813
|
const disclosure = JSON.parse(readFileSyncCli(resolveCli(disclosurePath), "utf-8"));
|
|
2604
2814
|
const result = verifySelectiveDisclosurePackage(receipt, disclosure);
|
|
@@ -2910,7 +3120,7 @@ ${bold("protect-mcp trace")}
|
|
|
2910
3120
|
process.stdout.write(`${prefix}${connector}${typeEmoji} ${bold(type)} ${dim(shortId)} ${dim(time)}
|
|
2911
3121
|
`);
|
|
2912
3122
|
if (rendered.has(id)) {
|
|
2913
|
-
process.stdout.write(`${prefix}${childPrefix}${dim("(cycle
|
|
3123
|
+
process.stdout.write(`${prefix}${childPrefix}${dim("(cycle: already rendered)")}
|
|
2914
3124
|
`);
|
|
2915
3125
|
return;
|
|
2916
3126
|
}
|
|
@@ -3094,7 +3304,7 @@ ${bold("protect-mcp init-hooks")}
|
|
|
3094
3304
|
if (!existsSync(keysDir)) mkdirSync(keysDir, { recursive: true });
|
|
3095
3305
|
const { randomBytes: rb } = await import("crypto");
|
|
3096
3306
|
try {
|
|
3097
|
-
const { ed25519 } = await import("./ed25519-
|
|
3307
|
+
const { ed25519 } = await import("./ed25519-BSHMMVNX.mjs");
|
|
3098
3308
|
const { bytesToHex } = await import("./utils-6AYZFE5A.mjs");
|
|
3099
3309
|
const privateKey = rb(32);
|
|
3100
3310
|
const publicKey = ed25519.getPublicKey(privateKey);
|
|
@@ -3113,7 +3323,7 @@ ${bold("protect-mcp init-hooks")}
|
|
|
3113
3323
|
|
|
3114
3324
|
`);
|
|
3115
3325
|
} catch {
|
|
3116
|
-
process.stdout.write(` ${yellow("\u26A0")} Could not generate Ed25519 keys
|
|
3326
|
+
process.stdout.write(` ${yellow("\u26A0")} Could not generate Ed25519 keys, signing disabled
|
|
3117
3327
|
|
|
3118
3328
|
`);
|
|
3119
3329
|
}
|
|
@@ -3184,13 +3394,14 @@ ${bold("protect-mcp init-hooks")}
|
|
|
3184
3394
|
process.stdout.write(` Every tool call will be receipted automatically.
|
|
3185
3395
|
|
|
3186
3396
|
`);
|
|
3187
|
-
process.stdout.write(` 3.
|
|
3397
|
+
process.stdout.write(` 3. See your record: a searchable view of every decision.
|
|
3188
3398
|
`);
|
|
3189
|
-
process.stdout.write(` ${dim(`
|
|
3399
|
+
process.stdout.write(` ${dim(`npx protect-mcp record`)}
|
|
3190
3400
|
`);
|
|
3191
|
-
process.stdout.write(` ${dim(`
|
|
3401
|
+
process.stdout.write(` ${dim(`Everything stays on this machine. Nothing is uploaded.`)}
|
|
3402
|
+
|
|
3192
3403
|
`);
|
|
3193
|
-
process.stdout.write(`
|
|
3404
|
+
process.stdout.write(` Prefer the terminal? ${dim(`npx protect-mcp receipts`)}, or ${dim("/verify-receipt")} in Claude Code.
|
|
3194
3405
|
|
|
3195
3406
|
`);
|
|
3196
3407
|
process.stdout.write(` 4. View policy suggestions:
|
|
@@ -3200,9 +3411,9 @@ ${bold("protect-mcp init-hooks")}
|
|
|
3200
3411
|
`);
|
|
3201
3412
|
process.stdout.write(`${bold("Key facts:")}
|
|
3202
3413
|
`);
|
|
3203
|
-
process.stdout.write(` \u2022 deny decisions are ${bold("AUTHORITATIVE")}
|
|
3414
|
+
process.stdout.write(` \u2022 deny decisions are ${bold("AUTHORITATIVE")}: they cannot be overridden
|
|
3204
3415
|
`);
|
|
3205
|
-
process.stdout.write(` \u2022 PostToolUse runs ${bold("async")}
|
|
3416
|
+
process.stdout.write(` \u2022 PostToolUse runs ${bold("async")}, so there is zero latency impact on tool execution
|
|
3206
3417
|
`);
|
|
3207
3418
|
process.stdout.write(` \u2022 Receipts are Ed25519-signed and append-only
|
|
3208
3419
|
`);
|
|
@@ -3221,15 +3432,7 @@ async function sendInstallTelemetry() {
|
|
|
3221
3432
|
if (existsSync(markerFile) || process.env.PROTECT_MCP_TELEMETRY === "off") {
|
|
3222
3433
|
return;
|
|
3223
3434
|
}
|
|
3224
|
-
|
|
3225
|
-
try {
|
|
3226
|
-
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
3227
|
-
const pkgPath = join(thisDir, "..", "package.json");
|
|
3228
|
-
if (existsSync(pkgPath)) {
|
|
3229
|
-
version = JSON.parse(readFileSync(pkgPath, "utf-8")).version;
|
|
3230
|
-
}
|
|
3231
|
-
} catch {
|
|
3232
|
-
}
|
|
3435
|
+
const version = await pkgVersion();
|
|
3233
3436
|
const controller = new AbortController();
|
|
3234
3437
|
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
3235
3438
|
fetch("https://api.scopeblind.com/telemetry/install", {
|
|
@@ -3403,6 +3606,7 @@ async function main() {
|
|
|
3403
3606
|
sendInstallTelemetry().catch(() => {
|
|
3404
3607
|
});
|
|
3405
3608
|
const args = process.argv.slice(2);
|
|
3609
|
+
process.env.PROTECT_MCP_VERSION = process.env.PROTECT_MCP_VERSION || await pkgVersion();
|
|
3406
3610
|
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
3407
3611
|
printHelp();
|
|
3408
3612
|
process.exit(0);
|
|
@@ -3441,6 +3645,10 @@ async function main() {
|
|
|
3441
3645
|
await startHookServer({ port, policyPath: policyPath2, cedarDir: cedarDir2, enforce: enforce2, verbose: verbose2 });
|
|
3442
3646
|
return;
|
|
3443
3647
|
}
|
|
3648
|
+
if (args[0] === "record") {
|
|
3649
|
+
await handleRecord(args.slice(1));
|
|
3650
|
+
return;
|
|
3651
|
+
}
|
|
3444
3652
|
if (args[0] === "init-hooks") {
|
|
3445
3653
|
await handleInitHooks(args.slice(1));
|
|
3446
3654
|
process.exit(0);
|
|
@@ -3629,7 +3837,7 @@ async function main() {
|
|
|
3629
3837
|
if (useHttp) {
|
|
3630
3838
|
const portIdx = args.indexOf("--port");
|
|
3631
3839
|
const httpPort = portIdx >= 0 && args[portIdx + 1] ? parseInt(args[portIdx + 1]) : 3e3;
|
|
3632
|
-
const { startHttpTransport } = await import("./http-transport-
|
|
3840
|
+
const { startHttpTransport } = await import("./http-transport-JBORN27G.mjs");
|
|
3633
3841
|
startHttpTransport({ port: httpPort, config, serverCommand: childCommand });
|
|
3634
3842
|
return;
|
|
3635
3843
|
}
|
|
@@ -3698,7 +3906,7 @@ async function handleDoctor() {
|
|
|
3698
3906
|
process.stdout.write(green2(`Node.js ${nodeVersion}
|
|
3699
3907
|
`));
|
|
3700
3908
|
} else {
|
|
3701
|
-
process.stdout.write(red2(`Node.js ${nodeVersion}
|
|
3909
|
+
process.stdout.write(red2(`Node.js ${nodeVersion}, requires >= 18
|
|
3702
3910
|
`));
|
|
3703
3911
|
issues++;
|
|
3704
3912
|
}
|
|
@@ -3709,7 +3917,7 @@ async function handleDoctor() {
|
|
|
3709
3917
|
if (config.signing?.private_key || config.signing?.key_file) {
|
|
3710
3918
|
process.stdout.write(green2("Signing keys configured\n"));
|
|
3711
3919
|
} else {
|
|
3712
|
-
process.stdout.write(yellow2("Config found but no signing keys
|
|
3920
|
+
process.stdout.write(yellow2("Config found but no signing keys. Run: protect-mcp init\n"));
|
|
3713
3921
|
issues++;
|
|
3714
3922
|
}
|
|
3715
3923
|
} catch {
|
|
@@ -3717,7 +3925,7 @@ async function handleDoctor() {
|
|
|
3717
3925
|
issues++;
|
|
3718
3926
|
}
|
|
3719
3927
|
} else {
|
|
3720
|
-
process.stdout.write(yellow2("No scopeblind.config.json
|
|
3928
|
+
process.stdout.write(yellow2("No scopeblind.config.json. Run: protect-mcp init\n"));
|
|
3721
3929
|
}
|
|
3722
3930
|
let policyFound = false;
|
|
3723
3931
|
for (const dir of ["cedar", "policies", "."]) {
|
|
@@ -3742,14 +3950,14 @@ async function handleDoctor() {
|
|
|
3742
3950
|
}
|
|
3743
3951
|
}
|
|
3744
3952
|
if (!policyFound) {
|
|
3745
|
-
process.stdout.write(yellow2("No policy files found
|
|
3953
|
+
process.stdout.write(yellow2("No policy files found, running in shadow mode (allow all)\n"));
|
|
3746
3954
|
}
|
|
3747
3955
|
try {
|
|
3748
3956
|
const cedarAvailable = await isCedarAvailable();
|
|
3749
3957
|
if (cedarAvailable) {
|
|
3750
3958
|
process.stdout.write(green2("Cedar WASM engine available\n"));
|
|
3751
3959
|
} else {
|
|
3752
|
-
process.stdout.write(dim2(" Cedar WASM not installed
|
|
3960
|
+
process.stdout.write(dim2(" Cedar WASM not installed. Install: npm install @cedar-policy/cedar-wasm\n"));
|
|
3753
3961
|
}
|
|
3754
3962
|
} catch {
|
|
3755
3963
|
process.stdout.write(dim2(" Cedar WASM not installed\n"));
|
|
@@ -3765,7 +3973,7 @@ async function handleDoctor() {
|
|
|
3765
3973
|
process.stdout.write(green2("Decision log exists\n"));
|
|
3766
3974
|
}
|
|
3767
3975
|
} else {
|
|
3768
|
-
process.stdout.write(dim2(" No decision log yet
|
|
3976
|
+
process.stdout.write(dim2(" No decision log yet, will be created on first tool call\n"));
|
|
3769
3977
|
}
|
|
3770
3978
|
if (existsSync(receiptFile)) {
|
|
3771
3979
|
try {
|
|
@@ -3780,17 +3988,17 @@ async function handleDoctor() {
|
|
|
3780
3988
|
execSync("npx @veritasacta/verify --version 2>/dev/null", { stdio: "pipe", timeout: 1e4 });
|
|
3781
3989
|
process.stdout.write(green2("Verifier available: @veritasacta/verify\n"));
|
|
3782
3990
|
} catch {
|
|
3783
|
-
process.stdout.write(dim2(" Verifier not cached
|
|
3991
|
+
process.stdout.write(dim2(" Verifier not cached. Install: npm install -g @veritasacta/verify\n"));
|
|
3784
3992
|
}
|
|
3785
3993
|
try {
|
|
3786
3994
|
const res = await fetch("https://api.scopeblind.com/health", { signal: AbortSignal.timeout(5e3) });
|
|
3787
3995
|
if (res.ok) {
|
|
3788
3996
|
process.stdout.write(green2("ScopeBlind API reachable\n"));
|
|
3789
3997
|
} else {
|
|
3790
|
-
process.stdout.write(yellow2("ScopeBlind API returned non-200
|
|
3998
|
+
process.stdout.write(yellow2("ScopeBlind API returned non-200, receipts will be stored locally\n"));
|
|
3791
3999
|
}
|
|
3792
4000
|
} catch {
|
|
3793
|
-
process.stdout.write(dim2(" ScopeBlind API not reachable
|
|
4001
|
+
process.stdout.write(dim2(" ScopeBlind API not reachable, offline mode (receipts stored locally)\n"));
|
|
3794
4002
|
}
|
|
3795
4003
|
process.stdout.write("\nRestraint self-test:\n");
|
|
3796
4004
|
try {
|
package/dist/demo-server.js
CHANGED
|
@@ -35115,7 +35115,7 @@ function handleRequest(request) {
|
|
|
35115
35115
|
id: request.id,
|
|
35116
35116
|
result: {
|
|
35117
35117
|
protocolVersion: "2024-11-05",
|
|
35118
|
-
serverInfo: { name: "protect-mcp-demo", version: "0.5.3" },
|
|
35118
|
+
serverInfo: { name: "protect-mcp-demo", version: process.env.PROTECT_MCP_VERSION || "0.5.3" },
|
|
35119
35119
|
capabilities: { tools: {} }
|
|
35120
35120
|
}
|
|
35121
35121
|
});
|
|
@@ -35201,7 +35201,7 @@ function createSandboxServer() {
|
|
|
35201
35201
|
const { z } = require_zod();
|
|
35202
35202
|
const server = new McpServer({
|
|
35203
35203
|
name: "protect-mcp",
|
|
35204
|
-
version: "0.4.5",
|
|
35204
|
+
version: process.env.PROTECT_MCP_VERSION || "0.4.5",
|
|
35205
35205
|
description: "Security gateway for MCP servers. Per-tool policies, Ed25519-signed receipts, human approval gates, trust tiers."
|
|
35206
35206
|
});
|
|
35207
35207
|
server.tool(
|
package/dist/demo-server.mjs
CHANGED
package/dist/hook-server.js
CHANGED
|
@@ -1361,7 +1361,7 @@ async function startHookServer(options = {}) {
|
|
|
1361
1361
|
res.end(JSON.stringify({
|
|
1362
1362
|
status: "ok",
|
|
1363
1363
|
server: "protect-mcp-hooks",
|
|
1364
|
-
version:
|
|
1364
|
+
version: process.env.PROTECT_MCP_VERSION || "unknown",
|
|
1365
1365
|
uptime_ms: Date.now() - state.startTime,
|
|
1366
1366
|
mode: enforce ? "enforce" : "shadow",
|
|
1367
1367
|
policy_digest: policyDigest,
|
|
@@ -1448,9 +1448,10 @@ async function startHookServer(options = {}) {
|
|
|
1448
1448
|
const pad = (s, n = 46) => s.padEnd(n);
|
|
1449
1449
|
w(`
|
|
1450
1450
|
`);
|
|
1451
|
-
w(` protect-mcp
|
|
1451
|
+
w(process.env.PROTECT_MCP_VERSION ? ` protect-mcp v${process.env.PROTECT_MCP_VERSION}
|
|
1452
|
+
` : ` protect-mcp
|
|
1452
1453
|
`);
|
|
1453
|
-
w(` ScopeBlind \
|
|
1454
|
+
w(` ScopeBlind \xB7 https://scopeblind.com
|
|
1454
1455
|
`);
|
|
1455
1456
|
w(`
|
|
1456
1457
|
`);
|
|
@@ -1478,7 +1479,13 @@ async function startHookServer(options = {}) {
|
|
|
1478
1479
|
`);
|
|
1479
1480
|
w(`
|
|
1480
1481
|
`);
|
|
1481
|
-
w(` deny is authoritative
|
|
1482
|
+
w(` deny is authoritative: it cannot be overridden.
|
|
1483
|
+
`);
|
|
1484
|
+
w(`
|
|
1485
|
+
`);
|
|
1486
|
+
w(` See your record npx protect-mcp record
|
|
1487
|
+
`);
|
|
1488
|
+
w(` a searchable view of every decision, all on this machine
|
|
1482
1489
|
`);
|
|
1483
1490
|
w(`
|
|
1484
1491
|
`);
|
package/dist/hook-server.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ProtectGateway
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-VTPZ4G5I.mjs";
|
|
4
|
+
import "./chunk-WIPWNWMJ.mjs";
|
|
5
5
|
import "./chunk-PQJP2ZCI.mjs";
|
|
6
6
|
|
|
7
7
|
// src/http-transport.ts
|
|
@@ -34,7 +34,7 @@ async function startHttpTransport(options) {
|
|
|
34
34
|
res.end(JSON.stringify({
|
|
35
35
|
status: "ok",
|
|
36
36
|
server: "protect-mcp",
|
|
37
|
-
version:
|
|
37
|
+
version: process.env.PROTECT_MCP_VERSION || "unknown",
|
|
38
38
|
transport: "streamable-http",
|
|
39
39
|
mode: config.policy ? config.enforce ? "enforce" : "shadow" : "shadow",
|
|
40
40
|
wrapping: serverCommand.join(" ")
|