protect-mcp 0.7.4 → 0.7.5
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 +597 -44
- package/dist/cli.mjs +195 -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/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>] [--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,162 @@ ${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 chosen = existsSync(recPath) ? recPath : existsSync(logPath) ? logPath : null;
|
|
2005
|
+
if (!chosen) {
|
|
2006
|
+
process.stderr.write(`
|
|
2007
|
+
${bold("protect-mcp record")}
|
|
2008
|
+
|
|
2009
|
+
No record found in ${dir}.
|
|
2010
|
+
Start the gate with ${bold("npx protect-mcp serve")}, use your agent, then run this again.
|
|
2011
|
+
|
|
2012
|
+
`);
|
|
2013
|
+
process.exit(0);
|
|
2014
|
+
return;
|
|
2015
|
+
}
|
|
2016
|
+
const raw = readFileSync(chosen, "utf-8");
|
|
2017
|
+
const recs = raw.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).map((l) => {
|
|
2018
|
+
try {
|
|
2019
|
+
return JSON.parse(l);
|
|
2020
|
+
} catch {
|
|
2021
|
+
return null;
|
|
2022
|
+
}
|
|
2023
|
+
}).filter((x) => x !== null).map(mapRecordEntry);
|
|
2024
|
+
const meta = { file: chosen, signed: chosen === recPath, count: recs.length };
|
|
2025
|
+
const html = RECORD_HTML.replace("__DATA__", () => JSON.stringify(recs)).replace("__META__", () => JSON.stringify(meta));
|
|
2026
|
+
const out = join(osMod.tmpdir(), "protect-mcp-record-" + Date.now() + ".html");
|
|
2027
|
+
writeFileSync(out, html);
|
|
2028
|
+
if (!argv.includes("--no-open")) {
|
|
2029
|
+
const platform = process.platform;
|
|
2030
|
+
const opener = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
|
|
2031
|
+
const openArgs = platform === "win32" ? ["/c", "start", "", out] : [out];
|
|
2032
|
+
try {
|
|
2033
|
+
const child = cp.spawn(opener, openArgs, { stdio: "ignore", detached: true });
|
|
2034
|
+
child.unref();
|
|
2035
|
+
} catch {
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
process.stdout.write(`
|
|
2039
|
+
${bold("\u{1F6E1}\uFE0F Your record")} ${recs.length} decision${recs.length === 1 ? "" : "s"}
|
|
2040
|
+
`);
|
|
2041
|
+
process.stdout.write(` Opened a searchable view in your browser. Everything stayed on this machine.
|
|
2042
|
+
`);
|
|
2043
|
+
if (!meta.signed) process.stdout.write(` ${dim("(decision log; signed receipts appear in .protect-mcp-receipts.jsonl once signing is on)")}
|
|
2044
|
+
`);
|
|
2045
|
+
process.stdout.write(` ${dim("If it did not open, open this file: " + out)}
|
|
2046
|
+
|
|
2047
|
+
`);
|
|
2048
|
+
process.exit(0);
|
|
2049
|
+
}
|
|
2050
|
+
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>
|
|
2051
|
+
<style>
|
|
2052
|
+
:root{--paper:#f6f4ef;--ink:#1b1815;--soft:#524d46;--faint:#8a837a;--line:#ddd7c9;--g:#3f6146;--gb:#e7eee3;--a:#8f6216;--ab:#f2e8d3;--r:#7d3535;--rb:#f2e0dc}
|
|
2053
|
+
*{box-sizing:border-box}
|
|
2054
|
+
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}
|
|
2055
|
+
.wrap{max-width:1000px;margin:0 auto;padding:26px 22px 60px}
|
|
2056
|
+
h1{font-size:24px;margin:0 0 4px;letter-spacing:-.012em}
|
|
2057
|
+
.meta{color:var(--faint);font-size:12.5px;font-family:ui-monospace,Menlo,Consolas,monospace}
|
|
2058
|
+
.bar{margin:18px 0 12px}
|
|
2059
|
+
input{width:100%;padding:10px 13px;border:1px solid var(--line);border-radius:9px;background:#fff;font:inherit}
|
|
2060
|
+
.chips{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:14px}
|
|
2061
|
+
.chip{cursor:pointer;font-size:12px;padding:3px 10px;border-radius:100px;border:1px solid var(--line);background:#fff;color:var(--soft)}
|
|
2062
|
+
.chip.on{background:var(--ink);color:var(--paper);border-color:var(--ink)}
|
|
2063
|
+
.count{color:var(--faint);font-size:12px;font-family:ui-monospace,Menlo,monospace;margin-bottom:8px}
|
|
2064
|
+
.row{border:1px solid var(--line);border-radius:9px;background:#fcfbf7;padding:11px 13px;margin-bottom:8px;cursor:pointer}
|
|
2065
|
+
.top{display:flex;gap:9px;align-items:center;flex-wrap:wrap}
|
|
2066
|
+
.pill{font-size:11px;font-weight:600;padding:2px 9px;border-radius:100px}
|
|
2067
|
+
.pill.allowed{background:var(--gb);color:var(--g)}.pill.held{background:var(--ab);color:var(--a)}.pill.blocked{background:var(--rb);color:var(--r)}
|
|
2068
|
+
.tag{font-size:11px;padding:1px 7px;border-radius:100px;background:var(--paper);border:1px solid var(--line);color:var(--faint)}
|
|
2069
|
+
.when{margin-left:auto;font-size:12px;color:var(--faint);font-family:ui-monospace,Menlo,monospace}
|
|
2070
|
+
.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}
|
|
2071
|
+
.row.open .det{display:block}
|
|
2072
|
+
.foot{margin-top:22px;color:var(--faint);font-size:12px;line-height:1.6;border-top:1px solid var(--line);padding-top:14px}
|
|
2073
|
+
.foot b{color:var(--soft)}
|
|
2074
|
+
</style></head><body><div class="wrap">
|
|
2075
|
+
<h1>Your record</h1>
|
|
2076
|
+
<div class="meta" id="meta"></div>
|
|
2077
|
+
<div class="bar"><input id="q" placeholder="Search your record: tool, reason, verdict"></div>
|
|
2078
|
+
<div class="chips" id="chips"></div>
|
|
2079
|
+
<div class="count" id="count"></div>
|
|
2080
|
+
<div id="list"></div>
|
|
2081
|
+
<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>
|
|
2082
|
+
</div>
|
|
2083
|
+
<script>
|
|
2084
|
+
var RECORDS=__DATA__;var META=__META__;var Q="",ACT={};
|
|
2085
|
+
function esc(s){return String(s).replace(/[&<>"]/g,function(c){return{"&":"&","<":"<",">":">",'"':"""}[c]})}
|
|
2086
|
+
function vlabel(v){return v==="allowed"?"Allowed":v==="held"?"Held":"Blocked"}
|
|
2087
|
+
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"})}
|
|
2088
|
+
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]]})}
|
|
2089
|
+
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}
|
|
2090
|
+
function render(){
|
|
2091
|
+
document.getElementById("meta").textContent=META.count+" decisions from "+META.file+(META.signed?" (signed)":" (decision log)")+" - all local";
|
|
2092
|
+
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>"})});
|
|
2093
|
+
document.getElementById("chips").innerHTML=chips;
|
|
2094
|
+
var rows=RECORDS.filter(match);
|
|
2095
|
+
document.getElementById("count").textContent=rows.length+" of "+RECORDS.length+" records";
|
|
2096
|
+
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>"});
|
|
2097
|
+
document.getElementById("list").innerHTML=html||'<p style="color:#8a837a">No records match.</p>'}
|
|
2098
|
+
document.getElementById("q").addEventListener("input",function(e){Q=e.target.value.toLowerCase().trim();render()});
|
|
2099
|
+
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()});
|
|
2100
|
+
document.getElementById("list").addEventListener("click",function(e){var row=e.target.closest(".row");if(row)row.classList.toggle("open")});
|
|
2101
|
+
render();
|
|
2102
|
+
</script></body></html>`;
|
|
1945
2103
|
async function handleBundle(argv) {
|
|
1946
2104
|
const { readFileSync, writeFileSync, existsSync } = await import("fs");
|
|
1947
2105
|
const { join } = await import("path");
|
|
@@ -2063,7 +2221,7 @@ ${bold("protect-mcp connect")}
|
|
|
2063
2221
|
`));
|
|
2064
2222
|
process.stderr.write(` Receipts will be uploaded automatically.
|
|
2065
2223
|
`);
|
|
2066
|
-
process.stderr.write(dim(` Free tier: 20,000 receipts/month
|
|
2224
|
+
process.stderr.write(dim(` Free tier: 20,000 receipts/month, no credit card required.
|
|
2067
2225
|
`));
|
|
2068
2226
|
process.stderr.write(`
|
|
2069
2227
|
${"\u2500".repeat(50)}
|
|
@@ -2106,7 +2264,7 @@ ${bold("protect-mcp quickstart")}
|
|
|
2106
2264
|
const { randomBytes } = await import("crypto");
|
|
2107
2265
|
let keypair;
|
|
2108
2266
|
try {
|
|
2109
|
-
const { ed25519 } = await import("./ed25519-
|
|
2267
|
+
const { ed25519 } = await import("./ed25519-BSHMMVNX.mjs");
|
|
2110
2268
|
const { bytesToHex } = await import("./utils-6AYZFE5A.mjs");
|
|
2111
2269
|
const privateKey = randomBytes(32);
|
|
2112
2270
|
const publicKey = ed25519.getPublicKey(privateKey);
|
|
@@ -2161,7 +2319,7 @@ ${bold("protect-mcp quickstart")}
|
|
|
2161
2319
|
`));
|
|
2162
2320
|
process.stdout.write(` Receipts will be uploaded automatically.
|
|
2163
2321
|
`);
|
|
2164
|
-
process.stdout.write(dim(` Free tier: 20,000 receipts/month
|
|
2322
|
+
process.stdout.write(dim(` Free tier: 20,000 receipts/month, no credit card required.
|
|
2165
2323
|
`));
|
|
2166
2324
|
process.stdout.write(`
|
|
2167
2325
|
`);
|
|
@@ -2321,7 +2479,7 @@ ${bold("protect-mcp registry status")}
|
|
|
2321
2479
|
async function handleKillerDemo(argv) {
|
|
2322
2480
|
const { mkdtempSync } = await import("fs");
|
|
2323
2481
|
const { tmpdir } = await import("os");
|
|
2324
|
-
const { ed25519 } = await import("./ed25519-
|
|
2482
|
+
const { ed25519 } = await import("./ed25519-BSHMMVNX.mjs");
|
|
2325
2483
|
const { bytesToHex } = await import("./utils-6AYZFE5A.mjs");
|
|
2326
2484
|
const { randomBytes } = await import("crypto");
|
|
2327
2485
|
const artifacts = await import("@veritasacta/artifacts");
|
|
@@ -2329,7 +2487,7 @@ async function handleKillerDemo(argv) {
|
|
|
2329
2487
|
createSelectiveDisclosurePackage,
|
|
2330
2488
|
signCommittedDecision,
|
|
2331
2489
|
verifySelectiveDisclosurePackage
|
|
2332
|
-
} = await import("./signing-committed-
|
|
2490
|
+
} = await import("./signing-committed-QXCW24RF.mjs");
|
|
2333
2491
|
const registryMod = await import("./receipt-registry-6CAOY6RP.mjs");
|
|
2334
2492
|
const dir = resolveCli(flagValue(argv, "--dir") || mkdtempSync(joinCli(tmpdir(), "scopeblind-killer-demo-")));
|
|
2335
2493
|
mkdirSyncCli(dir, { recursive: true });
|
|
@@ -2598,7 +2756,7 @@ async function handleVerifyDisclosure(argv) {
|
|
|
2598
2756
|
process.stderr.write("Usage: protect-mcp verify-disclosure --receipt <committed-receipt.json> --disclosure <selective-disclosure.json>\\n");
|
|
2599
2757
|
process.exit(1);
|
|
2600
2758
|
}
|
|
2601
|
-
const { verifySelectiveDisclosurePackage } = await import("./signing-committed-
|
|
2759
|
+
const { verifySelectiveDisclosurePackage } = await import("./signing-committed-QXCW24RF.mjs");
|
|
2602
2760
|
const receipt = JSON.parse(readFileSyncCli(resolveCli(receiptPath), "utf-8"));
|
|
2603
2761
|
const disclosure = JSON.parse(readFileSyncCli(resolveCli(disclosurePath), "utf-8"));
|
|
2604
2762
|
const result = verifySelectiveDisclosurePackage(receipt, disclosure);
|
|
@@ -2910,7 +3068,7 @@ ${bold("protect-mcp trace")}
|
|
|
2910
3068
|
process.stdout.write(`${prefix}${connector}${typeEmoji} ${bold(type)} ${dim(shortId)} ${dim(time)}
|
|
2911
3069
|
`);
|
|
2912
3070
|
if (rendered.has(id)) {
|
|
2913
|
-
process.stdout.write(`${prefix}${childPrefix}${dim("(cycle
|
|
3071
|
+
process.stdout.write(`${prefix}${childPrefix}${dim("(cycle: already rendered)")}
|
|
2914
3072
|
`);
|
|
2915
3073
|
return;
|
|
2916
3074
|
}
|
|
@@ -3094,7 +3252,7 @@ ${bold("protect-mcp init-hooks")}
|
|
|
3094
3252
|
if (!existsSync(keysDir)) mkdirSync(keysDir, { recursive: true });
|
|
3095
3253
|
const { randomBytes: rb } = await import("crypto");
|
|
3096
3254
|
try {
|
|
3097
|
-
const { ed25519 } = await import("./ed25519-
|
|
3255
|
+
const { ed25519 } = await import("./ed25519-BSHMMVNX.mjs");
|
|
3098
3256
|
const { bytesToHex } = await import("./utils-6AYZFE5A.mjs");
|
|
3099
3257
|
const privateKey = rb(32);
|
|
3100
3258
|
const publicKey = ed25519.getPublicKey(privateKey);
|
|
@@ -3113,7 +3271,7 @@ ${bold("protect-mcp init-hooks")}
|
|
|
3113
3271
|
|
|
3114
3272
|
`);
|
|
3115
3273
|
} catch {
|
|
3116
|
-
process.stdout.write(` ${yellow("\u26A0")} Could not generate Ed25519 keys
|
|
3274
|
+
process.stdout.write(` ${yellow("\u26A0")} Could not generate Ed25519 keys, signing disabled
|
|
3117
3275
|
|
|
3118
3276
|
`);
|
|
3119
3277
|
}
|
|
@@ -3184,13 +3342,14 @@ ${bold("protect-mcp init-hooks")}
|
|
|
3184
3342
|
process.stdout.write(` Every tool call will be receipted automatically.
|
|
3185
3343
|
|
|
3186
3344
|
`);
|
|
3187
|
-
process.stdout.write(` 3.
|
|
3345
|
+
process.stdout.write(` 3. See your record: a searchable view of every decision.
|
|
3188
3346
|
`);
|
|
3189
|
-
process.stdout.write(` ${dim(`
|
|
3347
|
+
process.stdout.write(` ${dim(`npx protect-mcp record`)}
|
|
3190
3348
|
`);
|
|
3191
|
-
process.stdout.write(` ${dim(`
|
|
3349
|
+
process.stdout.write(` ${dim(`Everything stays on this machine. Nothing is uploaded.`)}
|
|
3350
|
+
|
|
3192
3351
|
`);
|
|
3193
|
-
process.stdout.write(`
|
|
3352
|
+
process.stdout.write(` Prefer the terminal? ${dim(`npx protect-mcp receipts`)}, or ${dim("/verify-receipt")} in Claude Code.
|
|
3194
3353
|
|
|
3195
3354
|
`);
|
|
3196
3355
|
process.stdout.write(` 4. View policy suggestions:
|
|
@@ -3200,9 +3359,9 @@ ${bold("protect-mcp init-hooks")}
|
|
|
3200
3359
|
`);
|
|
3201
3360
|
process.stdout.write(`${bold("Key facts:")}
|
|
3202
3361
|
`);
|
|
3203
|
-
process.stdout.write(` \u2022 deny decisions are ${bold("AUTHORITATIVE")}
|
|
3362
|
+
process.stdout.write(` \u2022 deny decisions are ${bold("AUTHORITATIVE")}: they cannot be overridden
|
|
3204
3363
|
`);
|
|
3205
|
-
process.stdout.write(` \u2022 PostToolUse runs ${bold("async")}
|
|
3364
|
+
process.stdout.write(` \u2022 PostToolUse runs ${bold("async")}, so there is zero latency impact on tool execution
|
|
3206
3365
|
`);
|
|
3207
3366
|
process.stdout.write(` \u2022 Receipts are Ed25519-signed and append-only
|
|
3208
3367
|
`);
|
|
@@ -3221,15 +3380,7 @@ async function sendInstallTelemetry() {
|
|
|
3221
3380
|
if (existsSync(markerFile) || process.env.PROTECT_MCP_TELEMETRY === "off") {
|
|
3222
3381
|
return;
|
|
3223
3382
|
}
|
|
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
|
-
}
|
|
3383
|
+
const version = await pkgVersion();
|
|
3233
3384
|
const controller = new AbortController();
|
|
3234
3385
|
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
3235
3386
|
fetch("https://api.scopeblind.com/telemetry/install", {
|
|
@@ -3403,6 +3554,7 @@ async function main() {
|
|
|
3403
3554
|
sendInstallTelemetry().catch(() => {
|
|
3404
3555
|
});
|
|
3405
3556
|
const args = process.argv.slice(2);
|
|
3557
|
+
process.env.PROTECT_MCP_VERSION = process.env.PROTECT_MCP_VERSION || await pkgVersion();
|
|
3406
3558
|
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
3407
3559
|
printHelp();
|
|
3408
3560
|
process.exit(0);
|
|
@@ -3441,6 +3593,10 @@ async function main() {
|
|
|
3441
3593
|
await startHookServer({ port, policyPath: policyPath2, cedarDir: cedarDir2, enforce: enforce2, verbose: verbose2 });
|
|
3442
3594
|
return;
|
|
3443
3595
|
}
|
|
3596
|
+
if (args[0] === "record") {
|
|
3597
|
+
await handleRecord(args.slice(1));
|
|
3598
|
+
process.exit(0);
|
|
3599
|
+
}
|
|
3444
3600
|
if (args[0] === "init-hooks") {
|
|
3445
3601
|
await handleInitHooks(args.slice(1));
|
|
3446
3602
|
process.exit(0);
|
|
@@ -3629,7 +3785,7 @@ async function main() {
|
|
|
3629
3785
|
if (useHttp) {
|
|
3630
3786
|
const portIdx = args.indexOf("--port");
|
|
3631
3787
|
const httpPort = portIdx >= 0 && args[portIdx + 1] ? parseInt(args[portIdx + 1]) : 3e3;
|
|
3632
|
-
const { startHttpTransport } = await import("./http-transport-
|
|
3788
|
+
const { startHttpTransport } = await import("./http-transport-JBORN27G.mjs");
|
|
3633
3789
|
startHttpTransport({ port: httpPort, config, serverCommand: childCommand });
|
|
3634
3790
|
return;
|
|
3635
3791
|
}
|
|
@@ -3698,7 +3854,7 @@ async function handleDoctor() {
|
|
|
3698
3854
|
process.stdout.write(green2(`Node.js ${nodeVersion}
|
|
3699
3855
|
`));
|
|
3700
3856
|
} else {
|
|
3701
|
-
process.stdout.write(red2(`Node.js ${nodeVersion}
|
|
3857
|
+
process.stdout.write(red2(`Node.js ${nodeVersion}, requires >= 18
|
|
3702
3858
|
`));
|
|
3703
3859
|
issues++;
|
|
3704
3860
|
}
|
|
@@ -3709,7 +3865,7 @@ async function handleDoctor() {
|
|
|
3709
3865
|
if (config.signing?.private_key || config.signing?.key_file) {
|
|
3710
3866
|
process.stdout.write(green2("Signing keys configured\n"));
|
|
3711
3867
|
} else {
|
|
3712
|
-
process.stdout.write(yellow2("Config found but no signing keys
|
|
3868
|
+
process.stdout.write(yellow2("Config found but no signing keys. Run: protect-mcp init\n"));
|
|
3713
3869
|
issues++;
|
|
3714
3870
|
}
|
|
3715
3871
|
} catch {
|
|
@@ -3717,7 +3873,7 @@ async function handleDoctor() {
|
|
|
3717
3873
|
issues++;
|
|
3718
3874
|
}
|
|
3719
3875
|
} else {
|
|
3720
|
-
process.stdout.write(yellow2("No scopeblind.config.json
|
|
3876
|
+
process.stdout.write(yellow2("No scopeblind.config.json. Run: protect-mcp init\n"));
|
|
3721
3877
|
}
|
|
3722
3878
|
let policyFound = false;
|
|
3723
3879
|
for (const dir of ["cedar", "policies", "."]) {
|
|
@@ -3742,14 +3898,14 @@ async function handleDoctor() {
|
|
|
3742
3898
|
}
|
|
3743
3899
|
}
|
|
3744
3900
|
if (!policyFound) {
|
|
3745
|
-
process.stdout.write(yellow2("No policy files found
|
|
3901
|
+
process.stdout.write(yellow2("No policy files found, running in shadow mode (allow all)\n"));
|
|
3746
3902
|
}
|
|
3747
3903
|
try {
|
|
3748
3904
|
const cedarAvailable = await isCedarAvailable();
|
|
3749
3905
|
if (cedarAvailable) {
|
|
3750
3906
|
process.stdout.write(green2("Cedar WASM engine available\n"));
|
|
3751
3907
|
} else {
|
|
3752
|
-
process.stdout.write(dim2(" Cedar WASM not installed
|
|
3908
|
+
process.stdout.write(dim2(" Cedar WASM not installed. Install: npm install @cedar-policy/cedar-wasm\n"));
|
|
3753
3909
|
}
|
|
3754
3910
|
} catch {
|
|
3755
3911
|
process.stdout.write(dim2(" Cedar WASM not installed\n"));
|
|
@@ -3765,7 +3921,7 @@ async function handleDoctor() {
|
|
|
3765
3921
|
process.stdout.write(green2("Decision log exists\n"));
|
|
3766
3922
|
}
|
|
3767
3923
|
} else {
|
|
3768
|
-
process.stdout.write(dim2(" No decision log yet
|
|
3924
|
+
process.stdout.write(dim2(" No decision log yet, will be created on first tool call\n"));
|
|
3769
3925
|
}
|
|
3770
3926
|
if (existsSync(receiptFile)) {
|
|
3771
3927
|
try {
|
|
@@ -3780,17 +3936,17 @@ async function handleDoctor() {
|
|
|
3780
3936
|
execSync("npx @veritasacta/verify --version 2>/dev/null", { stdio: "pipe", timeout: 1e4 });
|
|
3781
3937
|
process.stdout.write(green2("Verifier available: @veritasacta/verify\n"));
|
|
3782
3938
|
} catch {
|
|
3783
|
-
process.stdout.write(dim2(" Verifier not cached
|
|
3939
|
+
process.stdout.write(dim2(" Verifier not cached. Install: npm install -g @veritasacta/verify\n"));
|
|
3784
3940
|
}
|
|
3785
3941
|
try {
|
|
3786
3942
|
const res = await fetch("https://api.scopeblind.com/health", { signal: AbortSignal.timeout(5e3) });
|
|
3787
3943
|
if (res.ok) {
|
|
3788
3944
|
process.stdout.write(green2("ScopeBlind API reachable\n"));
|
|
3789
3945
|
} else {
|
|
3790
|
-
process.stdout.write(yellow2("ScopeBlind API returned non-200
|
|
3946
|
+
process.stdout.write(yellow2("ScopeBlind API returned non-200, receipts will be stored locally\n"));
|
|
3791
3947
|
}
|
|
3792
3948
|
} catch {
|
|
3793
|
-
process.stdout.write(dim2(" ScopeBlind API not reachable
|
|
3949
|
+
process.stdout.write(dim2(" ScopeBlind API not reachable, offline mode (receipts stored locally)\n"));
|
|
3794
3950
|
}
|
|
3795
3951
|
process.stdout.write("\nRestraint self-test:\n");
|
|
3796
3952
|
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(" ")
|
package/dist/index.d.mts
CHANGED
|
@@ -1683,7 +1683,7 @@ declare const POLICY_PACKS: PolicyPack[];
|
|
|
1683
1683
|
declare function getPolicyPack(id: string): PolicyPack | undefined;
|
|
1684
1684
|
declare function policyPackIds(): string[];
|
|
1685
1685
|
|
|
1686
|
-
type ConnectorPilotId = 'github' | 'email-gmail' | 'filesystem-git' | 'slack-teams' | 'finance-pms';
|
|
1686
|
+
type ConnectorPilotId = 'github' | 'email-gmail' | 'filesystem-git' | 'slack-teams' | 'finance-pms' | 'nautilus-trader';
|
|
1687
1687
|
interface ConnectorEnvVar {
|
|
1688
1688
|
name: string;
|
|
1689
1689
|
required: boolean;
|
|
@@ -1696,6 +1696,11 @@ interface ConnectorAction {
|
|
|
1696
1696
|
mode: 'observe' | 'require_approval' | 'deny';
|
|
1697
1697
|
description: string;
|
|
1698
1698
|
}
|
|
1699
|
+
interface ConnectorArtifact {
|
|
1700
|
+
path: string;
|
|
1701
|
+
contents: string;
|
|
1702
|
+
executable?: boolean;
|
|
1703
|
+
}
|
|
1699
1704
|
interface ConnectorPilot {
|
|
1700
1705
|
id: ConnectorPilotId;
|
|
1701
1706
|
category: string;
|
|
@@ -1709,6 +1714,7 @@ interface ConnectorPilot {
|
|
|
1709
1714
|
setup: string[];
|
|
1710
1715
|
config: Record<string, unknown>;
|
|
1711
1716
|
cedar: string;
|
|
1717
|
+
artifacts?: ConnectorArtifact[];
|
|
1712
1718
|
}
|
|
1713
1719
|
interface InstalledConnectorPilot {
|
|
1714
1720
|
id: string;
|
|
@@ -2166,6 +2172,29 @@ interface ApprovalResult {
|
|
|
2166
2172
|
contextHash: string;
|
|
2167
2173
|
/** Timestamp of approval */
|
|
2168
2174
|
approvedAt: string;
|
|
2175
|
+
/** On failure, a machine-readable reason (e.g. 'invalid_signature'). */
|
|
2176
|
+
reason?: string;
|
|
2177
|
+
}
|
|
2178
|
+
/**
|
|
2179
|
+
* The registered credential public key, extracted from the COSE_Key at
|
|
2180
|
+
* registration. ES256 keys are an uncompressed P-256 point; EdDSA keys are a
|
|
2181
|
+
* 32-byte Ed25519 public key.
|
|
2182
|
+
*/
|
|
2183
|
+
interface CredentialPublicKey {
|
|
2184
|
+
/** COSE algorithm: -7 = ES256 (P-256 / ECDSA), -8 = EdDSA (Ed25519). */
|
|
2185
|
+
alg: -7 | -8;
|
|
2186
|
+
/** Public key, hex. ES256: 65-byte uncompressed point (0x04 || x || y). EdDSA: 32-byte key. */
|
|
2187
|
+
publicKeyHex: string;
|
|
2188
|
+
}
|
|
2189
|
+
interface VerifyAssertionOptions {
|
|
2190
|
+
/** Allowed origin(s) the assertion must come from, e.g. 'https://app.scopeblind.com'. Defaults to https://<rpId>. */
|
|
2191
|
+
expectedOrigin?: string | string[];
|
|
2192
|
+
/** Require the UV (user-verified / biometric or PIN) flag. Default true. */
|
|
2193
|
+
requireUserVerification?: boolean;
|
|
2194
|
+
/** The signCount stored from the previous assertion; a non-increasing counter signals a cloned authenticator. */
|
|
2195
|
+
prevSignCount?: number;
|
|
2196
|
+
/** Override 'now' (ms) for testing. */
|
|
2197
|
+
now?: number;
|
|
2169
2198
|
}
|
|
2170
2199
|
/**
|
|
2171
2200
|
* Create a WebAuthn challenge for approving a tool call.
|
|
@@ -2202,17 +2231,22 @@ declare function toCredentialRequestOptions(challenge: ApprovalChallenge, allowC
|
|
|
2202
2231
|
};
|
|
2203
2232
|
};
|
|
2204
2233
|
/**
|
|
2205
|
-
* Verify a WebAuthn assertion
|
|
2206
|
-
*
|
|
2207
|
-
*
|
|
2208
|
-
*
|
|
2209
|
-
*
|
|
2210
|
-
*
|
|
2211
|
-
*
|
|
2212
|
-
*
|
|
2213
|
-
*
|
|
2214
|
-
|
|
2215
|
-
|
|
2234
|
+
* Verify a WebAuthn assertion: full, fail-closed verification of a passkey or
|
|
2235
|
+
* security-key co-sign. This proves a SPECIFIC human authorized a SPECIFIC
|
|
2236
|
+
* action with a hardware-held key the host operator cannot exfiltrate. It
|
|
2237
|
+
* checks, in order: challenge freshness; clientDataJSON type, challenge, and
|
|
2238
|
+
* origin; the rpIdHash; the UP (and, by default, UV) flags; the authenticator
|
|
2239
|
+
* signature over authenticatorData || SHA-256(clientDataJSON) using the
|
|
2240
|
+
* registered credential public key (ES256 or EdDSA); and signCount monotonicity
|
|
2241
|
+
* (clone detection) when a previous count is supplied. Any failure returns
|
|
2242
|
+
* valid:false with a reason; nothing is trusted on a partial check.
|
|
2243
|
+
*
|
|
2244
|
+
* @param challenge - the original challenge
|
|
2245
|
+
* @param assertion - the assertion from navigator.credentials.get()
|
|
2246
|
+
* @param credentialPublicKey - the registered public key for assertion.credentialId
|
|
2247
|
+
* @param opts - origin / UV / signCount / clock options
|
|
2248
|
+
*/
|
|
2249
|
+
declare function verifyApprovalAssertion(challenge: ApprovalChallenge, assertion: ApprovalAssertion, credentialPublicKey?: CredentialPublicKey, opts?: VerifyAssertionOptions): ApprovalResult;
|
|
2216
2250
|
/**
|
|
2217
2251
|
* Create the approval receipt payload for embedding in an Acta receipt.
|
|
2218
2252
|
*
|