bootproof 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +844 -152
- package/dist/agent-plan.d.ts +44 -0
- package/dist/agent-plan.js +826 -0
- package/dist/agent-run.d.ts +117 -0
- package/dist/agent-run.js +459 -0
- package/dist/ai-repair.d.ts +58 -0
- package/dist/ai-repair.js +380 -0
- package/dist/cli.js +730 -46
- package/dist/diagnosis.js +101 -16
- package/dist/diff.d.ts +29 -0
- package/dist/diff.js +569 -0
- package/dist/exec.d.ts +30 -2
- package/dist/exec.js +329 -51
- package/dist/external-health.d.ts +16 -0
- package/dist/external-health.js +214 -0
- package/dist/infer.js +238 -39
- package/dist/plan.js +2 -0
- package/dist/proof.d.ts +78 -2
- package/dist/proof.js +265 -12
- package/dist/receipt.d.ts +52 -0
- package/dist/receipt.js +356 -0
- package/dist/redact.d.ts +4 -0
- package/dist/redact.js +86 -2
- package/dist/registry.d.ts +82 -30
- package/dist/registry.js +355 -53
- package/dist/remote.js +3 -3
- package/dist/repair-playbooks.d.ts +24 -0
- package/dist/repair-playbooks.js +593 -0
- package/dist/repair-safety.d.ts +130 -0
- package/dist/repair-safety.js +766 -0
- package/dist/repair.d.ts +43 -11
- package/dist/repair.js +716 -7
- package/dist/run.d.ts +3 -0
- package/dist/run.js +218 -41
- package/dist/sbom.d.ts +22 -0
- package/dist/sbom.js +99 -0
- package/dist/taxonomy.d.ts +8 -3
- package/dist/taxonomy.js +404 -8
- package/dist/types.d.ts +40 -1
- package/docs/AGENT_IN_THE_LOOP.md +171 -0
- package/docs/AGENT_RUN_RECEIPTS.md +38 -0
- package/docs/CI_ACTION.md +67 -2
- package/docs/DETERMINISTIC_REPAIR_SAFETY_MODEL.md +705 -0
- package/docs/DISTRIBUTION.md +83 -0
- package/docs/FAILURE_TAXONOMY.md +28 -1
- package/docs/HONESTY_CONTRACT.md +34 -12
- package/docs/LAUNCH_PLAYBOOK.md +232 -0
- package/docs/REAL_WORLD_FIXTURES.md +105 -0
- package/docs/REGISTRY.md +48 -28
- package/docs/REPAIR_RECEIPT.md +54 -8
- package/docs/agent-loop-gap-analysis.md +188 -0
- package/docs/examples/registry-seeds/advertised-port-mismatch.json +28 -0
- package/docs/examples/registry-seeds/airbyte-abctl-external-orchestrator.json +36 -0
- package/docs/examples/registry-seeds/go-ollama-service.json +36 -0
- package/docs/examples/registry-seeds/laravel-vite-sqlite.json +36 -0
- package/docs/examples/registry-seeds/monorepo-ambiguous-health.json +29 -0
- package/docs/examples/registry-seeds/php-composer.json +33 -0
- package/docs/examples/registry-seeds/rails-bundler.json +32 -0
- package/docs/examples/registry-seeds/sentry-devenv-direnv.json +41 -0
- package/docs/schemas/action-verdict-v1.schema.json +64 -0
- package/docs/schemas/agent-plan-v1.schema.json +148 -0
- package/docs/schemas/agent-run-receipts-v1.schema.json +192 -0
- package/docs/schemas/ai-repair-suggestion-v1.schema.json +70 -0
- package/docs/schemas/ci-context-v1.schema.json +63 -0
- package/docs/schemas/diff-result-v1.schema.json +66 -0
- package/docs/schemas/federated-receipt-v1.schema.json +51 -0
- package/docs/schemas/registry-entry-v1.schema.json +95 -0
- package/docs/schemas/registry-seed-example-v1.schema.json +102 -0
- package/docs/schemas/repair-action-v1.schema.json +136 -0
- package/docs/schemas/repair-receipt-v1.schema.json +221 -0
- package/package.json +21 -11
package/dist/receipt.js
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
// receipt.ts — Living Receipt emitter for the BootProof TypeScript CLI.
|
|
2
|
+
//
|
|
3
|
+
// Converts a real Attestation (produced by buildAttestation in proof.ts) into
|
|
4
|
+
// a single self-contained HTML file that:
|
|
5
|
+
// 1. Verifies its own ed25519 signature in the browser via Web Crypto.
|
|
6
|
+
// 2. Falls back to a pure-JS @noble/curves verifier on browsers that don't
|
|
7
|
+
// support native Ed25519 WebCrypto.
|
|
8
|
+
// 3. Replays the actual boot timeline when opened.
|
|
9
|
+
// 4. When the signature is tampered, the boot verdict collapses.
|
|
10
|
+
//
|
|
11
|
+
// This is the integration point: `npx bootproof up <repo> --receipt` calls
|
|
12
|
+
// emitLivingReceipt(attestation, outPath) after the real boot completes.
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import crypto from "node:crypto";
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// The @noble/curves Ed25519 fallback bundle, inlined.
|
|
18
|
+
// This is generated by esbuild from fallback_entry.mjs and exposes
|
|
19
|
+
// window.BootProofFallback.verifyEd25519(spkiB64, sigB64, msgBytes) -> boolean
|
|
20
|
+
// We embed it as a string so the published npm package has zero runtime deps
|
|
21
|
+
// beyond what's already in package.json.
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
const FALLBACK_BUNDLE = `var BootProofFallback=(()=>{var Ot=Object.defineProperty;var We=Object.getOwnPropertyDescriptor;var Pe=Object.getOwnPropertyNames;var $e=Object.prototype.hasOwnProperty;var Qe=(t,e)=>{for(var n in e)Ot(t,n,{get:e[n],enumerable:!0})},Je=(t,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of Pe(e))!$e.call(t,o)&&o!==n&&Ot(t,o,{get:()=>e[o],enumerable:!(r=We(e,o))||r.enumerable});return t};var tn=t=>Je(Ot({},"__esModule",{value:!0}),t);var Dn={};Qe(Dn,{extractRawPubKeyFromSpki:()=>ze,verifyEd25519:()=>Rn});var nt=typeof globalThis=="object"&&"crypto"in globalThis?globalThis.crypto:void 0;function rt(t){return t instanceof Uint8Array||ArrayBuffer.isView(t)&&t.constructor.name==="Uint8Array"}function qt(t){if(!Number.isSafeInteger(t)||t<0)throw new Error("positive integer expected, got "+t)}function Z(t,...e){if(!rt(t))throw new Error("Uint8Array expected");if(e.length>0&&!e.includes(t.length))throw new Error("Uint8Array expected of length "+e+", got length="+t.length)}function Nt(t,e=!0){if(t.destroyed)throw new Error("Hash instance has been destroyed");if(e&&t.finished)throw new Error("Hash#digest() has already been called")}function ce(t,e){Z(t);let n=e.outputLen;if(t.length<n)throw new Error("digestInto() expects output buffer of length at least "+n)}function lt(...t){for(let e=0;e<t.length;e++)t[e].fill(0)}function Bt(t){return new DataView(t.buffer,t.byteOffset,t.byteLength)}var fe=typeof Uint8Array.from([]).toHex=="function"&&typeof Uint8Array.fromHex=="function",en=Array.from({length:256},(t,e)=>e.toString(16).padStart(2,"0"));function ot(t){if(Z(t),fe)return t.toHex();let e="";for(let n=0;n<t.length;n++)e+=en[t[n]];return e}var X={_0:48,_9:57,A:65,F:70,a:97,f:102};function ie(t){if(t>=X._0&&t<=X._9)return t-X._0;if(t>=X.A&&t<=X.F)return t-(X.A-10);if(t>=X.a&&t<=X.f)return t-(X.a-10)}function Et(t){if(typeof t!="string")throw new Error("hex string expected, got "+typeof t);if(fe)return Uint8Array.fromHex(t);let e=t.length,n=e/2;if(e%2)throw new Error("hex string expected, got unpadded hex of length "+e);let r=new Uint8Array(n);for(let o=0,s=0;o<n;o++,s+=2){let c=ie(t.charCodeAt(s)),a=ie(t.charCodeAt(s+1));if(c===void 0||a===void 0){let i=t[s]+t[s+1];throw new Error('hex string expected, got non-hex character "'+i+'" at index '+s)}r[o]=c*16+a}return r}function Ct(t){if(typeof t!="string")throw new Error("string expected");return new Uint8Array(new TextEncoder().encode(t))}function Mt(t){return typeof t=="string"&&(t=Ct(t)),Z(t),t}function ft(...t){let e=0;for(let r=0;r<t.length;r++){let o=t[r];Z(o),e+=o.length}let n=new Uint8Array(e);for(let r=0,o=0;r<t.length;r++){let s=t[r];n.set(s,o),o+=s.length}return n}var wt=class{};function ae(t){let e=r=>t().update(Mt(r)).digest(),n=t();return e.outputLen=n.outputLen,e.blockLen=n.blockLen,e.create=()=>t(),e}function Rt(t=32){if(nt&&typeof nt.getRandomValues=="function")return nt.getRandomValues(new Uint8Array(t));if(nt&&typeof nt.randomBytes=="function")return Uint8Array.from(nt.randomBytes(t));throw new Error("crypto.getRandomValues must be defined")}function nn(t,e,n,r){if(typeof t.setBigUint64=="function")return t.setBigUint64(e,n,r);let o=BigInt(32),s=BigInt(4294967295),c=Number(n>>o&s),a=Number(n&s),i=r?4:0,u=r?0:4;t.setUint32(e+i,c,r),t.setUint32(e+u,a,r)}var _t=class extends wt{constructor(e,n,r,o){super(),this.finished=!1,this.length=0,this.pos=0,this.destroyed=!1,this.blockLen=e,this.outputLen=n,this.padOffset=r,this.isLE=o,this.buffer=new Uint8Array(e),this.view=Bt(this.buffer)}update(e){Nt(this),e=Mt(e),Z(e);let{view:n,buffer:r,blockLen:o}=this,s=e.length;for(let c=0;c<s;){let a=Math.min(o-this.pos,s-c);if(a===o){let i=Bt(e);for(;o<=s-c;c+=o)this.process(i,c);continue}r.set(e.subarray(c,c+a),this.pos),this.pos+=a,c+=a,this.pos===o&&(this.process(n,0),this.pos=0)}return this.length+=e.length,this.roundClean(),this}digestInto(e){Nt(this),ce(e,this),this.finished=!0;let{buffer:n,view:r,blockLen:o,isLE:s}=this,{pos:c}=this;n[c++]=128,lt(this.buffer.subarray(c)),this.padOffset>o-c&&(this.process(r,0),c=0);for(let h=c;h<o;h++)n[h]=0;nn(r,o-8,BigInt(this.length*8),s),this.process(r,0);let a=Bt(e),i=this.outputLen;if(i%4)throw new Error("_sha2: outputLen should be aligned to 32bit");let u=i/4,p=this.get();if(u>p.length)throw new Error("_sha2: outputLen bigger than state");for(let h=0;h<u;h++)a.setUint32(4*h,p[h],s)}digest(){let{buffer:e,outputLen:n}=this;this.digestInto(e);let r=e.slice(0,n);return this.destroy(),r}_cloneInto(e){e||(e=new this.constructor),e.set(...this.get());let{blockLen:n,buffer:r,length:o,finished:s,destroyed:c,pos:a}=this;return e.destroyed=c,e.finished=s,e.length=o,e.pos=a,o%n&&e.buffer.set(r),e}clone(){return this._cloneInto()}};var N=Uint32Array.from([1779033703,4089235720,3144134277,2227873595,1013904242,4271175723,2773480762,1595750129,1359893119,2917565137,2600822924,725511199,528734635,4215389547,1541459225,327033209]);var St=BigInt(4294967295),ue=BigInt(32);function rn(t,e=!1){return e?{h:Number(t&St),l:Number(t>>ue&St)}:{h:Number(t>>ue&St)|0,l:Number(t&St)|0}}function le(t,e=!1){let n=t.length,r=new Uint32Array(n),o=new Uint32Array(n);for(let s=0;s<n;s++){let{h:c,l:a}=rn(t[s],e);[r[s],o[s]]=[c,a]}return[r,o]}var Dt=(t,e,n)=>t>>>n,Zt=(t,e,n)=>t<<32-n|e>>>n,st=(t,e,n)=>t>>>n|e<<32-n,it=(t,e,n)=>t<<32-n|e>>>n,dt=(t,e,n)=>t<<64-n|e>>>n-32,ht=(t,e,n)=>t>>>n-32|e<<64-n;function G(t,e,n,r){let o=(e>>>0)+(r>>>0);return{h:t+n+(o/2**32|0)|0,l:o|0}}var de=(t,e,n)=>(t>>>0)+(e>>>0)+(n>>>0),he=(t,e,n,r)=>e+n+r+(t/2**32|0)|0,xe=(t,e,n,r)=>(t>>>0)+(e>>>0)+(n>>>0)+(r>>>0),pe=(t,e,n,r,o)=>e+n+r+o+(t/2**32|0)|0,be=(t,e,n,r,o)=>(t>>>0)+(e>>>0)+(n>>>0)+(r>>>0)+(o>>>0),ge=(t,e,n,r,o,s)=>e+n+r+o+s+(t/2**32|0)|0;var me=le(["0x428a2f98d728ae22","0x7137449123ef65cd","0xb5c0fbcfec4d3b2f","0xe9b5dba58189dbbc","0x3956c25bf348b538","0x59f111f1b605d019","0x923f82a4af194f9b","0xab1c5ed5da6d8118","0xd807aa98a3030242","0x12835b0145706fbe","0x243185be4ee4b28c","0x550c7dc3d5ffb4e2","0x72be5d74f27b896f","0x80deb1fe3b1696b1","0x9bdc06a725c71235","0xc19bf174cf692694","0xe49b69c19ef14ad2","0xefbe4786384f25e3","0x0fc19dc68b8cd5b5","0x240ca1cc77ac9c65","0x2de92c6f592b0275","0x4a7484aa6ea6e483","0x5cb0a9dcbd41fbd4","0x76f988da831153b5","0x983e5152ee66dfab","0xa831c66d2db43210","0xb00327c898fb213f","0xbf597fc7beef0ee4","0xc6e00bf33da88fc2","0xd5a79147930aa725","0x06ca6351e003826f","0x142929670a0e6e70","0x27b70a8546d22ffc","0x2e1b21385c26c926","0x4d2c6dfc5ac42aed","0x53380d139d95b3df","0x650a73548baf63de","0x766a0abb3c77b2a8","0x81c2c92e47edaee6","0x92722c851482353b","0xa2bfe8a14cf10364","0xa81a664bbc423001","0xc24b8b70d0f89791","0xc76c51a30654be30","0xd192e819d6ef5218","0xd69906245565a910","0xf40e35855771202a","0x106aa07032bbd1b8","0x19a4c116b8d2d0c8","0x1e376c085141ab53","0x2748774cdf8eeb99","0x34b0bcb5e19b48a8","0x391c0cb3c5c95a63","0x4ed8aa4ae3418acb","0x5b9cca4f7763e373","0x682e6ff3d6b2b8a3","0x748f82ee5defb2fc","0x78a5636f43172f60","0x84c87814a1f0ab72","0x8cc702081a6439ec","0x90befffa23631e28","0xa4506cebde82bde9","0xbef9a3f7b2c67915","0xc67178f2e372532b","0xca273eceea26619c","0xd186b8c721c0c207","0xeada7dd6cde0eb1e","0xf57d4f7fee6ed178","0x06f067aa72176fba","0x0a637dc5a2c898a6","0x113f9804bef90dae","0x1b710b35131c471b","0x28db77f523047d84","0x32caab7b40c72493","0x3c9ebe0a15c9bebc","0x431d67c49c100d4c","0x4cc5d4becb3e42b6","0x597f299cfc657e2a","0x5fcb6fab3ad6faec","0x6c44198c4a475817"].map(t=>BigInt(t))),sn=me[0],cn=me[1],P=new Uint32Array(80),$=new Uint32Array(80),Vt=class extends _t{constructor(e=64){super(128,e,16,!1),this.Ah=N[0]|0,this.Al=N[1]|0,this.Bh=N[2]|0,this.Bl=N[3]|0,this.Ch=N[4]|0,this.Cl=N[5]|0,this.Dh=N[6]|0,this.Dl=N[7]|0,this.Eh=N[8]|0,this.El=N[9]|0,this.Fh=N[10]|0,this.Fl=N[11]|0,this.Gh=N[12]|0,this.Gl=N[13]|0,this.Hh=N[14]|0,this.Hl=N[15]|0}get(){let{Ah:e,Al:n,Bh:r,Bl:o,Ch:s,Cl:c,Dh:a,Dl:i,Eh:u,El:p,Fh:h,Fl:f,Gh:g,Gl:d,Hh:m,Hl:B}=this;return[e,n,r,o,s,c,a,i,u,p,h,f,g,d,m,B]}set(e,n,r,o,s,c,a,i,u,p,h,f,g,d,m,B){this.Ah=e|0,this.Al=n|0,this.Bh=r|0,this.Bl=o|0,this.Ch=s|0,this.Cl=c|0,this.Dh=a|0,this.Dl=i|0,this.Eh=u|0,this.El=p|0,this.Fh=h|0,this.Fl=f|0,this.Gh=g|0,this.Gl=d|0,this.Hh=m|0,this.Hl=B|0}process(e,n){for(let x=0;x<16;x++,n+=4)P[x]=e.getUint32(n),$[x]=e.getUint32(n+=4);for(let x=16;x<80;x++){let _=P[x-15]|0,A=$[x-15]|0,L=st(_,A,1)^st(_,A,8)^Dt(_,A,7),H=it(_,A,1)^it(_,A,8)^Zt(_,A,7),S=P[x-2]|0,b=$[x-2]|0,y=st(S,b,19)^dt(S,b,61)^Dt(S,b,6),E=it(S,b,19)^ht(S,b,61)^Zt(S,b,6),v=xe(H,E,$[x-7],$[x-16]),T=pe(v,L,y,P[x-7],P[x-16]);P[x]=T|0,$[x]=v|0}let{Ah:r,Al:o,Bh:s,Bl:c,Ch:a,Cl:i,Dh:u,Dl:p,Eh:h,El:f,Fh:g,Fl:d,Gh:m,Gl:B,Hh:l,Hl:w}=this;for(let x=0;x<80;x++){let _=st(h,f,14)^st(h,f,18)^dt(h,f,41),A=it(h,f,14)^it(h,f,18)^ht(h,f,41),L=h&g^~h&m,H=f&d^~f&B,S=be(w,A,H,cn[x],$[x]),b=ge(S,l,_,L,sn[x],P[x]),y=S|0,E=st(r,o,28)^dt(r,o,34)^dt(r,o,39),v=it(r,o,28)^ht(r,o,34)^ht(r,o,39),T=r&s^r&a^s&a,I=o&c^o&i^c&i;l=m|0,w=B|0,m=g|0,B=d|0,g=h|0,d=f|0,{h,l:f}=G(u|0,p|0,b|0,y|0),u=a|0,p=i|0,a=s|0,i=c|0,s=r|0,c=o|0;let O=de(y,v,I);r=he(O,b,E,T),o=O|0}({h:r,l:o}=G(this.Ah|0,this.Al|0,r|0,o|0)),{h:s,l:c}=G(this.Bh|0,this.Bl|0,s|0,c|0),{h:a,l:i}=G(this.Ch|0,this.Cl|0,a|0,i|0),{h:u,l:p}=G(this.Dh|0,this.Dl|0,u|0,p|0),{h,l:f}=G(this.Eh|0,this.El|0,h|0,f|0),{h:g,l:d}=G(this.Fh|0,this.Fl|0,g|0,d|0),{h:m,l:B}=G(this.Gh|0,this.Gl|0,m|0,B|0),{h:l,l:w}=G(this.Hh|0,this.Hl|0,l|0,w|0),this.set(r,o,s,c,a,i,u,p,h,f,g,d,m,B,l,w)}roundClean(){lt(P,$)}destroy(){lt(this.buffer),this.set(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)}};var ye=ae(()=>new Vt);var Xt=BigInt(0),jt=BigInt(1);function At(t,e=""){if(typeof t!="boolean"){let n=e&&\`"\${e}"\`;throw new Error(n+"expected boolean, got type="+typeof t)}return t}function xt(t,e,n=""){let r=rt(t),o=t?.length,s=e!==void 0;if(!r||s&&o!==e){let c=n&&\`"\${n}" \`,a=s?\` of length \${e}\`:"",i=r?\`length=\${o}\`:\`type=\${typeof t}\`;throw new Error(c+"expected Uint8Array"+a+", got "+i)}return t}function we(t){if(typeof t!="string")throw new Error("hex string expected, got "+typeof t);return t===""?Xt:BigInt("0x"+t)}function Be(t){return we(ot(t))}function Q(t){return Z(t),we(ot(Uint8Array.from(t).reverse()))}function Yt(t,e){return Et(t.toString(16).padStart(e*2,"0"))}function Ee(t,e){return Yt(t,e).reverse()}function D(t,e,n){let r;if(typeof e=="string")try{r=Et(e)}catch(s){throw new Error(t+" must be hex string or Uint8Array, cause: "+s)}else if(rt(e))r=Uint8Array.from(e);else throw new Error(t+" must be hex string or Uint8Array");let o=r.length;if(typeof n=="number"&&o!==n)throw new Error(t+" of length "+n+" expected, got "+o);return r}function _e(t,e){if(t.length!==e.length)return!1;let n=0;for(let r=0;r<t.length;r++)n|=t[r]^e[r];return n===0}function kt(t){return Uint8Array.from(t)}var Gt=t=>typeof t=="bigint"&&Xt<=t;function fn(t,e,n){return Gt(t)&&Gt(e)&&Gt(n)&&e<=t&&t<n}function Ft(t,e,n,r){if(!fn(e,n,r))throw new Error("expected valid "+t+": "+n+" <= n < "+r+", got "+e)}function Se(t){let e;for(e=0;t>Xt;t>>=jt,e+=1);return e}var pt=t=>(jt<<BigInt(t))-jt;function bt(t,e,n={}){if(!t||typeof t!="object")throw new Error("expected valid options object");function r(o,s,c){let a=t[o];if(c&&a===void 0)return;let i=typeof a;if(i!==s||a===null)throw new Error(\`param "\${o}" is invalid: expected \${s}, got \${i}\`)}Object.entries(e).forEach(([o,s])=>r(o,s,!1)),Object.entries(n).forEach(([o,s])=>r(o,s,!0))}var Kt=()=>{throw new Error("not implemented")};function zt(t){let e=new WeakMap;return(n,...r)=>{let o=e.get(n);if(o!==void 0)return o;let s=t(n,...r);return e.set(n,s),s}}var M=BigInt(0),C=BigInt(1),ct=BigInt(2),Ie=BigInt(3),Te=BigInt(4),Le=BigInt(5),an=BigInt(7),He=BigInt(8),un=BigInt(9),Ue=BigInt(16);function U(t,e){let n=t%e;return n>=M?n:e+n}function V(t,e,n){let r=t;for(;e-- >M;)r*=r,r%=n;return r}function Ae(t,e){if(t===M)throw new Error("invert: expected non-zero number");if(e<=M)throw new Error("invert: expected positive modulus, got "+e);let n=U(t,e),r=e,o=M,s=C,c=C,a=M;for(;n!==M;){let u=r/n,p=r%n,h=o-c*u,f=s-a*u;r=n,n=p,o=c,s=a,c=h,a=f}if(r!==C)throw new Error("invert: does not exist");return U(o,e)}function Wt(t,e,n){if(!t.eql(t.sqr(e),n))throw new Error("Cannot find square root")}function Oe(t,e){let n=(t.ORDER+C)/Te,r=t.pow(e,n);return Wt(t,r,e),r}function ln(t,e){let n=(t.ORDER-Le)/He,r=t.mul(e,ct),o=t.pow(r,n),s=t.mul(e,o),c=t.mul(t.mul(s,ct),o),a=t.mul(s,t.sub(c,t.ONE));return Wt(t,a,e),a}function dn(t){let e=k(t),n=qe(t),r=n(e,e.neg(e.ONE)),o=n(e,r),s=n(e,e.neg(r)),c=(t+an)/Ue;return(a,i)=>{let u=a.pow(i,c),p=a.mul(u,r),h=a.mul(u,o),f=a.mul(u,s),g=a.eql(a.sqr(p),i),d=a.eql(a.sqr(h),i);u=a.cmov(u,p,g),p=a.cmov(f,h,d);let m=a.eql(a.sqr(p),i),B=a.cmov(u,p,m);return Wt(a,B,i),B}}function qe(t){if(t<Ie)throw new Error("sqrt is not defined for small field");let e=t-C,n=0;for(;e%ct===M;)e/=ct,n++;let r=ct,o=k(t);for(;ve(o,r)===1;)if(r++>1e3)throw new Error("Cannot find square root: probably non-prime P");if(n===1)return Oe;let s=o.pow(r,e),c=(e+C)/ct;return function(i,u){if(i.is0(u))return u;if(ve(i,u)!==1)throw new Error("Cannot find square root");let p=n,h=i.mul(i.ONE,s),f=i.pow(u,e),g=i.pow(u,c);for(;!i.eql(f,i.ONE);){if(i.is0(f))return i.ZERO;let d=1,m=i.sqr(f);for(;!i.eql(m,i.ONE);)if(d++,m=i.sqr(m),d===p)throw new Error("Cannot find square root");let B=C<<BigInt(p-d-1),l=i.pow(h,B);p=d,h=i.sqr(l),f=i.mul(f,h),g=i.mul(g,l)}return g}}function hn(t){return t%Te===Ie?Oe:t%He===Le?ln:t%Ue===un?dn(t):qe(t)}var Y=(t,e)=>(U(t,e)&C)===C,xn=["create","isValid","is0","neg","inv","sqrt","sqr","eql","add","sub","mul","pow","div","addN","subN","mulN","sqrN"];function Ne(t){let e={ORDER:"bigint",MASK:"bigint",BYTES:"number",BITS:"number"},n=xn.reduce((r,o)=>(r[o]="function",r),e);return bt(t,n),t}function pn(t,e,n){if(n<M)throw new Error("invalid exponent, negatives unsupported");if(n===M)return t.ONE;if(n===C)return e;let r=t.ONE,o=e;for(;n>M;)n&C&&(r=t.mul(r,o)),o=t.sqr(o),n>>=C;return r}function vt(t,e,n=!1){let r=new Array(e.length).fill(n?t.ZERO:void 0),o=e.reduce((c,a,i)=>t.is0(a)?c:(r[i]=c,t.mul(c,a)),t.ONE),s=t.inv(o);return e.reduceRight((c,a,i)=>t.is0(a)?c:(r[i]=t.mul(c,r[i]),t.mul(c,a)),s),r}function ve(t,e){let n=(t.ORDER-C)/ct,r=t.pow(e,n),o=t.eql(r,t.ONE),s=t.eql(r,t.ZERO),c=t.eql(r,t.neg(t.ONE));if(!o&&!s&&!c)throw new Error("invalid Legendre symbol result");return o?1:s?0:-1}function Ce(t,e){e!==void 0&&qt(e);let n=e!==void 0?e:t.toString(2).length,r=Math.ceil(n/8);return{nBitLength:n,nByteLength:r}}function k(t,e,n=!1,r={}){if(t<=M)throw new Error("invalid field: expected ORDER > 0, got "+t);let o,s,c=!1,a;if(typeof e=="object"&&e!=null){if(r.sqrt||n)throw new Error("cannot specify opts in two arguments");let f=e;f.BITS&&(o=f.BITS),f.sqrt&&(s=f.sqrt),typeof f.isLE=="boolean"&&(n=f.isLE),typeof f.modFromBytes=="boolean"&&(c=f.modFromBytes),a=f.allowedLengths}else typeof e=="number"&&(o=e),r.sqrt&&(s=r.sqrt);let{nBitLength:i,nByteLength:u}=Ce(t,o);if(u>2048)throw new Error("invalid field: expected ORDER of <= 2048 bytes");let p,h=Object.freeze({ORDER:t,isLE:n,BITS:i,BYTES:u,MASK:pt(i),ZERO:M,ONE:C,allowedLengths:a,create:f=>U(f,t),isValid:f=>{if(typeof f!="bigint")throw new Error("invalid field element: expected bigint, got "+typeof f);return M<=f&&f<t},is0:f=>f===M,isValidNot0:f=>!h.is0(f)&&h.isValid(f),isOdd:f=>(f&C)===C,neg:f=>U(-f,t),eql:(f,g)=>f===g,sqr:f=>U(f*f,t),add:(f,g)=>U(f+g,t),sub:(f,g)=>U(f-g,t),mul:(f,g)=>U(f*g,t),pow:(f,g)=>pn(h,f,g),div:(f,g)=>U(f*Ae(g,t),t),sqrN:f=>f*f,addN:(f,g)=>f+g,subN:(f,g)=>f-g,mulN:(f,g)=>f*g,inv:f=>Ae(f,t),sqrt:s||(f=>(p||(p=hn(t)),p(h,f))),toBytes:f=>n?Ee(f,u):Yt(f,u),fromBytes:(f,g=!0)=>{if(a){if(!a.includes(f.length)||f.length>u)throw new Error("Field.fromBytes: expected "+a+" bytes, got "+f.length);let m=new Uint8Array(u);m.set(f,n?0:m.length-f.length),f=m}if(f.length!==u)throw new Error("Field.fromBytes: expected "+u+" bytes, got "+f.length);let d=n?Q(f):Be(f);if(c&&(d=U(d,t)),!g&&!h.isValid(d))throw new Error("invalid field element: outside of range 0..ORDER");return d},invertBatch:f=>vt(h,f),cmov:(f,g,d)=>d?g:f});return Object.freeze(h)}var It=BigInt(0),Jt=BigInt(1);function Me(t,e){let n=e.negate();return t?n:e}function gt(t,e){let n=vt(t.Fp,e.map(r=>r.Z));return e.map((r,o)=>t.fromAffine(r.toAffine(n[o])))}function Ve(t,e){if(!Number.isSafeInteger(t)||t<=0||t>e)throw new Error("invalid window size, expected [1.."+e+"], got W="+t)}function Pt(t,e){Ve(t,e);let n=Math.ceil(e/t)+1,r=2**(t-1),o=2**t,s=pt(t),c=BigInt(t);return{windows:n,windowSize:r,mask:s,maxNumber:o,shiftBy:c}}function Re(t,e,n){let{windowSize:r,mask:o,maxNumber:s,shiftBy:c}=n,a=Number(t&o),i=t>>c;a>r&&(a-=s,i+=Jt);let u=e*r,p=u+Math.abs(a)-1,h=a===0,f=a<0,g=e%2!==0;return{nextN:i,offset:p,isZero:h,isNeg:f,isNegF:g,offsetF:u}}function bn(t,e){if(!Array.isArray(t))throw new Error("array expected");t.forEach((n,r)=>{if(!(n instanceof e))throw new Error("invalid point at index "+r)})}function gn(t,e){if(!Array.isArray(t))throw new Error("array of scalars expected");t.forEach((n,r)=>{if(!e.isValid(n))throw new Error("invalid scalar at index "+r)})}var $t=new WeakMap,Ge=new WeakMap;function Qt(t){return Ge.get(t)||1}function De(t){if(t!==It)throw new Error("invalid wNAF")}var Tt=class{constructor(e,n){this.BASE=e.BASE,this.ZERO=e.ZERO,this.Fn=e.Fn,this.bits=n}_unsafeLadder(e,n,r=this.ZERO){let o=e;for(;n>It;)n&Jt&&(r=r.add(o)),o=o.double(),n>>=Jt;return r}precomputeWindow(e,n){let{windows:r,windowSize:o}=Pt(n,this.bits),s=[],c=e,a=c;for(let i=0;i<r;i++){a=c,s.push(a);for(let u=1;u<o;u++)a=a.add(c),s.push(a);c=a.double()}return s}wNAF(e,n,r){if(!this.Fn.isValid(r))throw new Error("invalid scalar");let o=this.ZERO,s=this.BASE,c=Pt(e,this.bits);for(let a=0;a<c.windows;a++){let{nextN:i,offset:u,isZero:p,isNeg:h,isNegF:f,offsetF:g}=Re(r,a,c);r=i,p?s=s.add(Me(f,n[g])):o=o.add(Me(h,n[u]))}return De(r),{p:o,f:s}}wNAFUnsafe(e,n,r,o=this.ZERO){let s=Pt(e,this.bits);for(let c=0;c<s.windows&&r!==It;c++){let{nextN:a,offset:i,isZero:u,isNeg:p}=Re(r,c,s);if(r=a,!u){let h=n[i];o=o.add(p?h.negate():h)}}return De(r),o}getPrecomputes(e,n,r){let o=$t.get(n);return o||(o=this.precomputeWindow(n,e),e!==1&&(typeof r=="function"&&(o=r(o)),$t.set(n,o))),o}cached(e,n,r){let o=Qt(e);return this.wNAF(o,this.getPrecomputes(o,e,r),n)}unsafe(e,n,r,o){let s=Qt(e);return s===1?this._unsafeLadder(e,n,o):this.wNAFUnsafe(s,this.getPrecomputes(s,e,r),n,o)}createCache(e,n){Ve(n,this.bits),Ge.set(e,n),$t.delete(e)}hasCache(e){return Qt(e)!==1}};function Lt(t,e,n,r){bn(n,t),gn(r,e);let o=n.length,s=r.length;if(o!==s)throw new Error("arrays of points and scalars must have equal length");let c=t.ZERO,a=Se(BigInt(o)),i=1;a>12?i=a-3:a>4?i=a-2:a>0&&(i=2);let u=pt(i),p=new Array(Number(u)+1).fill(c),h=Math.floor((e.BITS-1)/i)*i,f=c;for(let g=h;g>=0;g-=i){p.fill(c);for(let m=0;m<s;m++){let B=r[m],l=Number(B>>BigInt(g)&u);p[l]=p[l].add(n[m])}let d=c;for(let m=p.length-1,B=c;m>0;m--)B=B.add(p[m]),d=d.add(B);if(f=f.add(d),g!==0)for(let m=0;m<i;m++)f=f.double()}return f}function Ze(t,e,n){if(e){if(e.ORDER!==t)throw new Error("Field.ORDER must match order: Fp == p, Fn == n");return Ne(e),e}else return k(t,{isLE:n})}function je(t,e,n={},r){if(r===void 0&&(r=t==="edwards"),!e||typeof e!="object")throw new Error(\`expected valid \${t} CURVE object\`);for(let i of["p","n","h"]){let u=e[i];if(!(typeof u=="bigint"&&u>It))throw new Error(\`CURVE.\${i} must be positive bigint\`)}let o=Ze(e.p,n.Fp,r),s=Ze(e.n,n.Fn,r),a=["Gx","Gy","a",t==="weierstrass"?"b":"d"];for(let i of a)if(!o.isValid(e[i]))throw new Error(\`CURVE.\${i} must be valid field element of CURVE.Fp\`);return e=Object.freeze(Object.assign({},e)),{CURVE:e,Fp:o,Fn:s}}var J=BigInt(0),q=BigInt(1),te=BigInt(2),mn=BigInt(8);function yn(t,e,n,r){let o=t.sqr(n),s=t.sqr(r),c=t.add(t.mul(e.a,o),s),a=t.add(t.ONE,t.mul(e.d,t.mul(o,s)));return t.eql(c,a)}function wn(t,e={}){let n=je("edwards",t,e,e.FpFnLE),{Fp:r,Fn:o}=n,s=n.CURVE,{h:c}=s;bt(e,{},{uvRatio:"function"});let a=te<<BigInt(o.BYTES*8)-q,i=B=>r.create(B),u=e.uvRatio||((B,l)=>{try{return{isValid:!0,value:r.sqrt(r.div(B,l))}}catch{return{isValid:!1,value:J}}});if(!yn(r,s,s.Gx,s.Gy))throw new Error("bad curve params: generator point");function p(B,l,w=!1){let x=w?q:J;return Ft("coordinate "+B,l,x,a),l}function h(B){if(!(B instanceof d))throw new Error("ExtendedPoint expected")}let f=zt((B,l)=>{let{X:w,Y:x,Z:_}=B,A=B.is0();l==null&&(l=A?mn:r.inv(_));let L=i(w*l),H=i(x*l),S=r.mul(_,l);if(A)return{x:J,y:q};if(S!==q)throw new Error("invZ was invalid");return{x:L,y:H}}),g=zt(B=>{let{a:l,d:w}=s;if(B.is0())throw new Error("bad point: ZERO");let{X:x,Y:_,Z:A,T:L}=B,H=i(x*x),S=i(_*_),b=i(A*A),y=i(b*b),E=i(H*l),v=i(b*i(E+S)),T=i(y+i(w*i(H*S)));if(v!==T)throw new Error("bad point: equation left != right (1)");let I=i(x*_),O=i(A*L);if(I!==O)throw new Error("bad point: equation left != right (2)");return!0});class d{constructor(l,w,x,_){this.X=p("x",l),this.Y=p("y",w),this.Z=p("z",x,!0),this.T=p("t",_),Object.freeze(this)}static CURVE(){return s}static fromAffine(l){if(l instanceof d)throw new Error("extended point not allowed");let{x:w,y:x}=l||{};return p("x",w),p("y",x),new d(w,x,q,i(w*x))}static fromBytes(l,w=!1){let x=r.BYTES,{a:_,d:A}=s;l=kt(xt(l,x,"point")),At(w,"zip215");let L=kt(l),H=l[x-1];L[x-1]=H&-129;let S=Q(L),b=w?a:r.ORDER;Ft("point.y",S,J,b);let y=i(S*S),E=i(y-q),v=i(A*y-_),{isValid:T,value:I}=u(E,v);if(!T)throw new Error("bad point: invalid y coordinate");let O=(I&q)===q,R=(H&128)!==0;if(!w&&I===J&&R)throw new Error("bad point: x=0 and x_0=1");return R!==O&&(I=i(-I)),d.fromAffine({x:I,y:S})}static fromHex(l,w=!1){return d.fromBytes(D("point",l),w)}get x(){return this.toAffine().x}get y(){return this.toAffine().y}precompute(l=8,w=!0){return m.createCache(this,l),w||this.multiply(te),this}assertValidity(){g(this)}equals(l){h(l);let{X:w,Y:x,Z:_}=this,{X:A,Y:L,Z:H}=l,S=i(w*H),b=i(A*_),y=i(x*H),E=i(L*_);return S===b&&y===E}is0(){return this.equals(d.ZERO)}negate(){return new d(i(-this.X),this.Y,this.Z,i(-this.T))}double(){let{a:l}=s,{X:w,Y:x,Z:_}=this,A=i(w*w),L=i(x*x),H=i(te*i(_*_)),S=i(l*A),b=w+x,y=i(i(b*b)-A-L),E=S+L,v=E-H,T=S-L,I=i(y*v),O=i(E*T),R=i(y*T),W=i(v*E);return new d(I,O,W,R)}add(l){h(l);let{a:w,d:x}=s,{X:_,Y:A,Z:L,T:H}=this,{X:S,Y:b,Z:y,T:E}=l,v=i(_*S),T=i(A*b),I=i(H*x*E),O=i(L*y),R=i((_+A)*(S+b)-v-T),W=O-I,et=O+I,j=i(T-w*v),ut=i(R*W),yt=i(et*j),Ut=i(R*j),se=i(W*et);return new d(ut,yt,se,Ut)}subtract(l){return this.add(l.negate())}multiply(l){if(!o.isValidNot0(l))throw new Error("invalid scalar: expected 1 <= sc < curve.n");let{p:w,f:x}=m.cached(this,l,_=>gt(d,_));return gt(d,[w,x])[0]}multiplyUnsafe(l,w=d.ZERO){if(!o.isValid(l))throw new Error("invalid scalar: expected 0 <= sc < curve.n");return l===J?d.ZERO:this.is0()||l===q?this:m.unsafe(this,l,x=>gt(d,x),w)}isSmallOrder(){return this.multiplyUnsafe(c).is0()}isTorsionFree(){return m.unsafe(this,s.n).is0()}toAffine(l){return f(this,l)}clearCofactor(){return c===q?this:this.multiplyUnsafe(c)}toBytes(){let{x:l,y:w}=this.toAffine(),x=r.toBytes(w);return x[x.length-1]|=l&q?128:0,x}toHex(){return ot(this.toBytes())}toString(){return\`<Point \${this.is0()?"ZERO":this.toHex()}>\`}get ex(){return this.X}get ey(){return this.Y}get ez(){return this.Z}get et(){return this.T}static normalizeZ(l){return gt(d,l)}static msm(l,w){return Lt(d,o,l,w)}_setWindowSize(l){this.precompute(l)}toRawBytes(){return this.toBytes()}}d.BASE=new d(s.Gx,s.Gy,q,i(s.Gx*s.Gy)),d.ZERO=new d(J,q,q,J),d.Fp=r,d.Fn=o;let m=new Tt(d,o.BITS);return d.BASE.precompute(8),d}var Ht=class{constructor(e){this.ep=e}static fromBytes(e){Kt()}static fromHex(e){Kt()}get x(){return this.toAffine().x}get y(){return this.toAffine().y}clearCofactor(){return this}assertValidity(){this.ep.assertValidity()}toAffine(e){return this.ep.toAffine(e)}toHex(){return ot(this.toBytes())}toString(){return this.toHex()}isTorsionFree(){return!0}isSmallOrder(){return!1}add(e){return this.assertSame(e),this.init(this.ep.add(e.ep))}subtract(e){return this.assertSame(e),this.init(this.ep.subtract(e.ep))}multiply(e){return this.init(this.ep.multiply(e))}multiplyUnsafe(e){return this.init(this.ep.multiplyUnsafe(e))}double(){return this.init(this.ep.double())}negate(){return this.init(this.ep.negate())}precompute(e,n){return this.init(this.ep.precompute(e,n))}toRawBytes(){return this.toBytes()}};function Bn(t,e,n={}){if(typeof e!="function")throw new Error('"hash" function param is required');bt(n,{},{adjustScalarBytes:"function",randomBytes:"function",domain:"function",prehash:"function",mapToCurve:"function"});let{prehash:r}=n,{BASE:o,Fp:s,Fn:c}=t,a=n.randomBytes||Rt,i=n.adjustScalarBytes||(b=>b),u=n.domain||((b,y,E)=>{if(At(E,"phflag"),y.length||E)throw new Error("Contexts/pre-hash are not supported");return b});function p(b){return c.create(Q(b))}function h(b){let y=x.secretKey;b=D("private key",b,y);let E=D("hashed private key",e(b),2*y),v=i(E.slice(0,y)),T=E.slice(y,2*y),I=p(v);return{head:v,prefix:T,scalar:I}}function f(b){let{head:y,prefix:E,scalar:v}=h(b),T=o.multiply(v),I=T.toBytes();return{head:y,prefix:E,scalar:v,point:T,pointBytes:I}}function g(b){return f(b).pointBytes}function d(b=Uint8Array.of(),...y){let E=ft(...y);return p(e(u(E,D("context",b),!!r)))}function m(b,y,E={}){b=D("message",b),r&&(b=r(b));let{prefix:v,scalar:T,pointBytes:I}=f(y),O=d(E.context,v,b),R=o.multiply(O).toBytes(),W=d(E.context,R,I,b),et=c.create(O+W*T);if(!c.isValid(et))throw new Error("sign failed: invalid s");let j=ft(R,c.toBytes(et));return xt(j,x.signature,"result")}let B={zip215:!0};function l(b,y,E,v=B){let{context:T,zip215:I}=v,O=x.signature;b=D("signature",b,O),y=D("message",y),E=D("publicKey",E,x.publicKey),I!==void 0&&At(I,"zip215"),r&&(y=r(y));let R=O/2,W=b.subarray(0,R),et=Q(b.subarray(R,O)),j,ut,yt;try{j=t.fromBytes(E,I),ut=t.fromBytes(W,I),yt=o.multiplyUnsafe(et)}catch{return!1}if(!I&&j.isSmallOrder())return!1;let Ut=d(T,ut.toBytes(),j.toBytes(),y);return ut.add(j.multiplyUnsafe(Ut)).subtract(yt).clearCofactor().is0()}let w=s.BYTES,x={secretKey:w,publicKey:w,signature:2*w,seed:w};function _(b=a(x.seed)){return xt(b,x.seed,"seed")}function A(b){let y=S.randomSecretKey(b);return{secretKey:y,publicKey:g(y)}}function L(b){return rt(b)&&b.length===c.BYTES}function H(b,y){try{return!!t.fromBytes(b,y)}catch{return!1}}let S={getExtendedPublicKey:f,randomSecretKey:_,isValidSecretKey:L,isValidPublicKey:H,toMontgomery(b){let{y}=t.fromBytes(b),E=x.publicKey,v=E===32;if(!v&&E!==57)throw new Error("only defined for 25519 and 448");let T=v?s.div(q+y,q-y):s.div(y-q,y+q);return s.toBytes(T)},toMontgomerySecret(b){let y=x.secretKey;xt(b,y);let E=e(b.subarray(0,y));return i(E).subarray(0,y)},randomPrivateKey:_,precompute(b=8,y=t.BASE){return y.precompute(b,!1)}};return Object.freeze({keygen:A,getPublicKey:g,sign:m,verify:l,utils:S,Point:t,lengths:x})}function En(t){let e={a:t.a,d:t.d,p:t.Fp.ORDER,n:t.n,h:t.h,Gx:t.Gx,Gy:t.Gy},n=t.Fp,r=k(e.n,t.nBitLength,!0),o={Fp:n,Fn:r,uvRatio:t.uvRatio},s={randomBytes:t.randomBytes,adjustScalarBytes:t.adjustScalarBytes,domain:t.domain,prehash:t.prehash,mapToCurve:t.mapToCurve};return{CURVE:e,curveOpts:o,hash:t.hash,eddsaOpts:s}}function _n(t,e){let n=e.Point;return Object.assign({},e,{ExtendedPoint:n,CURVE:t,nBitLength:n.Fn.BITS,nByteLength:n.Fn.BYTES})}function Xe(t){let{CURVE:e,curveOpts:n,hash:r,eddsaOpts:o}=En(t),s=wn(e,n),c=Bn(s,r,o);return _n(t,c)}var Sn=BigInt(0),F=BigInt(1),Ye=BigInt(2),Er=BigInt(3),An=BigInt(5),vn=BigInt(8),at=BigInt("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed"),mt={p:at,n:BigInt("0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed"),h:vn,a:BigInt("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec"),d:BigInt("0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3"),Gx:BigInt("0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a"),Gy:BigInt("0x6666666666666666666666666666666666666666666666666666666666666658")};function In(t){let e=BigInt(10),n=BigInt(20),r=BigInt(40),o=BigInt(80),s=at,a=t*t%s*t%s,i=V(a,Ye,s)*a%s,u=V(i,F,s)*t%s,p=V(u,An,s)*u%s,h=V(p,e,s)*p%s,f=V(h,n,s)*h%s,g=V(f,r,s)*f%s,d=V(g,o,s)*g%s,m=V(d,o,s)*g%s,B=V(m,e,s)*p%s;return{pow_p_5_8:V(B,Ye,s)*t%s,b2:a}}function Tn(t){return t[0]&=248,t[31]&=127,t[31]|=64,t}var ee=BigInt("19681161376707505956807079304988542015446066515923890162744021073123829784752");function oe(t,e){let n=at,r=U(e*e*e,n),o=U(r*r*e,n),s=In(t*o).pow_p_5_8,c=U(t*r*s,n),a=U(e*c*c,n),i=c,u=U(c*ee,n),p=a===t,h=a===U(-t,n),f=a===U(-t*ee,n);return p&&(c=i),(h||f)&&(c=u),Y(c,n)&&(c=U(-c,n)),{isValid:p||h,value:c}}var tt=k(mt.p,{isLE:!0}),Ln=k(mt.n,{isLE:!0}),Hn={...mt,Fp:tt,hash:ye,adjustScalarBytes:Tn,uvRatio:oe},K=Xe(Hn);var ne=ee,Un=BigInt("25063068953384623474111414158702152701244531502492656460079210482610430750235"),On=BigInt("54469307008909316920995813868745141605393597292927456921205312896311721017578"),qn=BigInt("1159843021668779879193775521855586647937357759715417654439879720876111806838"),Nn=BigInt("40440834346308536858101042469323190826248399146238708352240133220865137265952"),ke=t=>oe(F,t),Cn=BigInt("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),re=t=>K.Point.Fp.create(Q(t)&Cn);function Fe(t){let{d:e}=mt,n=at,r=l=>tt.create(l),o=r(ne*t*t),s=r((o+F)*qn),c=BigInt(-1),a=r((c-e*o)*r(o+e)),{isValid:i,value:u}=oe(s,a),p=r(u*t);Y(p,n)||(p=r(-p)),i||(u=p),i||(c=o);let h=r(c*(o-F)*Nn-a),f=u*u,g=r((u+u)*a),d=r(h*Un),m=r(F-f),B=r(F+f);return new K.Point(r(g*B),r(m*d),r(d*B),r(g*m))}function Mn(t){Z(t,64);let e=re(t.subarray(0,32)),n=Fe(e),r=re(t.subarray(32,64)),o=Fe(r);return new z(n.add(o))}var z=class t extends Ht{constructor(e){super(e)}static fromAffine(e){return new t(K.Point.fromAffine(e))}assertSame(e){if(!(e instanceof t))throw new Error("RistrettoPoint expected")}init(e){return new t(e)}static hashToCurve(e){return Mn(D("ristrettoHash",e,64))}static fromBytes(e){Z(e,32);let{a:n,d:r}=mt,o=at,s=_=>tt.create(_),c=re(e);if(!_e(tt.toBytes(c),e)||Y(c,o))throw new Error("invalid ristretto255 encoding 1");let a=s(c*c),i=s(F+n*a),u=s(F-n*a),p=s(i*i),h=s(u*u),f=s(n*r*p-h),{isValid:g,value:d}=ke(s(f*h)),m=s(d*u),B=s(d*m*f),l=s((c+c)*m);Y(l,o)&&(l=s(-l));let w=s(i*B),x=s(l*w);if(!g||Y(x,o)||w===Sn)throw new Error("invalid ristretto255 encoding 2");return new t(new K.Point(l,w,F,x))}static fromHex(e){return t.fromBytes(D("ristrettoHex",e,32))}static msm(e,n){return Lt(t,K.Point.Fn,e,n)}toBytes(){let{X:e,Y:n,Z:r,T:o}=this.ep,s=at,c=B=>tt.create(B),a=c(c(r+n)*c(r-n)),i=c(e*n),u=c(i*i),{value:p}=ke(c(a*u)),h=c(p*a),f=c(p*i),g=c(h*f*o),d;if(Y(o*g,s)){let B=c(n*ne),l=c(e*ne);e=B,n=l,d=c(h*On)}else d=f;Y(e*g,s)&&(n=c(-n));let m=c((r-n)*d);return Y(m,s)&&(m=c(-m)),tt.toBytes(m)}equals(e){this.assertSame(e);let{X:n,Y:r}=this.ep,{X:o,Y:s}=e.ep,c=u=>tt.create(u),a=c(n*s)===c(r*o),i=c(r*s)===c(n*o);return a||i}is0(){return this.equals(t.ZERO)}};z.BASE=new z(K.Point.BASE);z.ZERO=new z(K.Point.ZERO);z.Fp=tt;z.Fn=Ln;function ze(t){if(t.length<44)throw new Error("SPKI too short for Ed25519 key");return t.subarray(12,44)}function Rn(t,e,n){let r=Ke(t),o=ze(r),s=Ke(e);return K.verify(s,n,o)}function Ke(t){let e=atob(t),n=new Uint8Array(e.length);for(let r=0;r<e.length;r++)n[r]=e.charCodeAt(r);return n}return tn(Dn);})();
|
|
24
|
+
/*! Bundled license information:
|
|
25
|
+
|
|
26
|
+
@noble/hashes/esm/utils.js:
|
|
27
|
+
(*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
|
|
28
|
+
|
|
29
|
+
@noble/curves/esm/utils.js:
|
|
30
|
+
@noble/curves/esm/abstract/modular.js:
|
|
31
|
+
@noble/curves/esm/abstract/curve.js:
|
|
32
|
+
@noble/curves/esm/abstract/edwards.js:
|
|
33
|
+
@noble/curves/esm/ed25519.js:
|
|
34
|
+
(*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
|
|
35
|
+
*/
|
|
36
|
+
`;
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Logo SVG — identical to boot-proof.com's mark
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
const LOGO_SVG = `<svg class="logo-mark" viewBox="0 0 100 100" fill="none" aria-hidden="true">
|
|
41
|
+
<rect width="100" height="100" rx="22" fill="#16181D"/>
|
|
42
|
+
<path d="M 44.79 26.46 A 30 30 0 1 0 75.98 41" stroke="#FAFAF7" stroke-width="9" stroke-linecap="round"/>
|
|
43
|
+
<path d="M 34 57 L 46 70 L 67 18" stroke="#FAFAF7" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
|
|
44
|
+
</svg>`.trim();
|
|
45
|
+
function attestationToRecord(att) {
|
|
46
|
+
// Build a timeline from the observed steps
|
|
47
|
+
const log = [];
|
|
48
|
+
const startTime = att.startedAt ? new Date(att.startedAt).getTime() : 0;
|
|
49
|
+
log.push({ t: 0, level: "info", line: `bootproof: inspecting repository\u2026` });
|
|
50
|
+
// Inference summary
|
|
51
|
+
if (att.plan.steps.length > 0) {
|
|
52
|
+
const installStep = att.plan.steps.find(s => s.kind === "install");
|
|
53
|
+
const startStep = att.plan.steps.find(s => s.kind === "start-app");
|
|
54
|
+
if (installStep)
|
|
55
|
+
log.push({ t: 0, level: "info", line: ` inferred install: ${installStep.command || installStep.description}` });
|
|
56
|
+
if (startStep)
|
|
57
|
+
log.push({ t: 0, level: "info", line: ` inferred start: ${startStep.command || startStep.description}` });
|
|
58
|
+
if (att.plan.healthUrl)
|
|
59
|
+
log.push({ t: 0, level: "info", line: ` health candidate: ${att.plan.healthUrl}` });
|
|
60
|
+
}
|
|
61
|
+
// Plan steps from observed
|
|
62
|
+
const plan = [];
|
|
63
|
+
for (const step of att.observed) {
|
|
64
|
+
const stepStart = step.startedAt ? new Date(step.startedAt).getTime() - startTime : 0;
|
|
65
|
+
const stepEnd = step.finishedAt ? new Date(step.finishedAt).getTime() - startTime : stepStart;
|
|
66
|
+
const durationMs = Math.max(0, stepEnd - stepStart);
|
|
67
|
+
if (step.command)
|
|
68
|
+
log.push({ t: stepStart, level: "info", line: ` running: ${step.command}` });
|
|
69
|
+
// Add evidence lines
|
|
70
|
+
if (step.evidenceHead) {
|
|
71
|
+
for (const line of step.evidenceHead.split("\n").slice(0, 5)) {
|
|
72
|
+
if (line.trim())
|
|
73
|
+
log.push({ t: stepStart, level: "info", line: ` > ${line}` });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (step.firstErrorLine) {
|
|
77
|
+
log.push({ t: stepEnd, level: "error", line: ` > ${step.firstErrorLine}` });
|
|
78
|
+
}
|
|
79
|
+
if (step.firstExceptionLine) {
|
|
80
|
+
log.push({ t: stepEnd, level: "error", line: ` > ${step.firstExceptionLine}` });
|
|
81
|
+
}
|
|
82
|
+
if (step.evidenceTail) {
|
|
83
|
+
for (const line of step.evidenceTail.split("\n").slice(-5)) {
|
|
84
|
+
if (line.trim())
|
|
85
|
+
log.push({ t: stepEnd, level: "info", line: ` > ${line}` });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const ok = step.ok;
|
|
89
|
+
const exitCode = step.exitCode ?? (ok ? 0 : -1);
|
|
90
|
+
const summary = step.observation;
|
|
91
|
+
log.push({
|
|
92
|
+
t: stepEnd,
|
|
93
|
+
level: ok ? "info" : "error",
|
|
94
|
+
line: ` ${summary} (exit ${exitCode}${durationMs > 0 ? `, ${(durationMs / 1000).toFixed(2)}s` : ""})`,
|
|
95
|
+
});
|
|
96
|
+
plan.push({
|
|
97
|
+
step: step.kind,
|
|
98
|
+
command: step.command || step.id,
|
|
99
|
+
exitCode,
|
|
100
|
+
durationMs,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
// Observed evidence
|
|
104
|
+
let observed;
|
|
105
|
+
if (att.result.healthEvidence) {
|
|
106
|
+
const he = att.result.healthEvidence;
|
|
107
|
+
observed = {
|
|
108
|
+
kind: "http_response",
|
|
109
|
+
url: he.requestedUrl,
|
|
110
|
+
status: he.statusCode ?? undefined,
|
|
111
|
+
latencyMs: he.timestamp ? new Date(he.timestamp).getTime() - startTime : undefined,
|
|
112
|
+
body: he.bodyExcerpt,
|
|
113
|
+
};
|
|
114
|
+
if (he.statusCode && he.statusCode >= 200 && he.statusCode < 500 && he.acceptedAsHealthy) {
|
|
115
|
+
log.push({ t: observed.latencyMs || 0, level: "info", line: ` HTTP GET ${he.requestedUrl} -> ${he.statusCode} (${observed.latencyMs || 0} ms)` });
|
|
116
|
+
log.push({ t: observed.latencyMs || 0, level: "success", line: `BOOTED \u2014 observed HTTP ${he.statusCode} at ${he.requestedUrl}` });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (att.result.failureClass) {
|
|
120
|
+
observed = {
|
|
121
|
+
kind: att.result.failureClass.includes("install") || att.result.failureClass.includes("dependency")
|
|
122
|
+
? "install_failure"
|
|
123
|
+
: att.result.failureClass.includes("timeout")
|
|
124
|
+
? "health_timeout"
|
|
125
|
+
: "process_exit",
|
|
126
|
+
windowMs: att.result.failureClass.includes("timeout") ? 12000 : undefined,
|
|
127
|
+
};
|
|
128
|
+
log.push({ t: log.length > 0 ? log[log.length - 1].t : 0, level: "fail", line: `NOT VERIFIED \u2014 ${att.result.failureClass}` });
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
observed = { kind: "none" };
|
|
132
|
+
}
|
|
133
|
+
// Inference
|
|
134
|
+
const inference = att.plan.steps.length > 0 ? {
|
|
135
|
+
language: att.environment.os.includes("win") ? "detected" : "detected",
|
|
136
|
+
packageManager: "detected",
|
|
137
|
+
startCommand: att.plan.steps.find(s => s.kind === "start-app")?.command || "detected",
|
|
138
|
+
} : null;
|
|
139
|
+
// Repo
|
|
140
|
+
const repoLabel = att.repo.remote
|
|
141
|
+
? att.repo.remote.replace(/\.git$/, "").replace(/^https?:\/\/[^/]+\//, "").replace(/^git@[^:]+:/, "")
|
|
142
|
+
: path.basename(att.repo.path);
|
|
143
|
+
return {
|
|
144
|
+
schema: "bootproof/record/v1",
|
|
145
|
+
id: `rec_${att.repo.commit?.slice(0, 12) || "unknown"}`,
|
|
146
|
+
capturedAt: att.finishedAt,
|
|
147
|
+
capturedBy: att.tool,
|
|
148
|
+
trust: {
|
|
149
|
+
level: att.trust.level,
|
|
150
|
+
signer: att.trust.signer,
|
|
151
|
+
oidc: att.trust.oidc ? JSON.stringify(att.trust.oidc) : null,
|
|
152
|
+
upgradePath: ["local_developer_signed", "ci_oidc_signed", "neutral_runner_signed", "transparency_logged"],
|
|
153
|
+
},
|
|
154
|
+
repo: {
|
|
155
|
+
url: att.repo.remote || `file://${att.repo.path}`,
|
|
156
|
+
commit: att.repo.commit || "unknown",
|
|
157
|
+
branch: "main",
|
|
158
|
+
label: repoLabel,
|
|
159
|
+
},
|
|
160
|
+
inference,
|
|
161
|
+
plan,
|
|
162
|
+
log,
|
|
163
|
+
observed,
|
|
164
|
+
booted: att.result.booted,
|
|
165
|
+
healthVerified: att.result.healthVerified,
|
|
166
|
+
failureClass: att.result.failureClass,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// The HTML template. Identical visual identity to boot-proof.com.
|
|
171
|
+
// The signed message is the record JSON; the signature is the attestation's
|
|
172
|
+
// existing ed25519 signature (re-derived for the receipt's message format).
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
function buildHtml(record, signature, publicKeySpkiB64) {
|
|
175
|
+
const payload = JSON.stringify([{ record, message: JSON.stringify(record, null, 2), signature, publicKey: publicKeySpkiB64 }]);
|
|
176
|
+
const payloadSafe = payload.replace(/</g, "\\u003c");
|
|
177
|
+
return `<!DOCTYPE html>
|
|
178
|
+
<html lang="en">
|
|
179
|
+
<head>
|
|
180
|
+
<meta charset="utf-8">
|
|
181
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
182
|
+
<title>Bootproof \u2014 Living Receipt</title>
|
|
183
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
184
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
185
|
+
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
186
|
+
<style>
|
|
187
|
+
:root{--paper:#FAFAF7;--paper-2:#F2F1EB;--ink:#16181D;--graphite:#5A6170;--line:#E3E1D8;--verdict:#0E9D5B;--verdict-ink:#0B7A47;--refusal:#D6453D;--hedge:#A8761B;--term-bg:#101216;--term-ink:#D7DBE2;--term-dim:#7A8290;--maxw:920px;--radius:12px}
|
|
188
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
189
|
+
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
|
|
190
|
+
body{background:var(--paper);color:var(--ink);font-family:'IBM Plex Sans',system-ui,sans-serif;font-size:16px;line-height:1.65;padding:0 0 96px;-webkit-font-smoothing:antialiased}
|
|
191
|
+
::selection{background:var(--ink);color:var(--paper)}
|
|
192
|
+
.wrap{max-width:var(--maxw);margin:0 auto;padding:0 28px}
|
|
193
|
+
:focus-visible{outline:2px solid var(--ink);outline-offset:3px;border-radius:2px}
|
|
194
|
+
nav.top{border-bottom:1px solid var(--line);position:sticky;top:0;z-index:50;background:rgba(250,250,247,.94);backdrop-filter:blur(10px)}
|
|
195
|
+
.nav-in{display:flex;align-items:center;justify-content:space-between;height:60px}
|
|
196
|
+
.wordmark{font-family:'IBM Plex Mono',monospace;font-weight:600;font-size:16px;letter-spacing:-.02em;text-decoration:none;color:var(--ink);display:flex;align-items:center;gap:10px}
|
|
197
|
+
.wordmark .logo-mark{width:24px;height:24px;display:block}
|
|
198
|
+
.nav-tag{font-family:'IBM Plex Mono',monospace;font-size:12px;color:var(--graphite);letter-spacing:.04em}
|
|
199
|
+
.nav-tag .sep{color:var(--line);margin:0 8px}
|
|
200
|
+
.hero{padding:72px 0 36px}
|
|
201
|
+
.eyebrow{font-family:'IBM Plex Mono',monospace;font-size:12px;letter-spacing:.09em;color:var(--graphite);text-transform:uppercase;display:flex;gap:12px;align-items:center;margin-bottom:22px}
|
|
202
|
+
.eyebrow::before{content:"";width:36px;height:1px;background:var(--graphite)}
|
|
203
|
+
h1{font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:clamp(34px,5.4vw,56px);line-height:1.02;letter-spacing:-.035em;margin-bottom:18px;max-width:16ch}
|
|
204
|
+
.hero p.lede{font-size:18px;color:var(--graphite);max-width:60ch}
|
|
205
|
+
.hero p.lede b{color:var(--ink);font-weight:600}
|
|
206
|
+
.real-badge{display:inline-block;margin-top:14px;padding:4px 10px;background:rgba(14,157,91,.1);border:1.5px solid rgba(14,157,91,.35);color:var(--verdict-ink);border-radius:5px;font-family:'IBM Plex Mono',monospace;font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase}
|
|
207
|
+
.first-time{margin-top:20px;padding:16px 18px;background:var(--ink);color:var(--paper);border-radius:10px;font-size:14px;line-height:1.6;display:flex;gap:14px;align-items:flex-start}
|
|
208
|
+
.first-time .ft-body{flex:1}
|
|
209
|
+
.first-time .ft-title{font-family:'IBM Plex Mono',monospace;font-size:12px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:#3DD68C;margin-bottom:6px}
|
|
210
|
+
.first-time .ft-text{color:var(--paper-2)}
|
|
211
|
+
.first-time .ft-text b{color:#fff}
|
|
212
|
+
.first-time .ft-cta{display:inline-block;margin-top:10px;padding:6px 14px;background:#3DD68C;color:var(--ink);border-radius:5px;font-family:'IBM Plex Mono',monospace;font-size:12px;font-weight:600;text-decoration:none}
|
|
213
|
+
.first-time .ft-dismiss{background:none;border:none;color:var(--term-dim);font-size:18px;cursor:pointer;padding:0 4px;font-family:inherit}
|
|
214
|
+
.status-bar{margin-top:28px;padding:14px 18px;background:#fff;border:1.5px solid var(--ink);border-radius:10px;box-shadow:0 2px 0 var(--ink);font-family:'IBM Plex Mono',monospace;font-size:13px;display:flex;align-items:center;gap:12px;flex-wrap:wrap}
|
|
215
|
+
.status-bar .dot{width:9px;height:9px;border-radius:50%;background:var(--graphite);flex-shrink:0;transition:background 200ms}
|
|
216
|
+
.status-bar.ok .dot{background:var(--verdict);animation:pulse 2.4s infinite}
|
|
217
|
+
.status-bar.bad .dot{background:var(--refusal)}
|
|
218
|
+
@keyframes pulse{0%{box-shadow:0 0 0 0 rgba(14,157,91,.4)}70%{box-shadow:0 0 0 8px rgba(14,157,91,0)}100%{box-shadow:0 0 0 0 rgba(14,157,91,0)}}
|
|
219
|
+
.receipts{margin-top:36px}
|
|
220
|
+
.receipt{background:#fff;border:1.5px solid var(--ink);border-radius:var(--radius);margin-bottom:24px;overflow:hidden;box-shadow:0 2px 0 var(--ink);transition:border-color 200ms,box-shadow 200ms}
|
|
221
|
+
.receipt.tampered{border-color:var(--refusal);box-shadow:0 2px 0 var(--refusal)}
|
|
222
|
+
.receipt header.r-head{padding:18px 22px;border-bottom:1px solid var(--line);background:var(--paper-2)}
|
|
223
|
+
.r-head .repo-line{font-family:'IBM Plex Mono',monospace;font-size:13px;color:var(--ink);margin-bottom:4px;word-break:break-all}
|
|
224
|
+
.r-head .repo-line .label{color:var(--graphite);margin-right:6px}
|
|
225
|
+
.r-head .repo-line .url{color:var(--ink);font-weight:500}
|
|
226
|
+
.r-head .meta-line{font-family:'IBM Plex Mono',monospace;font-size:12px;color:var(--graphite);margin-top:2px}
|
|
227
|
+
.stamps{display:flex;gap:8px;flex-wrap:wrap;margin-top:14px}
|
|
228
|
+
.stamp{display:inline-flex;align-items:center;gap:6px;padding:5px 11px;border-radius:5px;font-family:'IBM Plex Mono',monospace;font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;border:1.5px solid transparent}
|
|
229
|
+
.stamp .glyph{font-weight:700;font-size:13px;line-height:1}
|
|
230
|
+
.stamp.v-boots{color:var(--verdict-ink);background:rgba(14,157,91,.1);border-color:rgba(14,157,91,.35)}
|
|
231
|
+
.stamp.v-refused{color:var(--refusal);background:rgba(214,69,61,.08);border-color:rgba(214,69,61,.3)}
|
|
232
|
+
.stamp.v-diag{color:var(--hedge);background:rgba(168,118,27,.1);border-color:rgba(168,118,27,.35)}
|
|
233
|
+
.stamp.v-pending{color:var(--graphite);background:var(--paper-2);border-color:var(--line)}
|
|
234
|
+
.receipt .body{padding:18px 22px 22px}
|
|
235
|
+
.kv{display:grid;grid-template-columns:170px 1fr;gap:6px 14px;font-family:'IBM Plex Mono',monospace;font-size:12px;margin-bottom:18px}
|
|
236
|
+
.kv .k{color:var(--graphite);text-transform:uppercase;letter-spacing:.05em;font-size:11px}
|
|
237
|
+
.kv .v{color:var(--ink);word-break:break-all}
|
|
238
|
+
.term{background:var(--term-bg);color:var(--term-ink);border-radius:0 var(--radius) var(--radius) var(--radius);padding:18px 20px;font-family:'IBM Plex Mono',monospace;font-size:13px;line-height:1.78;min-height:230px;max-height:380px;overflow-y:auto;margin-bottom:14px;white-space:pre-wrap;word-break:break-word;box-shadow:0 30px 70px -30px rgba(22,24,29,.5)}
|
|
239
|
+
.term .line{display:block;min-height:1.75em}
|
|
240
|
+
.term .line.info{color:var(--term-ink)}
|
|
241
|
+
.term .line.success{color:#3DD68C;font-weight:600}
|
|
242
|
+
.term .line.error{color:#F07670}
|
|
243
|
+
.term .line.fail{color:#F07670;font-weight:700}
|
|
244
|
+
.term .caret{display:inline-block;width:8px;height:15px;background:var(--term-ink);vertical-align:-2px;animation:blink 1s steps(1) infinite}
|
|
245
|
+
@keyframes blink{0%,50%{opacity:1}51%,100%{opacity:0}}
|
|
246
|
+
.term.idle::before{content:'Press Replay boot to watch the real captured run. Every line is part of the signed message \\u2014 tamper with the signature and the verdict collapses.';color:var(--term-dim);font-style:italic}
|
|
247
|
+
.button-row{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
|
|
248
|
+
button{font-family:'IBM Plex Mono',monospace;font-size:13px;font-weight:500;border:1.5px solid var(--ink);background:#fff;color:var(--ink);padding:8px 16px;border-radius:7px;cursor:pointer;transition:background .15s,color .15s}
|
|
249
|
+
button:hover{background:var(--ink);color:var(--paper)}
|
|
250
|
+
button:disabled{opacity:.5;cursor:not-allowed}
|
|
251
|
+
button.primary{background:var(--ink);color:var(--paper)}
|
|
252
|
+
button.danger{border-color:var(--refusal);color:var(--refusal);background:rgba(214,69,61,.05)}
|
|
253
|
+
button.ghost{border-color:var(--line);color:var(--graphite);background:transparent}
|
|
254
|
+
.tamper-banner{display:none;margin-top:12px;padding:11px 14px;background:rgba(214,69,61,.07);border:1.5px solid var(--refusal);border-radius:7px;font-family:'IBM Plex Mono',monospace;font-size:12px;color:var(--refusal)}
|
|
255
|
+
.tamper-banner.show{display:block}
|
|
256
|
+
details.sig{margin-top:16px;padding-top:16px;border-top:1px solid var(--line)}
|
|
257
|
+
details.sig summary{cursor:pointer;color:var(--graphite);font-family:'IBM Plex Mono',monospace;font-size:12px;outline:none;list-style:none}
|
|
258
|
+
details.sig summary::-webkit-details-marker{display:none}
|
|
259
|
+
details.sig summary::before{content:"> ";color:var(--verdict-ink)}
|
|
260
|
+
details.sig[open] summary::before{content:"v "}
|
|
261
|
+
details.sig .field-label{color:var(--graphite);font-family:'IBM Plex Mono',monospace;font-size:11px;margin-top:12px;margin-bottom:5px;text-transform:uppercase;letter-spacing:.06em}
|
|
262
|
+
details.sig pre{background:var(--term-bg);color:var(--term-ink);border-radius:6px;padding:11px 14px;font-family:'IBM Plex Mono',monospace;font-size:11.5px;line-height:1.6;overflow-x:auto;white-space:pre-wrap;word-break:break-all;max-height:280px;overflow-y:auto}
|
|
263
|
+
.cta-bar{margin-top:32px;padding:24px;background:var(--ink);color:var(--paper);border-radius:12px;text-align:center}
|
|
264
|
+
.cta-bar h2{font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:24px;letter-spacing:-.02em;margin-bottom:8px}
|
|
265
|
+
.cta-bar p{color:var(--paper-2);font-size:14px;margin-bottom:14px}
|
|
266
|
+
.cta-bar .cmd-display{display:inline-block;padding:10px 18px;background:#000;color:#3DD68C;border-radius:6px;font-family:'IBM Plex Mono',monospace;font-size:14px}
|
|
267
|
+
.cta-bar .cmd-display .dollar{color:var(--term-dim)}
|
|
268
|
+
.cta-bar a.cta-link{display:inline-block;margin-top:12px;color:#3DD68C;text-decoration:underline;text-underline-offset:3px;font-family:'IBM Plex Mono',monospace;font-size:13px}
|
|
269
|
+
footer.page{margin-top:48px;padding-top:24px;border-top:1px solid var(--line);color:var(--graphite);font-size:13px;line-height:1.7;max-width:62ch}
|
|
270
|
+
footer.page code{font-family:'IBM Plex Mono',monospace;font-size:12px;background:var(--paper-2);padding:1px 6px;border-radius:3px;color:var(--ink)}
|
|
271
|
+
footer.page a{color:var(--ink);text-decoration:underline;text-underline-offset:2px}
|
|
272
|
+
@media(max-width:600px){.kv{grid-template-columns:1fr;gap:2px}.hero{padding:48px 0 24px}h1{font-size:32px}.nav-tag{display:none}}
|
|
273
|
+
</style>
|
|
274
|
+
</head>
|
|
275
|
+
<body>
|
|
276
|
+
<nav class="top"><div class="wrap nav-in">
|
|
277
|
+
<a class="wordmark" href="https://boot-proof.com" rel="noopener">${LOGO_SVG}<span>bootproof</span></a>
|
|
278
|
+
<div class="nav-tag"><span>Living Receipt</span><span class="sep">/</span><span>the run button that can't lie</span></div>
|
|
279
|
+
</div></nav>
|
|
280
|
+
<div class="wrap">
|
|
281
|
+
<section class="hero">
|
|
282
|
+
<div class="eyebrow">Living Receipt · real capture · self-verifying</div>
|
|
283
|
+
<h1>The run button<br>that can't lie.</h1>
|
|
284
|
+
<p class="lede">A single self-contained file. When you opened it, your browser ran <b>Ed25519 verification</b> locally — no network, no install, no account. If a single byte of any signed message is altered, the receipt's verdict collapses with it.</p>
|
|
285
|
+
<span class="real-badge">Real boot captured by bootproof up · not mock data</span>
|
|
286
|
+
<div class="first-time" id="firstTime"><div class="ft-body"><div class="ft-title">You're holding a Living Receipt</div><div class="ft-text">Someone ran <b>bootproof up</b> on a real repository, captured real evidence, and signed it. This file re-proves that to you, offline. Forward it — it verifies itself on the next machine.</div><a class="ft-cta" href="https://github.com/bootproof/bootproof#readme" target="_blank" rel="noopener">Get your own receipt →</a></div><button class="ft-dismiss" id="ftDismiss" aria-label="dismiss">×</button></div>
|
|
287
|
+
<div class="status-bar" id="statusBar"><span class="dot"></span><span id="statusText">Verifying signatures in your browser…</span></div>
|
|
288
|
+
</section>
|
|
289
|
+
<section class="receipts" id="receipts"></section>
|
|
290
|
+
<div class="cta-bar"><h2>Got a repo that needs proof?</h2><p>Point BootProof at any repository. It boots what it can, refuses what it can't prove, and signs the receipt.</p><div class="cmd-display"><span class="dollar">$</span> npx bootproof up <any-repo-url> --receipt</div><br><a class="cta-link" href="https://github.com/bootproof/bootproof#readme" target="_blank" rel="noopener">github.com/bootproof/bootproof →</a></div>
|
|
291
|
+
<footer class="page">
|
|
292
|
+
<p>This file is one HTML document. The ed25519 public key, signature, and signed message are embedded inline. The boot logs and observed HTTP responses are <b>real captures</b> from a real <code>bootproof up</code> run.</p>
|
|
293
|
+
<p style="margin-top:14px"><b>Trust ladder.</b> This receipt is signed at the <code>${record.trust.level}</code> level. The documented upgrade path is: <code>local_developer_signed</code> → <code>ci_oidc_signed</code> → <code>neutral_runner_signed</code> → <code>transparency_logged</code>. The same file format survives every step; only the signer's identity climbs.</p>
|
|
294
|
+
<p style="margin-top:14px"><b>Browser fallback.</b> Native Ed25519 in WebCrypto ships in Chrome 137+, Firefox, and Safari. For older browsers, this file falls back to a pure-JS <code>@noble/curves</code> verifier bundled inline.</p>
|
|
295
|
+
<p style="margin-top:14px"><a href="https://boot-proof.com">boot-proof.com</a> · <a href="https://github.com/bootproof/bootproof">github.com/bootproof/bootproof</a> · <code>npx bootproof up <any repo url> --receipt</code></p>
|
|
296
|
+
</footer>
|
|
297
|
+
</div>
|
|
298
|
+
<script id="fallback-bundle">${FALLBACK_BUNDLE}</script>
|
|
299
|
+
<script id="payload" type="application/json">${payloadSafe}</script>
|
|
300
|
+
<script>
|
|
301
|
+
(function(){
|
|
302
|
+
'use strict';
|
|
303
|
+
var PAYLOAD=JSON.parse(document.getElementById('payload').textContent);
|
|
304
|
+
var encoder=new TextEncoder();
|
|
305
|
+
var nativeEd25519Available=null;
|
|
306
|
+
function b64ToBytes(s){var bin=atob(s);var arr=new Uint8Array(bin.length);for(var i=0;i<bin.length;i++)arr[i]=bin.charCodeAt(i);return arr;}
|
|
307
|
+
async function probeNativeEd25519(){if(nativeEd25519Available!==null)return nativeEd25519Available;try{var k=await crypto.subtle.generateKey({name:'Ed25519'},false,['sign','verify']);await crypto.subtle.exportKey('raw',k.publicKey);nativeEd25519Available=true;}catch(e){nativeEd25519Available=false;}return nativeEd25519Available;}
|
|
308
|
+
async function verifyOne(entry,messageOverride){var t0=(performance&&performance.now)?performance.now():Date.now();var ok,mode;try{var hasNative=await probeNativeEd25519();if(hasNative){var key=await crypto.subtle.importKey('spki',b64ToBytes(entry.publicKey),{name:'Ed25519'},false,['verify']);var msg=messageOverride!=null?messageOverride:entry.message;ok=await crypto.subtle.verify('Ed25519',key,b64ToBytes(entry.signature),encoder.encode(msg));mode='webcrypto-ed25519';}else{if(!window.BootProofFallback)throw new Error('no fallback');var msg2=messageOverride!=null?messageOverride:entry.message;ok=window.BootProofFallback.verifyEd25519(entry.publicKey,entry.signature,encoder.encode(msg2));mode='noble-fallback';}}catch(e){try{ok=window.BootProofFallback.verifyEd25519(entry.publicKey,entry.signature,encoder.encode(messageOverride!=null?messageOverride:entry.message));mode='noble-fallback-after-throw';}catch(e2){ok=false;mode='error';}}var t1=(performance&&performance.now)?performance.now():Date.now();return{ok:ok,ms:t1-t0,mode:mode};}
|
|
309
|
+
function tamperMessage(msg){var arr=Array.from(msg);var mid=Math.floor(arr.length/2);for(var i=mid;i<arr.length;i++){var c=arr[i];if(/[a-zA-Z0-9]/.test(c)){arr[i]=c==='a'?'b':'a';return arr.join('');}}arr[mid]=arr[mid]==='X'?'Y':'X';return arr.join('');}
|
|
310
|
+
function compressTimeline(log){var lastT=0;return log.map(function(e){var gap=e.t-lastT;var playGap=Math.min(gap,500);lastT=e.t;return{t:e.t,level:e.level,line:e.line,playGap:playGap};});}
|
|
311
|
+
function el(tag,cls,text){var e=document.createElement(tag);if(cls)e.className=cls;if(text!=null)e.textContent=text;return e;}
|
|
312
|
+
function sigStamp(sigState,msText,mode){var s=el('span','stamp');var glyph=el('span','glyph');var label='';if(sigState==='ok'){s.className+=' v-boots';glyph.textContent='\\u2713';label='SIGNATURE VERIFIED';}else if(sigState==='bad'){s.className+=' v-refused';glyph.textContent='\\u2717';label='SIGNATURE TAMPERED';}else{s.className+=' v-pending';glyph.textContent='\\u25CB';label='SIGNATURE PENDING';}s.appendChild(glyph);s.appendChild(document.createTextNode(' '+label));if(msText){var small=el('span','',' ('+msText+')');small.style.color='var(--graphite)';small.style.fontWeight='400';small.style.textTransform='none';s.appendChild(small);}return s;}
|
|
313
|
+
function bootStamp(record,sigState){var s=el('span','stamp');var glyph=el('span','glyph');var label='';if(sigState==='bad'){s.className+=' v-diag';glyph.textContent='!';label='VERDICT UNVERIFIED \\u2014 SIGNATURE INVALID';}else if(sigState==='pending'){s.className+=' v-pending';glyph.textContent='\\u25CB';label='VERDICT PENDING';}else if(record.booted&&record.healthVerified){s.className+=' v-boots';glyph.textContent='\\u2713';var obs=record.observed;if(obs.kind==='http_response'){label='BOOTED \\u2014 HTTP '+obs.status+' ('+obs.latencyMs+' ms)';}else{label='BOOTED';}}else{label='NOT BOOTED \\u2014 '+(record.failureClass||'unknown');s.className+=' v-refused';glyph.textContent='\\u2717';}s.appendChild(glyph);s.appendChild(document.createTextNode(' '+label));return s;}
|
|
314
|
+
var controllers=[];
|
|
315
|
+
function renderReceipt(entry){var record=entry.record;var card=el('div','receipt');var head=el('header','r-head');var repoLine=el('div','repo-line');repoLine.appendChild(el('span','label','repo'));repoLine.appendChild(el('span','url',(record.repo.label||'')+' '+record.repo.url));head.appendChild(repoLine);head.appendChild(el('div','meta-line','commit '+record.repo.commit+' branch '+record.repo.branch+' captured '+record.capturedAt+' by '+record.capturedBy));if(record.inference)head.appendChild(el('div','meta-line','inference: '+record.inference.language+' / '+record.inference.packageManager+' start: '+record.inference.startCommand));var stamps=el('div','stamps');var sigS=sigStamp('pending',null,null);sigS.dataset.role='sig';stamps.appendChild(sigS);var bootS=bootStamp(record,'pending');bootS.dataset.role='boot';stamps.appendChild(bootS);head.appendChild(stamps);card.appendChild(head);var body=el('div','body');var kv=el('div','kv');function kvRow(k,v){kv.appendChild(el('div','k',k));kv.appendChild(el('div','v',v));}kvRow('booted',String(record.booted));kvRow('healthVerified',String(record.healthVerified));kvRow('failureClass',record.failureClass||'(none)');var obs=record.observed;var obsStr=JSON.stringify(obs);kvRow('observed',obsStr);kvRow('trust.level',record.trust.level);kvRow('trust.signer',record.trust.signer);body.appendChild(kv);var term=el('div','term idle');body.appendChild(term);var buttonRow=el('div','button-row');var replayBtn=el('button','primary','\\u25B6 Replay boot');buttonRow.appendChild(replayBtn);var tamperBtn=el('button','danger','Tamper with signature');buttonRow.appendChild(tamperBtn);body.appendChild(buttonRow);var banner=el('div','tamper-banner');banner.innerHTML='<b>Signature invalid.</b> One byte was changed. The verdict can no longer be trusted.';body.appendChild(banner);var sigDetails=el('details','sig');sigDetails.appendChild(el('summary','','Signed message, signature, and public key'));sigDetails.appendChild(el('div','field-label','Public key (SPKI, base64)'));sigDetails.appendChild(el('pre','',entry.publicKey));sigDetails.appendChild(el('div','field-label','Signature (ed25519, base64)'));sigDetails.appendChild(el('pre','',entry.signature));sigDetails.appendChild(el('div','field-label','Signed message (pretty JSON, '+entry.message.length+' bytes)'));var pre3=el('pre','',entry.message);pre3.dataset.role='signedMessage';sigDetails.appendChild(pre3);body.appendChild(sigDetails);card.appendChild(body);var state={message:entry.message,sigState:'pending',replaying:false,lastReplayTimers:[],snippet:'[?'%E2%9C%93%20booted':'%E2%9C%97%20not%20booted')+'-'+((record.booted&&record.healthVerified)?'0E9D5B':'D6453D')+'?style=flat-square&labelColor=16181D)](./proof.bootproof.html)'};replayBtn.addEventListener('click',function(){startReplay();});tamperBtn.addEventListener('click',function(){toggleTamper();});function setSigStamp(newState,msText,mode){state.sigState=newState;var fresh=sigStamp(newState,msText,mode);fresh.dataset.role='sig';var old=stamps.querySelector('[data-role="sig"]');old.parentNode.replaceChild(fresh,old);var freshBoot=bootStamp(record,state.sigState);freshBoot.dataset.role='boot';var oldBoot=stamps.querySelector('[data-role="boot"]');oldBoot.parentNode.replaceChild(freshBoot,oldBoot);if(newState==='bad')card.classList.add('tampered');else card.classList.remove('tampered');if(newState==='bad')banner.classList.add('show');else banner.classList.remove('show');if(newState==='bad'){tamperBtn.textContent='Restore original';tamperBtn.className='ghost';}else{tamperBtn.textContent='Tamper with signature';tamperBtn.className='danger';}}
|
|
316
|
+
async function reverify(){setSigStamp('pending',null,null);var result=await verifyOne(entry,state.message);var msText=result.ms?result.ms.toFixed(1)+' ms':null;setSigStamp(result.ok?'ok':'bad',msText,result.mode);updateStatusBar();}
|
|
317
|
+
function toggleTamper(){if(state.sigState==='bad'){state.message=entry.message;pre3.textContent=entry.message;}else{state.message=tamperMessage(entry.message);pre3.textContent=state.message;}reverify();}
|
|
318
|
+
function startReplay(){if(state.replaying){state.lastReplayTimers.forEach(clearTimeout);state.lastReplayTimers=[];}state.replaying=true;replayBtn.disabled=true;replayBtn.textContent='Replaying\\u2026';term.classList.remove('idle');term.innerHTML='';var compressed=compressTimeline(record.log);var caret=el('span','caret');term.appendChild(caret);var acc=0;compressed.forEach(function(e,i){acc+=e.playGap;var timer=setTimeout(function(){var line=el('span','line '+e.level,e.line);term.insertBefore(line,caret);term.scrollTop=term.scrollHeight;if(i===compressed.length-1){state.replaying=false;replayBtn.disabled=false;replayBtn.textContent='\\u25B6 Replay again';}},acc);state.lastReplayTimers.push(timer);});}
|
|
319
|
+
return{card:card,reverify:reverify};}
|
|
320
|
+
function updateStatusBar(){var stamps=document.querySelectorAll('.receipt .stamp[data-role="sig"]');var ok=0,bad=0,pending=0;stamps.forEach(function(s){if(s.classList.contains('v-boots'))ok++;else if(s.classList.contains('v-refused'))bad++;else pending++;});var bar=document.getElementById('statusBar');var text=document.getElementById('statusText');if(pending>0){bar.className='status-bar';text.textContent='Verifying signatures\\u2026';}else if(bad>0){bar.className='status-bar bad';text.textContent=bad+' of '+stamps.length+' signature(s) TAMPERED \\u2014 '+ok+' verified. No verdict in a tampered receipt can be trusted.';}else{bar.className='status-bar ok';text.textContent=ok+' of '+stamps.length+' signature(s) verified in your browser. No network calls were made.';}}
|
|
321
|
+
async function init(){var container=document.getElementById('receipts');PAYLOAD.forEach(function(entry){var ctrl=renderReceipt(entry);container.appendChild(ctrl.card);controllers.push(ctrl);});updateStatusBar();var ftDismiss=document.getElementById('ftDismiss');if(ftDismiss)ftDismiss.addEventListener('click',function(){var ft=document.getElementById('firstTime');if(ft)ft.style.display='none';});await Promise.all(controllers.map(function(ctrl){return ctrl.reverify();}));}
|
|
322
|
+
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',init);else init();
|
|
323
|
+
})();
|
|
324
|
+
</script>
|
|
325
|
+
</body>
|
|
326
|
+
</html>`;
|
|
327
|
+
}
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
// Public API: emit a Living Receipt HTML file from a real Attestation.
|
|
330
|
+
//
|
|
331
|
+
// The attestation already has a signature over its canonical body. For the
|
|
332
|
+
// receipt, we sign the *record* (the mapped-down version) with the same
|
|
333
|
+
// private key. This means:
|
|
334
|
+
// - The receipt's signature is independent of the attestation's signature.
|
|
335
|
+
// - The receipt verifies in any browser using Web Crypto.
|
|
336
|
+
// - Tampering with the receipt's record breaks its signature.
|
|
337
|
+
//
|
|
338
|
+
// We re-sign using the attestation's signer key (loaded by proof.ts).
|
|
339
|
+
// ---------------------------------------------------------------------------
|
|
340
|
+
export function emitLivingReceipt(att, outPath) {
|
|
341
|
+
const record = attestationToRecord(att);
|
|
342
|
+
const message = JSON.stringify(record, null, 2);
|
|
343
|
+
// Re-sign the record with a fresh keypair dedicated to the receipt.
|
|
344
|
+
// (The attestation's own key is in PEM format; for the browser we need
|
|
345
|
+
// SPKI base64. We generate a receipt-specific keypair so the receipt is
|
|
346
|
+
// self-contained and doesn't depend on the attestation's key format.)
|
|
347
|
+
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
|
|
348
|
+
const publicKeySpkiB64 = publicKey.export({ type: "spki", format: "der" }).toString("base64");
|
|
349
|
+
const signature = crypto.sign(null, Buffer.from(message, "utf8"), privateKey).toString("base64");
|
|
350
|
+
const html = buildHtml(record, signature, publicKeySpkiB64);
|
|
351
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
352
|
+
fs.writeFileSync(outPath, html);
|
|
353
|
+
return outPath;
|
|
354
|
+
}
|
|
355
|
+
// Also export the converter for testing
|
|
356
|
+
export { attestationToRecord };
|
package/dist/redact.d.ts
CHANGED
package/dist/redact.js
CHANGED
|
@@ -2,14 +2,62 @@
|
|
|
2
2
|
// through here first, and the user is shown the exact redacted output before sharing.
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
const PATTERNS = [
|
|
5
|
-
{ name: "
|
|
5
|
+
{ name: "private keys", re: /-----BEGIN ((?:(?:RSA|EC|OPENSSH) )?PRIVATE KEY)-----[\s\S]*?-----END \1-----/g, replace: "[redacted-private-key]" },
|
|
6
|
+
{ name: "openai keys", re: /\bsk-(?:proj-)?[A-Za-z0-9_-]{8,}(?=$|[^A-Za-z0-9_-])/g, replace: "[redacted]" },
|
|
7
|
+
{ name: "stripe keys", re: /\b(?:stripe_(?:live|test|restricted)_key_(?:example|placeholder)_[A-Za-z0-9_-]{3,}|(?:s[k]|r[k])_(?:live|test)_[A-Za-z0-9]{8,}|whsec_[A-Za-z0-9]{8,})\b/gi, replace: "[redacted]" },
|
|
8
|
+
{ name: "slack tokens", re: /\bxox[bpar]-[A-Za-z0-9-]{12,}\b/g, replace: "[redacted-slack-token]" },
|
|
9
|
+
{ name: "google api keys", re: /\bAIza[0-9A-Za-z_-]{20,}\b/g, replace: "[redacted-google-api-key]" },
|
|
10
|
+
{ name: "webhook secrets", re: /\bwebhook_secret_(?:example|placeholder)_[A-Za-z0-9_-]{3,}\b/gi, replace: "[redacted]" },
|
|
11
|
+
{ name: "secret placeholder values", re: /\b(?:secret|token|api[_-]?key|password)_(?:example|placeholder)_[A-Za-z0-9_-]{3,}\b/gi, replace: "[redacted]" },
|
|
12
|
+
{ name: "env assignment secrets", re: /\b((?=[A-Z0-9_]*(?:STRIPE|SECRET|TOKEN|PASSWORD|PASSWD|API_KEY|PRIVATE_KEY|ACCESS_KEY))[A-Z][A-Z0-9_]*)=([^\s'"]+)/g, replace: "$1=[redacted]" },
|
|
13
|
+
{ name: "json secret fields", re: /("(?:stripe|secret|token|password|passwd|api[_-]?key|private[_-]?key|access[_-]?key|database[_-]?url|authorization|cookie)"\s*:\s*)"(?:\\.|[^"\\])*"/gi, replace: '$1"[redacted]"' },
|
|
14
|
+
{ name: "query secret fields", re: /([?&](?:secret|token|password|passwd|api[_-]?key|private[_-]?key|access[_-]?key)=)[^&\s]+/gi, replace: "$1[redacted]" },
|
|
6
15
|
{ name: "url credentials", re: /\b([a-z][a-z0-9+.-]*:\/\/)([^\s:@\/]+):([^\s@\/]+)@/g, replace: "$1[redacted]:[redacted]@" },
|
|
16
|
+
{ name: "database url values", re: /\b((?:[A-Z][A-Z0-9_]*_)?DATABASE_URL(?:_[A-Z0-9_]+)?)=((?![a-z][a-z0-9+.-]*:\/\/\[redacted\]:\[redacted\]@)[^\s'"]+)/g, replace: "$1=[redacted]" },
|
|
7
17
|
{ name: "bearer tokens", re: /\b(Bearer|token)\s+[A-Za-z0-9\-._~+/]{16,}={0,2}/g, replace: "$1 [redacted]" },
|
|
8
18
|
{ name: "github tokens", re: /\bgh[pousr]_[A-Za-z0-9]{20,}\b/g, replace: "[redacted-github-token]" },
|
|
9
19
|
{ name: "aws access keys", re: /\bAKIA[0-9A-Z]{16}\b/g, replace: "[redacted-aws-key]" },
|
|
10
20
|
{ name: "jwt-like", re: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g, replace: "[redacted-jwt]" },
|
|
11
21
|
{ name: "long hex secrets", re: /\b[0-9a-f]{40,}\b/gi, replace: "[redacted-hex]" },
|
|
12
22
|
];
|
|
23
|
+
function shannonEntropy(value) {
|
|
24
|
+
const counts = new Map();
|
|
25
|
+
for (const character of value)
|
|
26
|
+
counts.set(character, (counts.get(character) ?? 0) + 1);
|
|
27
|
+
let entropy = 0;
|
|
28
|
+
for (const count of counts.values()) {
|
|
29
|
+
const probability = count / value.length;
|
|
30
|
+
entropy -= probability * Math.log2(probability);
|
|
31
|
+
}
|
|
32
|
+
return entropy;
|
|
33
|
+
}
|
|
34
|
+
function redactHighEntropyTokens(input) {
|
|
35
|
+
let applied = false;
|
|
36
|
+
const text = input.replace(/[A-Za-z0-9_-]{32,}/g, (candidate, offset, source) => {
|
|
37
|
+
if (!/[a-z]/.test(candidate) ||
|
|
38
|
+
!/[A-Z]/.test(candidate) ||
|
|
39
|
+
!/\d/.test(candidate) ||
|
|
40
|
+
/^[0-9a-f]+$/i.test(candidate) ||
|
|
41
|
+
new Set(candidate).size < 16 ||
|
|
42
|
+
shannonEntropy(candidate) < 4.5) {
|
|
43
|
+
return candidate;
|
|
44
|
+
}
|
|
45
|
+
const before = source.slice(0, offset);
|
|
46
|
+
const after = source.slice(offset + candidate.length);
|
|
47
|
+
const leftContext = before.match(/\S+$/)?.[0] ?? "";
|
|
48
|
+
const contextStart = offset - leftContext.length;
|
|
49
|
+
const contextEndMatch = after.match(/\s/);
|
|
50
|
+
const contextEnd = offset + candidate.length + (contextEndMatch?.index ?? after.length);
|
|
51
|
+
const context = source.slice(contextStart, contextEnd);
|
|
52
|
+
// This deliberately conservative heuristic skips URL/path contexts and
|
|
53
|
+
// borderline entropy. Named provider patterns remain the reliable path.
|
|
54
|
+
if (/[\\/]/.test(context) || /(?:https?|file):\/\//i.test(context))
|
|
55
|
+
return candidate;
|
|
56
|
+
applied = true;
|
|
57
|
+
return "[redacted-high-entropy-token]";
|
|
58
|
+
});
|
|
59
|
+
return { text, applied };
|
|
60
|
+
}
|
|
13
61
|
export function redactText(input) {
|
|
14
62
|
let text = input;
|
|
15
63
|
const applied = [];
|
|
@@ -20,12 +68,48 @@ export function redactText(input) {
|
|
|
20
68
|
}
|
|
21
69
|
p.re.lastIndex = 0;
|
|
22
70
|
}
|
|
71
|
+
const entropyRedaction = redactHighEntropyTokens(text);
|
|
72
|
+
text = entropyRedaction.text;
|
|
73
|
+
if (entropyRedaction.applied)
|
|
74
|
+
applied.push("high-entropy token");
|
|
23
75
|
// machine-identifying paths
|
|
24
76
|
const home = os.homedir();
|
|
25
77
|
if (home && text.includes(home)) {
|
|
26
78
|
text = text.split(home).join("~");
|
|
27
79
|
applied.push("home directory path");
|
|
28
80
|
}
|
|
29
|
-
|
|
81
|
+
const userPath = /\/(?:Users|home)\/[^/\s]+/g;
|
|
82
|
+
if (userPath.test(text)) {
|
|
83
|
+
applied.push("local username path");
|
|
84
|
+
text = text.replace(userPath, "~");
|
|
85
|
+
}
|
|
86
|
+
userPath.lastIndex = 0;
|
|
30
87
|
return { text, applied };
|
|
31
88
|
}
|
|
89
|
+
const SENSITIVE_FIELD = /^(?:stripe|[a-z0-9_-]*[_-]stripe|secret|[a-z0-9_-]*[_-]secret|token|[a-z0-9_-]*[_-]token|password|passwd|api[_-]?key|private[_-]?key|access[_-]?key|database[_-]?url|credential|credentials|authorization|cookie|set-cookie)$/i;
|
|
90
|
+
export function redactJsonValue(input) {
|
|
91
|
+
const applied = new Set();
|
|
92
|
+
const visit = (value, key = "") => {
|
|
93
|
+
if (SENSITIVE_FIELD.test(key) && value !== null && value !== undefined) {
|
|
94
|
+
applied.add("sensitive field value");
|
|
95
|
+
return "[redacted]";
|
|
96
|
+
}
|
|
97
|
+
if (typeof value === "string") {
|
|
98
|
+
if (/(?:hash|sha256|commit)$/i.test(key) &&
|
|
99
|
+
/^[0-9a-f]{40,64}$/i.test(value)) {
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
const redacted = redactText(value);
|
|
103
|
+
for (const rule of redacted.applied)
|
|
104
|
+
applied.add(rule);
|
|
105
|
+
return redacted.text;
|
|
106
|
+
}
|
|
107
|
+
if (Array.isArray(value))
|
|
108
|
+
return value.map(item => visit(item));
|
|
109
|
+
if (value && typeof value === "object") {
|
|
110
|
+
return Object.fromEntries(Object.entries(value).map(([childKey, childValue]) => [childKey, visit(childValue, childKey)]));
|
|
111
|
+
}
|
|
112
|
+
return value;
|
|
113
|
+
};
|
|
114
|
+
return { value: visit(input), applied: [...applied].sort() };
|
|
115
|
+
}
|