protect-mcp 0.9.2 → 0.9.4
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/CHANGELOG.md +49 -0
- package/README.md +41 -0
- package/dist/{chunk-CXW2EIRM.mjs → chunk-7MHK5RF4.mjs} +2 -2
- package/dist/{chunk-744JMCY4.mjs → chunk-H332ZNJ6.mjs} +2 -2
- package/dist/{chunk-GHR65WVD.mjs → chunk-PB3TC7E3.mjs} +1 -1
- package/dist/{chunk-KMNXHGGT.mjs → chunk-WWPQNIVF.mjs} +35 -2
- package/dist/{chunk-IDUH2O4Q.mjs → chunk-XLJUZ4WO.mjs} +7 -1
- package/dist/{claim-TUDH2WPB.mjs → claim-RIXFOQM7.mjs} +168 -16
- package/dist/cli.js +471 -33
- package/dist/cli.mjs +268 -21
- package/dist/hook-server.js +42 -3
- package/dist/hook-server.mjs +3 -3
- package/dist/{http-transport-D7C64PIA.mjs → http-transport-HLSMVBI6.mjs} +2 -2
- package/dist/index.d.mts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +42 -3
- package/dist/index.mjs +5 -5
- package/package.json +1 -1
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-7MHK5RF4.mjs";
|
|
15
15
|
import {
|
|
16
16
|
ProtectGateway,
|
|
17
17
|
validateCredentials
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-PB3TC7E3.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-XLJUZ4WO.mjs";
|
|
30
30
|
import "./chunk-PQJP2ZCI.mjs";
|
|
31
31
|
|
|
32
32
|
// src/cli.ts
|
|
@@ -100,8 +100,12 @@ Commands:
|
|
|
100
100
|
digest Generate a human-readable summary of agent activity
|
|
101
101
|
receipts Show recent persisted signed receipts
|
|
102
102
|
record Open a local, searchable view of your record in the browser
|
|
103
|
-
claim Attest a signed, position-blind claim over your record (e.g. no egress
|
|
104
|
-
|
|
103
|
+
claim Attest a signed, position-blind claim over your record (e.g. no egress,
|
|
104
|
+
no payment, every payment under a cap)
|
|
105
|
+
verify-claim Verify a claim attestation offline (signature + predicate + commitment
|
|
106
|
+
+ the anchor sidecar and issuer identity when present)
|
|
107
|
+
anchor-record Checkpoint the record's Merkle root + count into the public log
|
|
108
|
+
(heartbeat-friendly: skips when unchanged; only hashes leave)
|
|
105
109
|
bundle Export an offline-verifiable audit bundle
|
|
106
110
|
|
|
107
111
|
Examples:
|
|
@@ -2033,6 +2037,16 @@ Start the gate with ${bold("npx protect-mcp serve")}, use your agent, then run t
|
|
|
2033
2037
|
return null;
|
|
2034
2038
|
}
|
|
2035
2039
|
}).filter((x) => x !== null).map(mapRecordEntry);
|
|
2040
|
+
let pinnedKey = "";
|
|
2041
|
+
let pinnedKid = "";
|
|
2042
|
+
try {
|
|
2043
|
+
const kd = JSON.parse(readFileSync(join(dir, "keys", "gateway.json"), "utf-8"));
|
|
2044
|
+
if (kd && typeof kd.publicKey === "string" && /^[0-9a-f]{64}$/i.test(kd.publicKey)) {
|
|
2045
|
+
pinnedKey = kd.publicKey;
|
|
2046
|
+
pinnedKid = typeof kd.kid === "string" ? kd.kid : "";
|
|
2047
|
+
}
|
|
2048
|
+
} catch {
|
|
2049
|
+
}
|
|
2036
2050
|
const openTarget = (target) => {
|
|
2037
2051
|
if (argv.includes("--no-open")) return;
|
|
2038
2052
|
const platform = process.platform;
|
|
@@ -2060,7 +2074,7 @@ Start the gate with ${bold("npx protect-mcp serve")}, use your agent, then run t
|
|
|
2060
2074
|
res.end(JSON.stringify({ recs: recs2, signed: f === recPath }));
|
|
2061
2075
|
return;
|
|
2062
2076
|
}
|
|
2063
|
-
const meta2 = { file: chosen, signed: pick() === recPath, count: 0, live: true };
|
|
2077
|
+
const meta2 = { file: chosen, signed: pick() === recPath, count: 0, live: true, pinned_key: pinnedKey, pinned_kid: pinnedKid };
|
|
2064
2078
|
const page = RECORD_HTML.replace("__DATA__", () => "[]").replace("__META__", () => JSON.stringify(meta2));
|
|
2065
2079
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
2066
2080
|
res.end(page);
|
|
@@ -2085,7 +2099,7 @@ ${bold("\u{1F6E1}\uFE0F Your record")} ${dim("\xB7")} live at ${url}
|
|
|
2085
2099
|
return;
|
|
2086
2100
|
}
|
|
2087
2101
|
const recs = readRecs(chosen);
|
|
2088
|
-
const meta = { file: chosen, signed: chosen === recPath, count: recs.length, live: false };
|
|
2102
|
+
const meta = { file: chosen, signed: chosen === recPath, count: recs.length, live: false, pinned_key: pinnedKey, pinned_kid: pinnedKid };
|
|
2089
2103
|
const html = RECORD_HTML.replace("__DATA__", () => JSON.stringify(recs)).replace("__META__", () => JSON.stringify(meta));
|
|
2090
2104
|
const out = join(osMod.tmpdir(), "protect-mcp-record-" + Date.now() + ".html");
|
|
2091
2105
|
writeFileSync(out, html);
|
|
@@ -2151,6 +2165,9 @@ input{width:100%;padding:10px 13px;border:1px solid var(--line);border-radius:9p
|
|
|
2151
2165
|
.badge{font-size:10.5px;font-weight:600;padding:1px 7px;border-radius:100px}
|
|
2152
2166
|
.badge.sgn{background:var(--gb);color:var(--g)}
|
|
2153
2167
|
.badge.log{background:var(--paper);color:var(--faint);border:1px solid var(--line)}
|
|
2168
|
+
.badge.vbad{background:#fbecec;color:#b3382f;border:1px solid #edc6c2}
|
|
2169
|
+
.badge.vfor{background:#fbf3df;color:#8a6d1a;border:1px solid #e8d8ae}
|
|
2170
|
+
.stat .badk{color:#b3382f;font-weight:680}.stat .warnk{color:#8a6d1a}.stat .dim2{color:var(--faint);font-weight:400}
|
|
2154
2171
|
.dg{font-size:10.5px;color:var(--faint);font-family:ui-monospace,Menlo,monospace}
|
|
2155
2172
|
.when{margin-left:auto;font-size:12px;color:var(--faint);font-family:ui-monospace,Menlo,monospace}
|
|
2156
2173
|
.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}
|
|
@@ -2197,6 +2214,30 @@ input{width:100%;padding:10px 13px;border:1px solid var(--line);border-radius:9p
|
|
|
2197
2214
|
</div>
|
|
2198
2215
|
<script>
|
|
2199
2216
|
var RECORDS=__DATA__;var META=__META__;var Q="",ACT={},VIEW="list",OPEN={};var NL=String.fromCharCode(10);
|
|
2217
|
+
// In-browser signature verification. Mirrors @veritasacta/artifacts exactly:
|
|
2218
|
+
// preimage = JCS-style canonical JSON (sorted keys) of the receipt minus its
|
|
2219
|
+
// signature, verified with WebCrypto Ed25519. Pinned key (your keys/gateway.json
|
|
2220
|
+
// public half, injected by the CLI) = authenticity; key embedded in the receipt
|
|
2221
|
+
// payload (0.9.3+) = self-consistency. Everything runs locally.
|
|
2222
|
+
var VSTATE={},VDONE=false,VBUSY=false,VUNSUP=false;
|
|
2223
|
+
function vkey(r){return "row:"+(r.id||"")+"|"+(r.ts||"")}
|
|
2224
|
+
function hexb(h){h=String(h||"");var a=new Uint8Array(h.length>>1);for(var i=0;i<a.length;i++)a[i]=parseInt(h.substr(i*2,2),16);return a}
|
|
2225
|
+
function canon(v){return JSON.stringify(v,function(k,x){if(x&&typeof x==="object"&&!Array.isArray(x)){var s={},ks=Object.keys(x).sort();for(var i=0;i<ks.length;i++)s[ks[i]]=x[ks[i]];return s}return x})}
|
|
2226
|
+
async function edv(sig,msg,pub){var key=await crypto.subtle.importKey("raw",hexb(pub),{name:"Ed25519"},false,["verify"]);return crypto.subtle.verify({name:"Ed25519"},key,hexb(sig),msg)}
|
|
2227
|
+
async function verifyRow(r){var raw=r.raw;if(!raw||typeof raw.signature!=="string")return"unsigned";
|
|
2228
|
+
var rest={},k;for(k in raw)if(k!=="signature")rest[k]=raw[k];
|
|
2229
|
+
var msg=new TextEncoder().encode(canon(rest));
|
|
2230
|
+
var pin=String(META.pinned_key||"").toLowerCase();
|
|
2231
|
+
var emb=String((raw.payload&&raw.payload.public_key)||raw.public_key||"").toLowerCase();
|
|
2232
|
+
if(!/^[0-9a-f]{64}$/.test(emb))emb="";
|
|
2233
|
+
if(pin){if(await edv(raw.signature,msg,pin))return"ok";if(emb&&emb!==pin&&await edv(raw.signature,msg,emb))return"foreign";return"bad"}
|
|
2234
|
+
if(emb)return(await edv(raw.signature,msg,emb))?"ok":"bad";
|
|
2235
|
+
return"nokey"}
|
|
2236
|
+
function vsum(){var s={ok:0,bad:0,foreign:0,nokey:0};RECORDS.forEach(function(r){if(!r.signed)return;var v=VSTATE[vkey(r)];if(v&&s[v]!==undefined)s[v]++});return s}
|
|
2237
|
+
async function kickVerify(){if(VBUSY||VUNSUP||!(window.crypto&&crypto.subtle))return;VBUSY=true;
|
|
2238
|
+
try{var rows=RECORDS.slice(0,1500);for(var i=0;i<rows.length;i++){var r=rows[i],kk=vkey(r);if(VSTATE[kk])continue;
|
|
2239
|
+
try{VSTATE[kk]=await verifyRow(r)}catch(e){if(e&&e.name==="NotSupportedError"){VUNSUP=true;break}VSTATE[kk]="bad"}}}
|
|
2240
|
+
finally{VDONE=true;VBUSY=false;render()}}
|
|
2200
2241
|
function esc(s){return String(s).replace(/[&<>"]/g,function(c){return{"&":"&","<":"<",">":">",'"':"""}[c]})}
|
|
2201
2242
|
function vlabel(v){return v==="allowed"?"Allowed":v==="held"?"Held":"Blocked"}
|
|
2202
2243
|
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"})}
|
|
@@ -2210,8 +2251,9 @@ function exportJsonl(){var rows=filtered();if(!rows.length)return;var lines=rows
|
|
|
2210
2251
|
function exportMd(){var rows=filtered();if(!rows.length)return;var c=counts(rows);var head=["# Agent decision record","",rows.length+" decisions from "+META.file,c.allowed+" allowed, "+c.held+" held, "+c.blocked+" blocked, "+c.signed+" signed.","","Generated locally by protect-mcp. These are signed receipts; verify offline with npx @veritasacta/verify (our code removed).","","| When | Decision | Tool | Reason | Hook | Signed |","|---|---|---|---|---|---|"];var body=rows.slice(0,3000).map(function(r){return "| "+(r.ts||"")+" | "+vlabel(r.verdict)+" | "+String(r.tool||"").replace(/\\|/g,"/")+" | "+String(r.reason||"").replace(/\\|/g,"/")+" | "+(r.hook||"")+" | "+(r.signed?"yes":"no")+" |"});dl("protect-mcp-record-"+stamp()+".md",head.concat(body).join(NL)+NL,"text/markdown")}
|
|
2211
2252
|
function copyAttest(){var a=document.getElementById("attest");var cmd=a?a.getAttribute("data-cmd"):"";try{navigator.clipboard&&cmd&&navigator.clipboard.writeText(cmd)}catch(e){}var b=document.getElementById("cpa");if(b){var t=b.textContent;b.textContent="Copied";setTimeout(function(){b.textContent=t},1200)}}
|
|
2212
2253
|
function copyVerify(){var cmd="npx @veritasacta/verify";try{navigator.clipboard&&navigator.clipboard.writeText(cmd)}catch(e){}var b=document.getElementById("cpv");if(b){var t=b.textContent;b.textContent="Copied";setTimeout(function(){b.textContent=t},1200)}}
|
|
2213
|
-
function renderStats(){var c=counts(RECORDS);var p=[];p.push('<span class="stat"><b>'+RECORDS.length+'</b> decisions</span>');p.push('<span class="stat"><span class="dot g"></span>'+c.allowed+' allowed</span>');if(c.held)p.push('<span class="stat"><span class="dot a"></span>'+c.held+' held</span>');p.push('<span class="stat"><span class="dot r"></span>'+c.blocked+' blocked</span>');
|
|
2214
|
-
|
|
2254
|
+
function renderStats(){var c=counts(RECORDS);var p=[];p.push('<span class="stat"><b>'+RECORDS.length+'</b> decisions</span>');p.push('<span class="stat"><span class="dot g"></span>'+c.allowed+' allowed</span>');if(c.held)p.push('<span class="stat"><span class="dot a"></span>'+c.held+' held</span>');p.push('<span class="stat"><span class="dot r"></span>'+c.blocked+' blocked</span>');var st;if(!c.signed){st='0 signed, verifiable offline'}else if(VUNSUP||!(window.crypto&&crypto.subtle)){st=c.signed+' signed, verifiable offline <span class="dim2">(in-browser check unavailable here; run npx protect-mcp receipts)</span>'}else if(!VDONE){st=c.signed+' signed \xB7 verifying in your browser\u2026'}else{var s=vsum();st=s.ok+' of '+c.signed+' signatures verified in your browser';if(s.foreign)st+=' <span class="warnk">\xB7 '+s.foreign+' signed by an unpinned key</span>';if(s.bad)st+=' <span class="badk">\xB7 '+s.bad+' INVALID</span>';if(s.nokey)st+=' <span class="dim2">\xB7 '+s.nokey+' need a key to check</span>'}
|
|
2255
|
+
p.push('<span class="stat sig">'+st+'</span>');document.getElementById("stats").innerHTML=p.join("")}
|
|
2256
|
+
function renderList(rows){var html="";rows.slice(0,800).forEach(function(r){var vs=VSTATE[vkey(r)];var sig=!r.signed?'<span class="badge log">log</span>':vs==="ok"?'<span class="badge sgn">\u2713 verified</span>':vs==="bad"?'<span class="badge vbad">\u2717 invalid signature</span>':vs==="foreign"?'<span class="badge vfor">signed \xB7 unpinned key</span>':'<span class="badge sgn">signed</span>';var dg=r.digest?'<span class="dg">'+esc(String(r.digest).slice(0,10))+'</span>':'';var ct=(r.caps||[]).map(function(c){return '<span class="cap">'+esc(c)+'</span>'}).join('');var rk="row:"+(r.id||"")+"|"+(r.ts||"");html+='<div class="row '+r.verdict+(OPEN[rk]?" open":"")+'" data-k="'+esc(rk)+'"><div class="top"><span class="pill '+r.verdict+'">'+vlabel(r.verdict)+"</span><b>"+esc(r.tool)+'</b><span class="tag">'+esc(r.reason)+"</span>"+ct+(r.hook?'<span class="tag">'+esc(r.hook)+"</span>":"")+sig+dg+'<span class="when">'+esc(when(r.ts))+'</span></div><div class="det">'+esc(JSON.stringify(r.raw||r,null,2))+"</div></div>"});document.getElementById("list").innerHTML=html||'<p style="color:#8a837a">No records match.</p>';}
|
|
2215
2257
|
function isLifecycle(r){var h=r.hook||"";return h==="SessionStart"||h==="SessionEnd"||h==="Stop"||h==="SubagentStart"||h==="SubagentStop"||h==="TaskCreated"||h==="TaskCompleted"||h==="ConfigChange"||h==="Notification"||h==="PreCompact";}
|
|
2216
2258
|
function buildTree(rows){var ags={},order=[];rows.forEach(function(r){var a=r.agent||"main agent";if(!ags[a]){ags[a]={name:a,byId:{},items:[],caps:{},blocked:0,actions:0};order.push(a);}var g=ags[a];(r.caps||[]).forEach(function(c){g.caps[c]=(g.caps[c]||0)+1;});if(isLifecycle(r)){g.items.push({t:"e",ts:r.ts,r:r});return;}var id=r.id||("_"+r.ts);var n=g.byId[id];if(!n){n={t:"a",id:id,tool:r.tool,verdict:r.verdict,caps:(r.caps||[]).slice(),ts:r.ts,dur:0,signed:!!r.signed,raw:r.raw};g.byId[id]=n;g.items.push(n);g.actions++;}if(r.hook==="PostToolUse"){if(r.dur)n.dur=r.dur;if(!n.raw)n.raw=r.raw;}else{n.verdict=r.verdict;if((r.caps||[]).length)n.caps=r.caps.slice();n.raw=r.raw;n.ts=r.ts;}if(r.signed)n.signed=true;});order.forEach(function(a){var g=ags[a];g.blocked=g.items.filter(function(it){return it.t==="a"&&it.verdict==="blocked";}).length;g.items.sort(function(x,y){return (x.ts<y.ts)?-1:1;});});return order.map(function(a){return ags[a];});}
|
|
2217
2259
|
function renderTree(ags){if(!ags.length){document.getElementById("list").innerHTML='<p style="color:#8a837a">No records match.</p>';return;}var html="",N=0;ags.forEach(function(g,gi){var capstr=Object.keys(g.caps).sort(function(a,b){return g.caps[b]-g.caps[a];}).slice(0,5).map(function(c){return '<span class="cap">'+esc(c)+'</span>';}).join('');var ak="ag:"+g.name;var op=(OPEN.hasOwnProperty(ak)?OPEN[ak]:(ags.length===1||gi===0))?" open":"";html+='<div class="agent'+op+'" data-k="'+esc(ak)+'"><div class="ahead"><span class="atwist">\u25B8</span><b>'+esc(g.name)+'</b><span class="acount">'+g.actions+' action'+(g.actions===1?'':'s')+'</span>'+(g.blocked?'<span class="badge blk">'+g.blocked+' blocked</span>':'')+capstr+'</div><div class="akids">';g.items.forEach(function(it){if(N++>1500)return;if(it.t==="e"){var r=it.r;html+='<div class="ev"><span class="evdot"></span>'+esc(r.hook||r.tool)+' <span class="evre">'+esc(r.reason)+'</span><span class="when">'+esc(when(r.ts))+'</span></div>';}else{var ct=(it.caps||[]).map(function(c){return '<span class="cap">'+esc(c)+'</span>';}).join('');var dur=it.dur?'<span class="dg">'+it.dur+'ms</span>':'';var ik="act:"+it.id;html+='<div class="act '+it.verdict+(OPEN[ik]?" open":"")+'" data-k="'+esc(ik)+'"><span class="pill '+it.verdict+'">'+vlabel(it.verdict)+'</span><b>'+esc(it.tool)+'</b>'+ct+(it.signed?'<span class="badge sgn">signed</span>':'')+dur+'<span class="when">'+esc(when(it.ts))+'</span><div class="det">'+esc(JSON.stringify(it.raw||{},null,2))+'</div></div>';}});html+='</div></div>';});if(N>1500)html+='<p style="color:#8a837a;font-size:12px;margin-top:10px">Showing the first 1500 items. Search or pick a facet to narrow.</p>';document.getElementById("list").innerHTML=html;}
|
|
@@ -2229,31 +2271,34 @@ if(VIEW==="tree"){renderTree(buildTree(rows));}else{renderList(rows);}}
|
|
|
2229
2271
|
document.getElementById("q").addEventListener("input",function(e){Q=e.target.value.toLowerCase().trim();render()});
|
|
2230
2272
|
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()});
|
|
2231
2273
|
document.getElementById("list").addEventListener("click",function(e){var ah=e.target.closest(".ahead");if(ah){var ag=ah.parentNode;ag.classList.toggle("open");var ak=ag.getAttribute("data-k");if(ak)OPEN[ak]=ag.classList.contains("open");return;}var el=e.target.closest(".act")||e.target.closest(".row");if(el){el.classList.toggle("open");var k=el.getAttribute("data-k");if(k)OPEN[k]=el.classList.contains("open");}});
|
|
2232
|
-
render();
|
|
2233
|
-
if(META.live){var poll=function(){fetch('/data',{cache:'no-store'}).then(function(r){return r.json()}).then(function(d){var nr=d.recs||[];var changed=nr.length!==RECORDS.length;RECORDS=nr;META.count=RECORDS.length;if(typeof d.signed==='boolean')META.signed=d.signed;if(changed)render()}).catch(function(){})};poll();setInterval(poll,2000);}
|
|
2274
|
+
render();kickVerify();
|
|
2275
|
+
if(META.live){var poll=function(){fetch('/data',{cache:'no-store'}).then(function(r){return r.json()}).then(function(d){var nr=d.recs||[];var changed=nr.length!==RECORDS.length;RECORDS=nr;META.count=RECORDS.length;if(typeof d.signed==='boolean')META.signed=d.signed;if(changed){render();kickVerify()}}).catch(function(){})};poll();setInterval(poll,2000);}
|
|
2234
2276
|
</script></body></html>`;
|
|
2235
2277
|
async function handleClaim(argv) {
|
|
2236
2278
|
const { readFileSync, existsSync, writeFileSync } = await import("fs");
|
|
2237
2279
|
const { join } = await import("path");
|
|
2238
|
-
const { buildClaim } = await import("./claim-
|
|
2280
|
+
const { buildClaim } = await import("./claim-RIXFOQM7.mjs");
|
|
2239
2281
|
let dir = process.cwd();
|
|
2240
2282
|
const di = argv.indexOf("--dir");
|
|
2241
2283
|
if (di !== -1 && argv[di + 1]) dir = argv[di + 1];
|
|
2242
2284
|
let predicate = null;
|
|
2243
|
-
const noIdx = argv.indexOf("--no"), onlyIdx = argv.indexOf("--only"), nvIdx = argv.indexOf("--no-verdict"), cvIdx = argv.indexOf("--count");
|
|
2285
|
+
const noIdx = argv.indexOf("--no"), onlyIdx = argv.indexOf("--only"), nvIdx = argv.indexOf("--no-verdict"), cvIdx = argv.indexOf("--count"), puIdx = argv.indexOf("--payment-under");
|
|
2244
2286
|
if (noIdx !== -1 && argv[noIdx + 1]) predicate = { kind: "no_capability", capability: argv[noIdx + 1] };
|
|
2245
2287
|
else if (onlyIdx !== -1 && argv[onlyIdx + 1]) predicate = { kind: "only_capabilities", capabilities: argv[onlyIdx + 1].split(",").map((s) => s.trim()).filter(Boolean) };
|
|
2246
2288
|
else if (nvIdx !== -1 && argv[nvIdx + 1]) predicate = { kind: "no_verdict", verdict: argv[nvIdx + 1] };
|
|
2247
2289
|
else if (cvIdx !== -1 && argv[cvIdx + 1]) predicate = { kind: "count_verdict", verdict: argv[cvIdx + 1] };
|
|
2290
|
+
else if (puIdx !== -1 && argv[puIdx + 1] && isFinite(parseFloat(argv[puIdx + 1]))) predicate = { kind: "payment_under", cap: parseFloat(argv[puIdx + 1]) };
|
|
2248
2291
|
if (!predicate) {
|
|
2249
2292
|
process.stderr.write(`
|
|
2250
2293
|
${bold("protect-mcp claim")}
|
|
2251
2294
|
|
|
2252
2295
|
Attest a signed, position-blind claim over your record:
|
|
2253
|
-
--no <capability> no action used it, e.g. ${dim("--no net.egress")}
|
|
2296
|
+
--no <capability> no action used it, e.g. ${dim("--no net.egress")} or ${dim("--no payment")}
|
|
2254
2297
|
--only <c1,c2,...> all actions confined to these capabilities
|
|
2255
2298
|
--no-verdict <verdict> e.g. ${dim("--no-verdict blocked")}
|
|
2256
2299
|
--count <verdict> how many, e.g. ${dim("--count blocked")}
|
|
2300
|
+
--payment-under <cap> every agent payment stayed under the cap (amounts the
|
|
2301
|
+
gate could not read count as OVER, so this cannot lie)
|
|
2257
2302
|
--anchor also record the claim digest in the public append-only
|
|
2258
2303
|
log so a counterparty can trust it is complete (only the
|
|
2259
2304
|
hash is sent; your record stays local)
|
|
@@ -2336,7 +2381,7 @@ ${bold("\u{1F6E1}\uFE0F Signed claim")}
|
|
|
2336
2381
|
process.stdout.write(` Hand it to anyone. They verify offline: ${bold("npx protect-mcp verify-claim " + out)}
|
|
2337
2382
|
`);
|
|
2338
2383
|
if (argv.indexOf("--anchor") !== -1) {
|
|
2339
|
-
const { anchorClaim } = await import("./claim-
|
|
2384
|
+
const { anchorClaim } = await import("./claim-RIXFOQM7.mjs");
|
|
2340
2385
|
const li = argv.indexOf("--log");
|
|
2341
2386
|
const logBase = li !== -1 && argv[li + 1] ? argv[li + 1] : void 0;
|
|
2342
2387
|
process.stdout.write(`
|
|
@@ -2356,8 +2401,18 @@ ${bold("\u{1F6E1}\uFE0F Signed claim")}
|
|
|
2356
2401
|
`);
|
|
2357
2402
|
process.stdout.write(` ${dim("Anchor record written to " + sidecar + ". Only the digest was sent; your record stayed local.")}
|
|
2358
2403
|
`);
|
|
2359
|
-
|
|
2404
|
+
const { lookupPinnedIdentity } = await import("./claim-RIXFOQM7.mjs");
|
|
2405
|
+
const who = await lookupPinnedIdentity(key.publicKey, { log: logBase });
|
|
2406
|
+
if (who && who.found && !who.revoked) {
|
|
2407
|
+
process.stdout.write(` ${green("Identity:")} anchored as ${bold(who.name || who.slug || "enrolled org")} ${dim("(key pinned in the ScopeBlind directory" + (who.enrolled_at ? ", enrolled " + who.enrolled_at.slice(0, 10) : "") + ")")}
|
|
2360
2408
|
`);
|
|
2409
|
+
} else if (who && who.found && who.revoked) {
|
|
2410
|
+
process.stdout.write(` ${red("Identity: this key is REVOKED in the ScopeBlind directory.")}
|
|
2411
|
+
`);
|
|
2412
|
+
} else {
|
|
2413
|
+
process.stdout.write(` ${dim("Identity: anonymous (key not enrolled). To anchor as a named org a counterparty can pin, see")} ${bold("scopeblind.com/enroll")}
|
|
2414
|
+
`);
|
|
2415
|
+
}
|
|
2361
2416
|
} else {
|
|
2362
2417
|
process.stdout.write(` ${yellow("Anchor skipped")} ${dim("(" + (res.error || "unavailable") + "). The claim above is complete and verifiable offline without it.")}
|
|
2363
2418
|
`);
|
|
@@ -2367,9 +2422,135 @@ ${bold("\u{1F6E1}\uFE0F Signed claim")}
|
|
|
2367
2422
|
`);
|
|
2368
2423
|
process.exit(0);
|
|
2369
2424
|
}
|
|
2425
|
+
async function handleAnchorRecord(argv) {
|
|
2426
|
+
const { readFileSync, existsSync, appendFileSync } = await import("fs");
|
|
2427
|
+
const { join } = await import("path");
|
|
2428
|
+
const { anchorRecordCheckpoint, buildRecordCheckpoint, lookupPinnedIdentity } = await import("./claim-RIXFOQM7.mjs");
|
|
2429
|
+
let dir = process.cwd();
|
|
2430
|
+
const di = argv.indexOf("--dir");
|
|
2431
|
+
if (di !== -1 && argv[di + 1]) dir = argv[di + 1];
|
|
2432
|
+
const li = argv.indexOf("--log");
|
|
2433
|
+
const logBase = li !== -1 && argv[li + 1] ? argv[li + 1] : void 0;
|
|
2434
|
+
const keyPath = join(dir, "keys", "gateway.json");
|
|
2435
|
+
if (!existsSync(keyPath)) {
|
|
2436
|
+
process.stderr.write(`
|
|
2437
|
+
${bold("protect-mcp anchor-record")}
|
|
2438
|
+
|
|
2439
|
+
No signing key at ${keyPath}. A checkpoint must be signed. Run ${bold("npx protect-mcp init")} first.
|
|
2440
|
+
|
|
2441
|
+
`);
|
|
2442
|
+
process.exit(1);
|
|
2443
|
+
return;
|
|
2444
|
+
}
|
|
2445
|
+
let key;
|
|
2446
|
+
try {
|
|
2447
|
+
key = JSON.parse(readFileSync(keyPath, "utf-8"));
|
|
2448
|
+
} catch {
|
|
2449
|
+
process.stderr.write(`
|
|
2450
|
+
protect-mcp anchor-record: ${keyPath} is not valid JSON.
|
|
2451
|
+
|
|
2452
|
+
`);
|
|
2453
|
+
process.exit(1);
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
if (!key.privateKey || !key.publicKey) {
|
|
2457
|
+
process.stderr.write(`
|
|
2458
|
+
protect-mcp anchor-record: ${keyPath} is missing privateKey/publicKey.
|
|
2459
|
+
|
|
2460
|
+
`);
|
|
2461
|
+
process.exit(1);
|
|
2462
|
+
return;
|
|
2463
|
+
}
|
|
2464
|
+
const recPath = join(dir, ".protect-mcp-receipts.jsonl");
|
|
2465
|
+
if (!existsSync(recPath)) {
|
|
2466
|
+
process.stderr.write(`
|
|
2467
|
+
${bold("protect-mcp anchor-record")}
|
|
2468
|
+
|
|
2469
|
+
No signed receipts in ${dir}. Run the gate with signing on, then try again.
|
|
2470
|
+
|
|
2471
|
+
`);
|
|
2472
|
+
process.exit(0);
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2475
|
+
const receipts = readFileSync(recPath, "utf-8").split(/\r?\n/).map((l) => l.trim()).filter(Boolean).map((l) => {
|
|
2476
|
+
try {
|
|
2477
|
+
return JSON.parse(l);
|
|
2478
|
+
} catch {
|
|
2479
|
+
return null;
|
|
2480
|
+
}
|
|
2481
|
+
}).filter((x) => x !== null);
|
|
2482
|
+
if (!receipts.length) {
|
|
2483
|
+
process.stderr.write(`
|
|
2484
|
+
protect-mcp anchor-record: no readable receipts in ${recPath}.
|
|
2485
|
+
|
|
2486
|
+
`);
|
|
2487
|
+
process.exit(0);
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2490
|
+
const claimKey = { privateKey: key.privateKey, publicKey: key.publicKey, kid: key.kid || "gateway", issuer: "protect-mcp" };
|
|
2491
|
+
const historyPath = join(dir, ".protect-mcp-anchors.jsonl");
|
|
2492
|
+
const preview = buildRecordCheckpoint(receipts, claimKey, "preview");
|
|
2493
|
+
if (!argv.includes("--force") && existsSync(historyPath)) {
|
|
2494
|
+
const lines = readFileSync(historyPath, "utf-8").split(/\r?\n/).filter(Boolean);
|
|
2495
|
+
const last = lines.length ? (() => {
|
|
2496
|
+
try {
|
|
2497
|
+
return JSON.parse(lines[lines.length - 1]);
|
|
2498
|
+
} catch {
|
|
2499
|
+
return null;
|
|
2500
|
+
}
|
|
2501
|
+
})() : null;
|
|
2502
|
+
if (last && last.record_root === preview.record_root && last.total === preview.total) {
|
|
2503
|
+
process.stdout.write(`
|
|
2504
|
+
${bold("\u{1F6E1}\uFE0F Record checkpoint")}
|
|
2505
|
+
`);
|
|
2506
|
+
process.stdout.write(` Unchanged since entry ${bold("#" + last.seq)} ${dim("(" + last.total + " receipts, anchored " + (last.anchored_at || "") + ")")}. Nothing new to anchor.
|
|
2507
|
+
`);
|
|
2508
|
+
process.stdout.write(` ${dim("Use --force to re-anchor anyway.")}
|
|
2509
|
+
|
|
2510
|
+
`);
|
|
2511
|
+
process.exit(0);
|
|
2512
|
+
return;
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
const res = await anchorRecordCheckpoint(receipts, claimKey, { log: logBase, issuedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2516
|
+
process.stdout.write(`
|
|
2517
|
+
${bold("\u{1F6E1}\uFE0F Record checkpoint")}
|
|
2518
|
+
`);
|
|
2519
|
+
process.stdout.write(` ${res.total} receipts ${dim("\xB7")} root ${dim(res.record_root.slice(0, 16) + "\u2026")} ${dim("(" + res.checkpoint.from.slice(0, 10) + " \u2192 " + res.checkpoint.to.slice(0, 10) + ")")}
|
|
2520
|
+
`);
|
|
2521
|
+
if (!res.ok) {
|
|
2522
|
+
process.stdout.write(` ${yellow("Anchor failed")} ${dim("(" + (res.error || "unavailable") + "). Nothing was recorded; try again.")}
|
|
2523
|
+
|
|
2524
|
+
`);
|
|
2525
|
+
process.exit(1);
|
|
2526
|
+
return;
|
|
2527
|
+
}
|
|
2528
|
+
appendFileSync(historyPath, JSON.stringify({ schema: res.checkpoint.schema, seq: res.seq, anchored_at: res.anchored_at, total: res.total, record_root: res.record_root, entry_url: res.entry_url, digest: res.checkpoint.digest }) + "\n");
|
|
2529
|
+
process.stdout.write(` ${green("Anchored")} as log entry ${bold("#" + res.seq)} ${dim(res.entry_url || "")}
|
|
2530
|
+
`);
|
|
2531
|
+
process.stdout.write(` ${dim("Only the root, count, and time range were sent. History: " + historyPath)}
|
|
2532
|
+
`);
|
|
2533
|
+
const who = await lookupPinnedIdentity(claimKey.publicKey, { log: logBase });
|
|
2534
|
+
if (who && who.found && !who.revoked) {
|
|
2535
|
+
process.stdout.write(` ${green("Identity:")} anchored as ${bold(who.name || who.slug || "enrolled org")} ${dim("(key pinned in the ScopeBlind directory)")}
|
|
2536
|
+
`);
|
|
2537
|
+
} else if (who && who.found && who.revoked) {
|
|
2538
|
+
process.stdout.write(` ${red("Identity: this key is REVOKED in the ScopeBlind directory.")}
|
|
2539
|
+
`);
|
|
2540
|
+
} else {
|
|
2541
|
+
process.stdout.write(` ${dim("Identity: anonymous (key not enrolled). Named identity: scopeblind.com/enroll")}
|
|
2542
|
+
`);
|
|
2543
|
+
}
|
|
2544
|
+
process.stdout.write(` ${dim("A claim whose commitment matches this root is provably over the complete record as of")}
|
|
2545
|
+
`);
|
|
2546
|
+
process.stdout.write(` ${dim("this checkpoint. Run this on a heartbeat (e.g. cron) to keep the anchored history growing.")}
|
|
2547
|
+
|
|
2548
|
+
`);
|
|
2549
|
+
process.exit(0);
|
|
2550
|
+
}
|
|
2370
2551
|
async function handleVerifyClaim(argv) {
|
|
2371
2552
|
const { readFileSync, existsSync } = await import("fs");
|
|
2372
|
-
const { verifyClaim } = await import("./claim-
|
|
2553
|
+
const { verifyClaim } = await import("./claim-RIXFOQM7.mjs");
|
|
2373
2554
|
const file = argv.find((a) => !a.startsWith("--"));
|
|
2374
2555
|
if (!file || !existsSync(file)) {
|
|
2375
2556
|
process.stderr.write(`
|
|
@@ -2416,17 +2597,79 @@ ${bold("protect-mcp verify-claim")}
|
|
|
2416
2597
|
`);
|
|
2417
2598
|
process.stdout.write(` Predicate: ${ok(v.predicate_ok)} ${v.predicate_ok ? "recomputed independently and matches" : "MISMATCH"}
|
|
2418
2599
|
`);
|
|
2600
|
+
const ai = argv.indexOf("--anchor-file");
|
|
2601
|
+
const sidecarPath = ai !== -1 && argv[ai + 1] ? argv[ai + 1] : file.replace(/\.json$/, "") + ".anchor.json";
|
|
2602
|
+
const requireAnchor = argv.includes("--check-anchor");
|
|
2603
|
+
let anchorOk = true;
|
|
2604
|
+
if (existsSync(sidecarPath)) {
|
|
2605
|
+
const { checkClaimAnchor } = await import("./claim-RIXFOQM7.mjs");
|
|
2606
|
+
let sidecar = null;
|
|
2607
|
+
try {
|
|
2608
|
+
sidecar = JSON.parse(readFileSync(sidecarPath, "utf-8"));
|
|
2609
|
+
} catch {
|
|
2610
|
+
}
|
|
2611
|
+
if (!sidecar) {
|
|
2612
|
+
anchorOk = false;
|
|
2613
|
+
process.stdout.write(` Anchor: ${red("\u2717")} ${sidecarPath} is not valid JSON
|
|
2614
|
+
`);
|
|
2615
|
+
} else {
|
|
2616
|
+
const a = await checkClaimAnchor(pack, sidecar, { offline: argv.includes("--offline") });
|
|
2617
|
+
anchorOk = a.local_ok && a.log_ok !== false;
|
|
2618
|
+
if (a.local_ok) {
|
|
2619
|
+
process.stdout.write(` Anchor: ${green("\u2713")} anchored envelope binds this exact claim and its record root
|
|
2620
|
+
`);
|
|
2621
|
+
process.stdout.write(` ${green("\u2713")} envelope signed by the claim issuer's key
|
|
2622
|
+
`);
|
|
2623
|
+
} else {
|
|
2624
|
+
for (const r of a.reasons.slice(0, 3)) process.stdout.write(` Anchor: ${red("\u2717")} ${r}
|
|
2625
|
+
`);
|
|
2626
|
+
}
|
|
2627
|
+
if (a.log_ok === true) {
|
|
2628
|
+
process.stdout.write(` ${green("\u2713")} public log confirms it${typeof a.seq === "number" ? ": entry " + bold("#" + a.seq) : ""}${a.anchored_at ? dim(" \xB7 anchored " + a.anchored_at) : ""}
|
|
2629
|
+
`);
|
|
2630
|
+
} else if (a.log_ok === false) {
|
|
2631
|
+
process.stdout.write(` ${red("\u2717")} ${a.reasons[a.reasons.length - 1]}
|
|
2632
|
+
`);
|
|
2633
|
+
} else if (a.local_ok) {
|
|
2634
|
+
process.stdout.write(` ${yellow("~")} log not checked ${dim(argv.includes("--offline") ? "(--offline)" : "(unreachable; local binding checks stand)")}
|
|
2635
|
+
`);
|
|
2636
|
+
}
|
|
2637
|
+
if (!argv.includes("--offline") && sidecar.envelope) {
|
|
2638
|
+
const { lookupPinnedIdentity } = await import("./claim-RIXFOQM7.mjs");
|
|
2639
|
+
const who = await lookupPinnedIdentity(sidecar.envelope.verification_key, {});
|
|
2640
|
+
if (who && who.found && !who.revoked) {
|
|
2641
|
+
process.stdout.write(` ${green("\u2713")} issuer key pinned to ${bold(who.name || who.slug || "an enrolled org")} ${dim("(ScopeBlind key directory)")}
|
|
2642
|
+
`);
|
|
2643
|
+
} else if (who && who.found && who.revoked) {
|
|
2644
|
+
anchorOk = false;
|
|
2645
|
+
process.stdout.write(` ${red("\u2717")} issuer key is REVOKED in the ScopeBlind key directory
|
|
2646
|
+
`);
|
|
2647
|
+
} else if (who && !who.found) {
|
|
2648
|
+
process.stdout.write(` ${dim("issuer key not enrolled (anonymous issuer); named identities pin via scopeblind.com/enroll")}
|
|
2649
|
+
`);
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
} else if (requireAnchor) {
|
|
2654
|
+
anchorOk = false;
|
|
2655
|
+
process.stdout.write(` Anchor: ${red("\u2717")} no anchor sidecar at ${sidecarPath} ${dim("(mint with: protect-mcp claim ... --anchor)")}
|
|
2656
|
+
`);
|
|
2657
|
+
} else {
|
|
2658
|
+
process.stdout.write(` Anchor: ${dim("none found (" + sidecarPath + "). Anchoring proves the claim was fixed at a time: claim ... --anchor")}
|
|
2659
|
+
`);
|
|
2660
|
+
}
|
|
2661
|
+
const finalValid = v.valid && anchorOk;
|
|
2419
2662
|
process.stdout.write(`
|
|
2420
|
-
${
|
|
2663
|
+
${finalValid ? green("VALID") : red("INVALID")} attestation${v.valid && !anchorOk ? red(" (anchor check failed)") : ""}.
|
|
2421
2664
|
`);
|
|
2422
2665
|
process.stdout.write(` ${dim("Proves the pack came from the issuer key and the claim is true over the disclosed decision")}
|
|
2423
2666
|
`);
|
|
2424
2667
|
process.stdout.write(` ${dim("categories (verdict + capabilities), which reveal no tool inputs, outputs, or data. Completeness")}
|
|
2425
2668
|
`);
|
|
2426
|
-
process.stdout.write(` ${dim("of the disclosed set is attested by the issuer;
|
|
2669
|
+
process.stdout.write(` ${dim("of the disclosed set is attested by the issuer; the anchor fixes it in a public append-only log.")}
|
|
2427
2670
|
|
|
2428
2671
|
`);
|
|
2429
|
-
process.exit(
|
|
2672
|
+
process.exit(finalValid ? 0 : 1);
|
|
2430
2673
|
}
|
|
2431
2674
|
async function handleBundle(argv) {
|
|
2432
2675
|
const { readFileSync, writeFileSync, existsSync } = await import("fs");
|
|
@@ -3933,6 +4176,10 @@ async function main() {
|
|
|
3933
4176
|
await handleVerifyClaim(args.slice(1));
|
|
3934
4177
|
return;
|
|
3935
4178
|
}
|
|
4179
|
+
if (args[0] === "anchor-record") {
|
|
4180
|
+
await handleAnchorRecord(args.slice(1));
|
|
4181
|
+
return;
|
|
4182
|
+
}
|
|
3936
4183
|
if (args[0] === "init-hooks") {
|
|
3937
4184
|
await handleInitHooks(args.slice(1));
|
|
3938
4185
|
process.exit(0);
|
|
@@ -4121,7 +4368,7 @@ async function main() {
|
|
|
4121
4368
|
if (useHttp) {
|
|
4122
4369
|
const portIdx = args.indexOf("--port");
|
|
4123
4370
|
const httpPort = portIdx >= 0 && args[portIdx + 1] ? parseInt(args[portIdx + 1]) : 3e3;
|
|
4124
|
-
const { startHttpTransport } = await import("./http-transport-
|
|
4371
|
+
const { startHttpTransport } = await import("./http-transport-HLSMVBI6.mjs");
|
|
4125
4372
|
startHttpTransport({ port: httpPort, config, serverCommand: childCommand });
|
|
4126
4373
|
return;
|
|
4127
4374
|
}
|
package/dist/hook-server.js
CHANGED
|
@@ -307,7 +307,13 @@ function signDecision(entry) {
|
|
|
307
307
|
// - scopeblind:verified = issued via ScopeBlind VOPRF backend (paid tier)
|
|
308
308
|
// - self-signed = signed with local Ed25519 key (free tier, protect-mcp default)
|
|
309
309
|
// - uncertified = unsigned receipt (shadow mode, no signing configured)
|
|
310
|
-
issuer_certification: signerState ? "self-signed" : "uncertified"
|
|
310
|
+
issuer_certification: signerState ? "self-signed" : "uncertified",
|
|
311
|
+
// The signer's PUBLIC key, inside the signed payload, so a receipt is
|
|
312
|
+
// self-contained: any verifier (including the record viewer, in-browser)
|
|
313
|
+
// can check the signature without a side channel. Binding the key inside
|
|
314
|
+
// the signature means it cannot be swapped without breaking the signature;
|
|
315
|
+
// authenticity (that the key is YOUR gate's) still comes from pinning it.
|
|
316
|
+
public_key: signerState.publicKey
|
|
311
317
|
};
|
|
312
318
|
if (entry.tier) payload.tier = entry.tier;
|
|
313
319
|
if (entry.credential_ref) payload.credential_ref = entry.credential_ref;
|
|
@@ -1039,7 +1045,7 @@ var sha256 = /* @__PURE__ */ createHasher(() => new SHA256());
|
|
|
1039
1045
|
var sha2562 = sha256;
|
|
1040
1046
|
|
|
1041
1047
|
// src/receipt-enrichment.ts
|
|
1042
|
-
var ENRICHMENT_VERSION =
|
|
1048
|
+
var ENRICHMENT_VERSION = 2;
|
|
1043
1049
|
function canonicalJson(value) {
|
|
1044
1050
|
const seen = /* @__PURE__ */ new WeakSet();
|
|
1045
1051
|
const enc = (v) => {
|
|
@@ -1076,7 +1082,11 @@ var RULES = [
|
|
|
1076
1082
|
{ cap: "secret.adjacent", text: /\.env\b|secret|credential|passwd|password|api[_-]?key|private[_-]?key|\.pem\b|\.key\b|id_rsa|bearer\s|aws_(access|secret)|authorization/ },
|
|
1077
1083
|
{ cap: "destructive", text: /rm\s+-[a-z]*[rf]|\brmdir\b|drop\s+table|truncate\s+table|delete\s+from|reset\s+--hard|--force\b|\bmkfs\b|\bdd\s+if=|shutdown|reboot|kill\s+-9|>\s*\/dev\/sd/ },
|
|
1078
1084
|
{ cap: "financial", text: /\b(order|trade|buy|sell|transfer|wire|payment|withdraw|deposit|swap|invoice|charge|refund|settle)\b/ },
|
|
1079
|
-
{ cap: "data.query", text: /\bselect\s+[\s\S]+\bfrom\b|\binsert\s+into\b|\bupdate\s+[\s\S]+\bset\b|\bdelete\s+from\b/ }
|
|
1085
|
+
{ cap: "data.query", text: /\bselect\s+[\s\S]+\bfrom\b|\binsert\s+into\b|\bupdate\s+[\s\S]+\bset\b|\bdelete\s+from\b/ },
|
|
1086
|
+
// Agent payments (x402 / value transfer). Deliberately BROAD: a false positive
|
|
1087
|
+
// only makes a `claim --no payment` harder to assert (conservative); a false
|
|
1088
|
+
// negative would let a real payment escape the record's payment claims.
|
|
1089
|
+
{ cap: "payment", tool: /(^|[_.-])(pay|payment|x402|checkout)($|[_.-])|wallet.*send|send.*payment/, text: /x402|x-payment|paymentrequirements|maxamountrequired|payto|"pay_to"|eip-3009|transferwithauthorization|payment_intent|send_payment|create_payment/ }
|
|
1080
1090
|
];
|
|
1081
1091
|
function deriveCapabilities(tool, input) {
|
|
1082
1092
|
const t = String(tool || "").toLowerCase();
|
|
@@ -1110,6 +1120,33 @@ function deriveResource(input) {
|
|
|
1110
1120
|
}
|
|
1111
1121
|
return void 0;
|
|
1112
1122
|
}
|
|
1123
|
+
function findField(input, names, depth = 0) {
|
|
1124
|
+
if (depth > 4 || input === null || typeof input !== "object") return void 0;
|
|
1125
|
+
const o = input;
|
|
1126
|
+
const keys = Object.keys(o).sort();
|
|
1127
|
+
for (const k of keys) {
|
|
1128
|
+
if (names.indexOf(k.toLowerCase()) >= 0 && o[k] !== void 0 && o[k] !== null) return o[k];
|
|
1129
|
+
}
|
|
1130
|
+
for (const k of keys) {
|
|
1131
|
+
const v = findField(o[k], names, depth + 1);
|
|
1132
|
+
if (v !== void 0) return v;
|
|
1133
|
+
}
|
|
1134
|
+
return void 0;
|
|
1135
|
+
}
|
|
1136
|
+
function derivePayment(tool, input) {
|
|
1137
|
+
if (deriveCapabilities(tool, input).indexOf("payment") < 0) return void 0;
|
|
1138
|
+
const p = { amount: null, asset: null, recipient_digest: null };
|
|
1139
|
+
const amt = findField(input, ["amount"]);
|
|
1140
|
+
if (typeof amt === "number" && Number.isFinite(amt) && amt >= 0) p.amount = amt;
|
|
1141
|
+
else if (typeof amt === "string" && /^\d{1,15}(\.\d{1,18})?$/.test(amt.trim()) && amt.indexOf(".") >= 0) p.amount = parseFloat(amt);
|
|
1142
|
+
const asset = findField(input, ["asset", "currency", "token"]);
|
|
1143
|
+
if (typeof asset === "string" && asset.trim()) p.asset = asset.trim().slice(0, 64);
|
|
1144
|
+
const to = findField(input, ["payto", "pay_to", "recipient", "destination", "to"]);
|
|
1145
|
+
if (typeof to === "string" && to.trim()) p.recipient_digest = sha256Hex(to.trim().toLowerCase());
|
|
1146
|
+
const scheme = findField(input, ["scheme"]);
|
|
1147
|
+
if (typeof scheme === "string" && scheme.trim()) p.scheme = scheme.trim().slice(0, 32);
|
|
1148
|
+
return p;
|
|
1149
|
+
}
|
|
1113
1150
|
function buildEnrichment(tool, input) {
|
|
1114
1151
|
const e = {
|
|
1115
1152
|
v: ENRICHMENT_VERSION,
|
|
@@ -1118,6 +1155,8 @@ function buildEnrichment(tool, input) {
|
|
|
1118
1155
|
};
|
|
1119
1156
|
const resource = deriveResource(input);
|
|
1120
1157
|
if (resource) e.resource = resource;
|
|
1158
|
+
const payment = derivePayment(tool, input);
|
|
1159
|
+
if (payment) e.payment = payment;
|
|
1121
1160
|
return e;
|
|
1122
1161
|
}
|
|
1123
1162
|
|
package/dist/hook-server.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
startHookServer
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-H332ZNJ6.mjs";
|
|
4
|
+
import "./chunk-WWPQNIVF.mjs";
|
|
5
5
|
import "./chunk-AYNQIEN7.mjs";
|
|
6
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-XLJUZ4WO.mjs";
|
|
7
7
|
import "./chunk-JIDDQUSQ.mjs";
|
|
8
8
|
import "./chunk-D733KAPG.mjs";
|
|
9
9
|
import "./chunk-PQJP2ZCI.mjs";
|
package/dist/index.d.mts
CHANGED
|
@@ -3,6 +3,16 @@ export { BUILTIN_PATTERNS, HookPattern, generateHookSettings, generateSampleCeda
|
|
|
3
3
|
export { createSandboxServer } from './demo-server.mjs';
|
|
4
4
|
import 'node:http';
|
|
5
5
|
|
|
6
|
+
interface PaymentInfo {
|
|
7
|
+
/** Normalized human amount in `asset` units, when derivable; else null. */
|
|
8
|
+
amount: number | null;
|
|
9
|
+
/** Asset symbol (e.g. 'USDC') or contract address, when derivable; else null. */
|
|
10
|
+
asset: string | null;
|
|
11
|
+
/** SHA-256 (hex) of the lowercased recipient: position-blind, when present. */
|
|
12
|
+
recipient_digest: string | null;
|
|
13
|
+
/** x402 scheme ('exact' | 'upto' | ...) when present. */
|
|
14
|
+
scheme?: string;
|
|
15
|
+
}
|
|
6
16
|
interface ReceiptEnrichment {
|
|
7
17
|
/** Rule/schema version, so derivations stay reproducible as rules evolve. */
|
|
8
18
|
v: number;
|
|
@@ -15,6 +25,8 @@ interface ReceiptEnrichment {
|
|
|
15
25
|
kind: 'path' | 'host' | 'command';
|
|
16
26
|
digest: string;
|
|
17
27
|
};
|
|
28
|
+
/** Minimum-disclosure facts about a value transfer (x402 / agent payment). */
|
|
29
|
+
payment?: PaymentInfo;
|
|
18
30
|
}
|
|
19
31
|
|
|
20
32
|
interface ProtectPolicy {
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,16 @@ export { BUILTIN_PATTERNS, HookPattern, generateHookSettings, generateSampleCeda
|
|
|
3
3
|
export { createSandboxServer } from './demo-server.js';
|
|
4
4
|
import 'node:http';
|
|
5
5
|
|
|
6
|
+
interface PaymentInfo {
|
|
7
|
+
/** Normalized human amount in `asset` units, when derivable; else null. */
|
|
8
|
+
amount: number | null;
|
|
9
|
+
/** Asset symbol (e.g. 'USDC') or contract address, when derivable; else null. */
|
|
10
|
+
asset: string | null;
|
|
11
|
+
/** SHA-256 (hex) of the lowercased recipient: position-blind, when present. */
|
|
12
|
+
recipient_digest: string | null;
|
|
13
|
+
/** x402 scheme ('exact' | 'upto' | ...) when present. */
|
|
14
|
+
scheme?: string;
|
|
15
|
+
}
|
|
6
16
|
interface ReceiptEnrichment {
|
|
7
17
|
/** Rule/schema version, so derivations stay reproducible as rules evolve. */
|
|
8
18
|
v: number;
|
|
@@ -15,6 +25,8 @@ interface ReceiptEnrichment {
|
|
|
15
25
|
kind: 'path' | 'host' | 'command';
|
|
16
26
|
digest: string;
|
|
17
27
|
};
|
|
28
|
+
/** Minimum-disclosure facts about a value transfer (x402 / agent payment). */
|
|
29
|
+
payment?: PaymentInfo;
|
|
18
30
|
}
|
|
19
31
|
|
|
20
32
|
interface ProtectPolicy {
|