@stacksjs/registry 0.11.12 → 0.11.16
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 +2 -0
- package/dist/index.js +416 -415
- package/dist/scanner-worker.js +3 -0
- package/dist/server.js +416 -415
- package/package.json +2 -2
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
import{createConnection as U}from"net";import{createHash as S,randomUUID as h}from"crypto";import{join as k}from"path";var f=30000,y=5000,m=14000,v=2700000;function p(J,W=t()){let Z=Math.ceil(J/1e6)*m;return Math.min(v,W+Z)}var u=4,d=32,c=1073741824,i=1073741824,l=65536,o=8192,n=16384;function A(J,W){return J&&/^[1-9]\d*(?:[KMGT])?$/.test(J)?J:W}function a(J,W,Z={}){let $=Z.platform||process.platform,q=Z.env||process.env,G=[process.execPath,J],K={CLAMD_TIMEOUT_MS:String(W)};if($!=="linux")return{command:G,env:K};if(q.PANTRY_SCANNER_SYSTEMD_ISOLATION!=="true")return{command:["nice","-n","15","ionice","-c","3",...G],env:K};let Q=Z.unitName||`pantry-scanner-${process.pid}-${h().slice(0,8)}`,H=["CLAMD_SOCKET","CLAMD_HOST","CLAMD_PORT","CLAMD_TIMEOUT_MS","CLAMD_HEALTH_TIMEOUT_MS","CLAMD_MAX_BYTES","CLAMD_CHUNK_BYTES","PANTRY_SCANNER_DOWNLOAD_BYTES_PER_SECOND"].flatMap((j)=>{let V=K[j]??q[j];return V?[`--setenv=${j}=${V}`]:[]}),F=Math.ceil((W+30000)/1000);return{unitName:Q,env:K,command:["systemd-run","--quiet","--pipe","--wait","--collect",`--unit=${Q}`,"--property=Type=exec","--property=MemoryAccounting=yes",`--property=MemoryHigh=${A(q.PANTRY_SCANNER_WORKER_MEMORY_HIGH,"192M")}`,`--property=MemoryMax=${A(q.PANTRY_SCANNER_WORKER_MEMORY_MAX,"256M")}`,"--property=MemorySwapMax=0","--property=TasksMax=32",`--property=RuntimeMaxSec=${F}s`,"--property=TimeoutStopSec=5s","--property=KillMode=control-group","--property=Nice=15","--property=IOSchedulingClass=idle","--property=CPUWeight=25","--property=IOWeight=25",...H,...G]}}class E extends Error{}class x{limit;maxQueued;active=0;waiters=[];constructor(J,W){this.limit=J;this.maxQueued=W}get inFlight(){return this.active}get queued(){return this.waiters.length}async acquire(){if(this.active<this.limit){this.active++;return}if(this.waiters.length>=this.maxQueued)throw new E(`scanner busy: ${this.active} scans in flight, ${this.waiters.length} queued`);await new Promise((J)=>{this.waiters.push(J)}),this.active++}release(){this.active--;let J=this.waiters.shift();if(J)J()}}class w extends Error{observedSha256;constructor(J,W){super(J);this.observedSha256=W}}function r(J,W){if(!J)return W;let Z=Number.parseInt(J,10);return Number.isSafeInteger(Z)&&Z>0?Z:W}function s(J){return S("sha256").update(Buffer.from(J)).digest("hex")}function L(J){return Math.max(0,Math.round(performance.now()-J))}function N(J){return J.replace(/[\u0000-\u001f\u007f]/g," ").trim().slice(0,256)}async function M(J,W=n){let Z=J.getReader(),$=[],q=0;while(!0){let G=await Z.read();if(G.done)break;if(q+=G.value.byteLength,q>W)throw await Z.cancel(),Error(`isolated scanner output exceeded ${W} bytes`);$.push(G.value)}return Buffer.concat($).toString("utf8")}function b(J){if(!J||/^(?:COMMAND UNAVAILABLE|UNKNOWN COMMAND)$/i.test(J))return{};let[W,Z]=J.split("/");return{engineVersion:N(W),databaseVersion:Z?N(Z):void 0}}function C(J,W){return new Promise((Z,$)=>{J.write(W,(q)=>q?$(q):Z())})}class T{enabled=!0;required=!0;socketPath;host;port;timeoutMs;healthTimeoutMs;maxBytes;maxStreamBytes;streamOverlapBytes;chunkBytes;workerPath;admission;cachedVersion=null;constructor(J={}){if(this.socketPath=J.socketPath?.trim()||void 0,this.host=J.host||"127.0.0.1",this.port=J.port||3310,this.timeoutMs=J.timeoutMs||f,this.healthTimeoutMs=Math.min(J.healthTimeoutMs||y,this.timeoutMs),this.maxBytes=J.maxBytes||c,this.maxStreamBytes=Math.min(J.maxStreamBytes||i,this.maxBytes),this.streamOverlapBytes=J.streamOverlapBytes||0,!Number.isSafeInteger(this.streamOverlapBytes)||this.streamOverlapBytes<0||this.streamOverlapBytes>=this.maxStreamBytes)throw Error("streamOverlapBytes must be a non-negative integer below maxStreamBytes");this.chunkBytes=J.chunkBytes||l,this.workerPath=J.workerPath||k(import.meta.dir,"scanner-worker.js"),this.admission=new x(J.maxConcurrentScans||u,J.maxQueuedScans??d)}async scan(J,W){let Z=performance.now(),$=new Date().toISOString(),q=s(J);if(J.byteLength>this.maxBytes)return{verdict:"error",engine:"clamav",scannedAt:$,durationMs:L(Z),artifactSha256:q,reason:`artifact exceeds scanner limit of ${this.maxBytes} bytes`};try{let G=await this.instream(new Uint8Array(J)),K=await this.version().catch(()=>{return}),Q=b(K);if(G==="stream: OK")return{verdict:"clean",engine:"clamav",scannedAt:$,durationMs:L(Z),artifactSha256:q,...Q};let H=G.match(/^stream:\s+(.+)\s+FOUND$/);if(H){let F=N(H[1]);if(F.startsWith("Heuristics.Limits.Exceeded"))return{verdict:"error",engine:"clamav",scannedAt:$,durationMs:L(Z),artifactSha256:q,reason:`scanner coverage limit exceeded: ${F}`,...Q};return{verdict:"blocked",engine:"clamav",scannedAt:$,durationMs:L(Z),artifactSha256:q,signature:F,...Q}}return{verdict:"error",engine:"clamav",scannedAt:$,durationMs:L(Z),artifactSha256:q,...Q,reason:`unexpected scanner response: ${N(G)}`}}catch(G){return{verdict:"error",engine:"clamav",scannedAt:$,durationMs:L(Z),artifactSha256:q,reason:N(G.message||"scanner unavailable")}}}async scanStream(J,W,Z){let $=performance.now(),q=new Date().toISOString();if(Z.size>this.maxBytes)return{verdict:"error",engine:"clamav",scannedAt:q,durationMs:L($),artifactSha256:Z.sha256,reason:`artifact exceeds scanner limit of ${this.maxBytes} bytes`};try{let{responses:G,observedSha256:K}=await this.instreamChunks(J,Z),Q=await this.version().catch(()=>{return}),H=b(Q),F=G.map((V)=>V.match(/^stream:\s+(.+)\s+FOUND$/)).find((V)=>V);if(F){let V=N(F[1]);if(V.startsWith("Heuristics.Limits.Exceeded"))return{verdict:"error",engine:"clamav",scannedAt:q,durationMs:L($),artifactSha256:K,reason:`scanner coverage limit exceeded: ${V}`,...H};return{verdict:"blocked",engine:"clamav",scannedAt:q,durationMs:L($),artifactSha256:K,signature:V,...H}}let j=G.find((V)=>V!=="stream: OK");if(!j)return{verdict:"clean",engine:"clamav",scannedAt:q,durationMs:L($),artifactSha256:K,...H};return{verdict:"error",engine:"clamav",scannedAt:q,durationMs:L($),artifactSha256:K,reason:`unexpected scanner response: ${N(j)}`,...H}}catch(G){let K=G instanceof w?G.observedSha256:Z.sha256;return{verdict:"error",engine:"clamav",scannedAt:q,durationMs:L($),artifactSha256:K,reason:N(G.message||"scanner unavailable")}}}async scanUrl(J,W,Z){let $=p(Z.size,this.timeoutMs),q=performance.now(),G=new Date().toISOString();if(Z.size>this.maxBytes)return{verdict:"error",engine:"clamav",scannedAt:G,durationMs:L(q),artifactSha256:Z.sha256,reason:`artifact exceeds scanner limit of ${this.maxBytes} bytes`};try{await this.admission.acquire()}catch(Q){return{verdict:"error",engine:"clamav",scannedAt:G,durationMs:L(q),artifactSha256:Z.sha256,reason:N(Q.message)}}let K;try{let{command:Q,env:H}=a(this.workerPath,$),F=Bun.spawn(Q,{env:{...process.env,...H},stdin:"pipe",stdout:"pipe",stderr:"pipe"});K=F,F.stdin.write(JSON.stringify({url:J,expected:Z})),F.stdin.end();let j=M(F.stdout),V=M(F.stderr),R,Y=Promise.race([F.exited,new Promise((D,B)=>{R=setTimeout(()=>{K?.kill(),B(Error(`isolated scanner timed out after ${$+30000}ms`))},$+30000)})]),[z,I,O]=await Promise.all([j,V,Y]).finally(()=>{if(R)clearTimeout(R)});if(O!==0)throw Error(I.trim().slice(0,256)||`isolated scanner exited ${O}`);let X;try{X=JSON.parse(z)}catch{throw Error(N(`isolated scanner exited ${O} without a parsable result (${z.length} bytes stdout${z.length>0?`, starting "${z.slice(0,64)}"`:""}); stderr: ${I.trim().slice(0,160)||"(empty)"}`))}if(!["clean","blocked","review","error"].includes(X.verdict||"")||X.engine!=="clamav"||typeof X.scannedAt!=="string"||typeof X.durationMs!=="number"||!Number.isFinite(X.durationMs)||X.durationMs<0||typeof X.artifactSha256!=="string"||!/^[a-f0-9]{64}$/.test(X.artifactSha256))throw Error("isolated scanner returned a malformed verdict");return{...X,...X.signature?{signature:N(X.signature)}:{},...X.reason?{reason:N(X.reason)}:{}}}catch(Q){return K?.kill(),{verdict:"error",engine:"clamav",scannedAt:G,durationMs:L(q),artifactSha256:Z.sha256,reason:N(Q.message||"isolated scanner unavailable")}}finally{this.admission.release()}}async health(){try{await this.ping(this.healthTimeoutMs);let J=await this.version(!1,this.healthTimeoutMs).catch(()=>{return}),W=J&&!/^(?:COMMAND UNAVAILABLE|UNKNOWN COMMAND)$/i.test(J)?J:void 0;return{enabled:!0,required:!0,ready:!0,engine:"clamav",...W?{version:W}:{}}}catch(J){return{enabled:!0,required:!0,ready:!1,engine:"clamav",reason:N(J.message||"scanner unavailable")}}}async instream(J){let W=await this.connect();try{await C(W,new TextEncoder().encode("zINSTREAM\x00"));for(let Z=0;Z<J.byteLength;Z+=this.chunkBytes){let $=J.subarray(Z,Math.min(Z+this.chunkBytes,J.byteLength)),q=new Uint8Array(4);new DataView(q.buffer).setUint32(0,$.byteLength,!1),await C(W,q),await C(W,$)}return await C(W,new Uint8Array(4)),await this.readResponse(W)}finally{W.destroy()}}async instreamChunks(J,W){let Z="connect",$,q=0,G=[],K=0,Q=Buffer.alloc(0),H=[],F=S("sha256"),j=0,V=async()=>{try{let Y=await this.connect();if(Z="command write",await C(Y,new TextEncoder().encode("zINSTREAM\x00")),$=Y,q=0,G=[],K=0,Q.byteLength>0){for(let z=0;z<Q.byteLength;z+=this.chunkBytes){let I=Q.subarray(z,Math.min(z+this.chunkBytes,Q.byteLength)),O=new Uint8Array(4);new DataView(O.buffer).setUint32(0,I.byteLength,!1),await C(Y,O),await C(Y,I)}q=Q.byteLength,G=[Q],K=Q.byteLength,Q=Buffer.alloc(0)}return Y}catch(Y){throw Error(`scanner ${Z} failed: ${Y.message||String(Y)}`)}},R=async()=>{if(!$)return;let Y=$;try{Z="terminator write",await C(Y,new Uint8Array(4)),Z="response read",H.push(await this.readResponse(Y))}finally{Y.destroy(),$=void 0,q=0}};try{Z="artifact read";for await(let z of J){let I=z instanceof Uint8Array?z:new Uint8Array(z);for(let O=0;O<I.byteLength;){let X=$||await V(),D=Math.min(this.chunkBytes,this.maxStreamBytes-q,I.byteLength-O),B=I.subarray(O,O+D);if(j+=B.byteLength,j>this.maxBytes||j>W.size)throw Error("streamed artifact exceeded its declared size");F.update(B);let P=new Uint8Array(4);if(new DataView(P.buffer).setUint32(0,B.byteLength,!1),Z="chunk header write",await C(X,P),Z="chunk body write",await C(X,B),q+=B.byteLength,this.streamOverlapBytes>0){G.push(Buffer.from(B.buffer,B.byteOffset,B.byteLength)),K+=B.byteLength;while(G.length>1&&K-G[0].byteLength>=this.streamOverlapBytes)K-=G.shift().byteLength;if(K>this.streamOverlapBytes){let g=K-this.streamOverlapBytes;G[0]=G[0].subarray(g),K=this.streamOverlapBytes}}if(O+=B.byteLength,q===this.maxStreamBytes&&j<W.size)Q=Buffer.concat(G,K),await R()}Z="artifact read"}let Y=F.digest("hex");if(j!==W.size)throw new w("streamed artifact size did not match the declared size",Y);if(Y!==W.sha256&&!W.allowDigestMismatch)throw new w("streamed artifact SHA-256 did not match the declared digest",Y);if(!$)await V();return await R(),{responses:H,observedSha256:Y}}catch(Y){if(Y instanceof w)throw Y;throw Error(`scanner ${Z} failed: ${Y.message||String(Y)}`)}finally{try{$?.destroy()}catch{}}}async version(J=!1,W=this.timeoutMs){if(!J&&this.cachedVersion&&this.cachedVersion.expiresAt>Date.now())return this.cachedVersion.value;let Z=await this.connect(W);try{await C(Z,new TextEncoder().encode("zVERSION\x00"));let $=N(await this.readResponse(Z));return this.cachedVersion={value:$,expiresAt:Date.now()+300000},$}finally{Z.destroy()}}async ping(J=this.timeoutMs){let W=await this.connect(J);try{await C(W,new TextEncoder().encode("zPING\x00"));let Z=await this.readResponse(W);if(Z!=="PONG")throw Error(`unexpected scanner ping response: ${N(Z)}`)}finally{W.destroy()}}connect(J=this.timeoutMs){return new Promise((W,Z)=>{let $=this.socketPath?U(this.socketPath):U({host:this.host,port:this.port}),q=setTimeout(()=>{$.destroy(),Z(Error(`scanner connection timed out after ${J}ms`))},J);$.once("connect",()=>{clearTimeout(q),$.setTimeout(J),$.on("timeout",()=>$.destroy(Error(`scanner timed out after ${J}ms`))),W($)}),$.once("error",(G)=>{clearTimeout(q),Z(G)})})}readResponse(J){return new Promise((W,Z)=>{let $=[],q=0,G=()=>{let K=Buffer.concat($).toString("utf8").replace(/\0.*$/,"").trim();K?W(K):Z(Error("empty scanner response"))};J.on("data",(K)=>{if(q+=K.byteLength,q>o){Z(Error("scanner response exceeded limit")),J.destroy();return}if($.push(K),K.includes(0))G()}),J.once("end",G),J.once("error",Z)})}}function t(J=process.env){return r(J.CLAMD_TIMEOUT_MS,f)}function _(J,W){if(!J)return W;let Z=Number.parseInt(J,10);return Number.isSafeInteger(Z)&&Z>0?Z:W}async function*e(J,W,Z={now:()=>performance.now(),sleep:($)=>Bun.sleep($)}){let $=Z.now(),q=0;for await(let G of J){q+=G.byteLength,yield G;let Q=q/W*1000-(Z.now()-$);if(Q>0)await Z.sleep(Q)}}async function JJ(){let J=JSON.parse(await Bun.stdin.text());if(typeof J.url!=="string"||!J.url.startsWith("https://")||!Number.isSafeInteger(J.expected?.size)||J.expected.size<0||!/^[a-f0-9]{64}$/.test(J.expected?.sha256||""))throw Error("invalid isolated scanner input");let W=_(process.env.CLAMD_TIMEOUT_MS,30000),Z=await fetch(J.url,{method:"GET",redirect:"error",signal:AbortSignal.timeout(W)});if(!Z.ok||!Z.body)throw Error(`artifact download failed with HTTP ${Z.status}`);let $=Number.parseInt(Z.headers.get("content-length")||"",10);if(Number.isSafeInteger($)&&$!==J.expected.size)throw Error("artifact download size did not match the declared size");let q=new T({socketPath:process.env.CLAMD_SOCKET,host:process.env.CLAMD_HOST||"127.0.0.1",port:_(process.env.CLAMD_PORT,3310),timeoutMs:W,healthTimeoutMs:_(process.env.CLAMD_HEALTH_TIMEOUT_MS,5000),maxBytes:_(process.env.CLAMD_MAX_BYTES,1073741824),chunkBytes:_(process.env.CLAMD_CHUNK_BYTES,65536)}),G=Z.body.getReader(),K={async*[Symbol.asyncIterator](){while(!0){let F=await G.read();if(F.done)break;yield F.value}}},Q=_(process.env.PANTRY_SCANNER_DOWNLOAD_BYTES_PER_SECOND,8388608),H=await q.scanStream(e(K,Q),{surface:"binary",name:"_isolated"},J.expected);process.stdout.write(JSON.stringify(H))}if(import.meta.main)JJ().catch((J)=>{console.error(`Isolated scanner failed: ${J.message}`),process.exit(1)});export{e as rateLimitStream};
|