@stacksjs/security 0.75.197 → 0.75.199
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +9 -1
- package/dist/regex.d.ts +25 -0
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,2 +1,10 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var rt=import.meta.require;import{Buffer as y}from"buffer";import{config as F}from"@stacksjs/config";import{decrypt as B,encrypt as Ot}from"ts-security-crypto";function H(r){return r.startsWith("base64:")?r.slice(7):r}var L=1,M=2,nt="stacks:encrypt:v2",O=600000,P=32,m=16,l=12,st=64,p=new Map;function ot(r,t,e){return`${e}:${y.from(t).toString("base64")}:${r}`}async function _(r,t,e){let n=ot(r,t,e),s=p.get(n);if(s)return s.hits++,p.delete(n),p.set(n,s),s.key;let o=await crypto.subtle.importKey("raw",new TextEncoder().encode(r),{name:"PBKDF2"},!1,["deriveKey"]),i=await crypto.subtle.deriveKey({name:"PBKDF2",salt:new Uint8Array(t),iterations:e,hash:"SHA-256"},o,{name:"AES-GCM",length:P*8},!1,["encrypt","decrypt"]);if(p.set(n,{key:i,hits:1}),p.size>st){let a=p.keys().next().value;if(a!==void 0)p.delete(a)}return i}function I(r){if(!r.startsWith("base64:"))return null;let t=y.from(r.slice(7),"base64");return t.length>=P?new Uint8Array(t):null}async function V(r,t){let e=await crypto.subtle.importKey("raw",new Uint8Array(r),"HKDF",!1,["deriveKey"]);return await crypto.subtle.deriveKey({name:"HKDF",hash:"SHA-256",salt:new Uint8Array(t),info:new TextEncoder().encode(nt)},e,{name:"AES-GCM",length:P*8},!1,["encrypt","decrypt"])}async function it(r,t){let e=crypto.getRandomValues(new Uint8Array(m)),n=crypto.getRandomValues(new Uint8Array(l)),s=await V(t,e),o=new Uint8Array(await crypto.subtle.encrypt({name:"AES-GCM",iv:n},s,new TextEncoder().encode(r))),i=new Uint8Array(1+m+l+o.byteLength);return i[0]=M,i.set(e,1),i.set(n,1+m),i.set(o,1+m+l),y.from(i).toString("base64")}async function at(r,t){let e=y.from(r,"base64");if(e.length<1+m+l+16)return null;if(e[0]!==M)return null;let n=new Uint8Array(e.subarray(1,1+m)),s=new Uint8Array(e.subarray(1+m,1+m+l)),o=new Uint8Array(e.subarray(1+m+l)),i=await V(t,n);try{return new TextDecoder().decode(await crypto.subtle.decrypt({name:"AES-GCM",iv:s},i,o))}catch{return null}}async function ct(r,t){let e=crypto.getRandomValues(new Uint8Array(m)),n=crypto.getRandomValues(new Uint8Array(l)),s=await _(t,e,O),o=await crypto.subtle.encrypt({name:"AES-GCM",iv:n},s,new TextEncoder().encode(r)),i=new Uint8Array(o),a=new Uint8Array(1+m+l+i.byteLength);return a[0]=L,a.set(e,1),a.set(n,1+m),a.set(i,1+m+l),y.from(a).toString("base64")}async function ut(r,t){let e=y.from(r,"base64");if(e.length<1+m+l+16)return null;if(e[0]!==L)return null;let n=new Uint8Array(e.subarray(1,1+m)),s=new Uint8Array(e.subarray(1+m,1+m+l)),o=new Uint8Array(e.subarray(1+m+l)),i=await _(t,n,O);try{let a=await crypto.subtle.decrypt({name:"AES-GCM",iv:s},i,o);return new TextDecoder().decode(a)}catch{return null}}async function _t(r,t){if(!r&&r!=="")throw Error("encrypt() requires a string message");let e=t||F.app.key;if(!e)throw Error("APP_KEY is not defined");let n=I(e);if(n)return await it(r,n);return await ct(r,H(e))}async function It(r,t){if(!r)throw Error("decrypt() requires a non-empty encrypted string");let e=t||F.app.key;if(!e)throw Error("APP_KEY is not defined");let n=H(e),s=I(e);if(s){let i=await at(r,s);if(i!==null)return i}let o=await ut(r,n);if(o!==null)return o;try{return await B(r,n)}catch(i){if(n!==e)try{return await B(r,e)}catch{}throw i}}import{timingSafeEqual as D}from"crypto";import{base64Encode as Gt,hashPassword as G,verifyPassword as q}from"ts-security-crypto";var w=null;function R(){if(w)return w;try{let{hashing:r}=rt("@stacksjs/config");return w=r||{},w}catch{return console.warn("[Security] Failed to load hashing config, using defaults"),w={driver:"bcrypt",bcrypt:{rounds:12},argon2:{memory:65536,time:2}},w}}function W(r){if(!r||typeof r!=="string")return"unknown";if(r.startsWith("$2a$")||r.startsWith("$2b$")||r.startsWith("$2y$"))return"bcrypt";if(r.startsWith("$argon2id$"))return"argon2id";if(r.startsWith("$argon2i$"))return"argon2i";if(r.startsWith("$argon2d$"))return"argon2d";return"unknown"}function mt(r){let t=W(r),e={};if(t==="bcrypt"){let n=r.match(/^\$2[aby]\$(\d{2})\$/);if(n&&n[1]!==void 0)e.rounds=Number.parseInt(n[1],10)}else if(t==="argon2id"||t==="argon2i"||t==="argon2d"){let n=r.match(/v=(\d+)/),s=r.match(/m=(\d+)/),o=r.match(/t=(\d+)/),i=r.match(/p=(\d+)/);if(n&&n[1]!==void 0)e.version=Number.parseInt(n[1],10);if(s&&s[1]!==void 0)e.memory=Number.parseInt(s[1],10);if(o&&o[1]!==void 0)e.rounds=Number.parseInt(o[1],10);if(i&&i[1]!==void 0)e.parallelism=Number.parseInt(i[1],10)}return{algorithm:t,options:e}}function Wt(r,t){let e=mt(r),n=R(),s=t?.algorithm||n.driver||"bcrypt",o=(c)=>{if(c==="argon2")return"argon2id";return c},i=o(e.algorithm),a=o(s);if(i!==a)return!0;if(i==="bcrypt"){let c=t?.rounds||n.bcrypt?.rounds||12;if(e.options.rounds!==c)return!0}if(i.startsWith("argon2")){let c=t?.memory||n.argon2?.memory||65536,u=t?.time||n.argon2?.time||2;if(e.options.memory!==c||e.options.rounds!==u)return!0}return!1}async function Yt(r,t){let e=R(),n=t?.algorithm||e.driver||"bcrypt";if(n==="bcrypt")return await ft(r,t?.rounds);if(n==="argon2"||n==="argon2id"||n==="argon2i"||n==="argon2d")return await lt(r,{type:n==="argon2"?"argon2id":n,memory:t?.memory,time:t?.time});throw Error(`Unsupported hashing algorithm: ${n}`)}async function zt(r,t){if(!r||!t)return!1;if(W(t)==="unknown")try{return await q(r,t)}catch{return!1}return await q(r,t)}async function ft(r,t){let e=R(),n=t||e.bcrypt?.rounds||12;return await G(r,{algorithm:"bcrypt",cost:n})}async function lt(r,t){let e=R(),n=t?.type||"argon2id",s=t?.memory||e.argon2?.memory||65536,o=t?.time||e.argon2?.time||2;return await G(r,{algorithm:n,memoryCost:s,timeCost:o})}function Xt(r,t){let e=Buffer.from(r),n=Buffer.from(t);if(e.length!==n.length)return D(e,e),!1;return D(e,n)}import{generateKey as gt}from"ts-security-crypto";function Qt(){return gt(32)}import{createHmac as ht,randomBytes as dt,timingSafeEqual as Y}from"crypto";class f extends Error{reason;constructor(r,t="mismatch"){super(r);this.name="InvalidWebhookSignature",this.reason=t}}function k(r){if(!/^[0-9a-f]*$/i.test(r)||r.length%2!==0)throw new f("Signature is not valid hex","malformed");return Buffer.from(r,"hex")}function T(r,t){if(r.length!==t.length)return Y(r,r),!1;return Y(r,t)}function S(r,t,e){return ht(e,r).update(t).digest()}function pt(r,t){let e=r.split(","),n=Number.NaN,s=[];for(let o of e){let i=o.indexOf("=");if(i===-1)continue;let a=o.slice(0,i).trim(),c=o.slice(i+1).trim();if(a==="t")n=Number.parseInt(c,10);else if(a==="v1")s.push(c)}if(!Number.isFinite(n)||s.length===0)throw new f(`${t} header is malformed`,"malformed");return{timestamp:n,signatures:s}}function z(r,t,e,n,s){if(!r)throw new f(`${s.name} secret is empty`,"missing");if(!t)throw new f(`${s.header} header is missing`,"missing");let{timestamp:o,signatures:i}=pt(t,s.header),a=n.toleranceSeconds??300,c=Math.floor((n.now??Date.now())/1000);if(Math.abs(c-o)>a)throw new f(`${s.name} timestamp is outside tolerance window`,"expired");let u=typeof e==="string"?e:e.toString("utf8"),g=S(r,`${o}.${u}`,"sha256");for(let b of i){let x;try{x=k(b)}catch{continue}if(T(g,x))return!0}throw new f(`${s.name} signature did not match`,"mismatch")}function yt(r,t,e,n={}){return z(r,t,e,n,{name:"Stripe webhook",header:"Stripe-Signature"})}function wt(r,t,e,n={}){return z(r,t,e,n,{name:"Webhook",header:"Signature"})}function ee(r,t,e={}){let n=(Array.isArray(r)?r:[r]).filter((a)=>typeof a==="string"&&a.length>0);if(!n.length)throw new f("Webhook signing secret is empty","missing");let s=e.timestamp??Math.floor(Date.now()/1000);if(!Number.isInteger(s)||s<0)throw new f("Webhook timestamp must be whole unix seconds","malformed");let o=typeof t==="string"?t:t.toString("utf8"),i=n.map((a)=>S(a,`${s}.${o}`,"sha256").toString("hex"));return{timestamp:s,signatures:i,header:[`t=${s}`,...i.map((a)=>`v1=${a}`)].join(",")}}function re(r="whsec_"){return`${r}${dt(32).toString("base64url")}`}function bt(r,t,e){if(!r)throw new f("GitHub webhook secret is empty","missing");if(!t)throw new f("X-Hub-Signature-256 header is missing","missing");let n="sha256=";if(!t.startsWith(n))throw new f("X-Hub-Signature-256 has wrong algorithm prefix","malformed");let s=k(t.slice(n.length)),o=S(r,e,"sha256");if(!T(o,s))throw new f("GitHub webhook signature did not match","mismatch");return!0}function xt(r,t,e,n="sha256"){if(!r)throw new f("HMAC secret is empty","missing");if(!e)throw new f("Signature header is missing","missing");let s=e.indexOf("="),o=s!==-1&&/^[a-z0-9]+$/i.test(e.slice(0,s))?e.slice(s+1):e,i=k(o),a=S(r,t,n);if(!T(a,i))throw new f("HMAC signature did not match","mismatch");return!0}function ne(r,t,e,n,s={}){switch(r){case"stripe":return yt(t,e,n,s);case"github":return bt(t,e,n);case"generic":return xt(t,n,e,s.algorithm??"sha256");case"signed":return wt(t,e,n,s);default:throw new f(`Unknown webhook provider: ${String(r)}`,"malformed")}}import{createHash as At,randomBytes as vt}from"crypto";function $t(r){return At("sha256").update(r,"utf8").digest("hex")}function Ut(r,t=""){let e=t&&r.startsWith(t)?r.slice(t.length):r;if(e.length<=8)return`${t}\u2026`;return`${t}${e.slice(0,4)}\u2026${e.slice(-4)}`}function ie(r="",t=32){if(!Number.isInteger(t)||t<16)throw Error("An API key needs at least 16 random bytes");let e=`${r}${vt(t).toString("base64url")}`;return{key:e,hash:$t(e),hint:Ut(e,r)}}import{lookup as Rt}from"dns/promises";import{isIP as A}from"net";class h extends Error{reason;constructor(r,t){super(r);this.name="OutboundUrlError",this.reason=t}}function j(r){let t=r.split(".");if(t.length!==4)return null;let e=t.map((n)=>/^\d{1,3}$/.test(n)?Number(n):Number.NaN);return e.every((n)=>Number.isInteger(n)&&n>=0&&n<=255)?e:null}function E(r){let t=j(r);if(!t)return!0;let[e,n,s]=t;return e===0||e===10||e===100&&n>=64&&n<=127||e===127||e===169&&n===254||e===172&&n>=16&&n<=31||e===192&&n===0&&(s===0||s===2)||e===192&&n===88&&s===99||e===192&&n===168||e===198&&(n===18||n===19)||e===198&&n===51&&s===100||e===203&&n===0&&s===113||e>=224}function St(r){let t=r.toLowerCase(),e=t.indexOf("%");if(e!==-1)t=t.slice(0,e);let n=t.match(/^(.*:)(\d+\.\d+\.\d+\.\d+)$/);if(n){let u=j(n[2]);if(!u)return null;t=`${n[1]}${(u[0]<<8|u[1]).toString(16)}:${(u[2]<<8|u[3]).toString(16)}`}let s=t.split("::");if(s.length>2)return null;let o=s[0]?s[0].split(":"):[],i=s.length===2&&s[1]?s[1].split(":"):[],a=8-o.length-i.length;if(s.length===1&&a!==0||a<0)return null;let c=[...o,...Array.from({length:s.length===2?a:0},()=>"0"),...i];if(c.length!==8||c.some((u)=>!/^[0-9a-f]{1,4}$/.test(u)))return null;return c.map((u)=>Number.parseInt(u,16))}function Et(r){let t=St(r);if(!t)return!0;let e=`${t[6]>>8}.${t[6]&255}.${t[7]>>8}.${t[7]&255}`;if(t.slice(0,7).every((n)=>n===0))return!0;if(t.slice(0,5).every((n)=>n===0)&&t[5]===65535)return E(e);if(t[0]===100&&t[1]===65435&&t.slice(2,6).every((n)=>n===0))return E(e);if(t[0]===8194)return E(`${t[1]>>8}.${t[1]&255}.${t[2]>>8}.${t[2]&255}`);return(t[0]&65024)===64512||(t[0]&65472)===65152||(t[0]&65280)===65280||t[0]===8193&&t[1]===3512||t[0]===8193&&t[1]<512||t[0]===256}function C(r){let t=r.replace(/^\[|\]$/g,""),e=A(t.split("%")[0]);if(e===4)return E(t);if(e===6)return Et(t);return!0}var Pt=[".localhost",".local",".internal",".localdomain",".home.arpa"];function kt(r,t={}){let e;try{e=new URL(r)}catch{return{reason:"invalid",message:"Not a valid URL"}}if(!(t.allowHttp?["https:","http:"]:["https:"]).includes(e.protocol))return{reason:"scheme",message:t.allowHttp?"The URL must start with https:// or http://":"The URL must start with https://"};if(e.username||e.password)return{reason:"credentials",message:"The URL must not contain a username or password"};if(t.allowPrivate)return null;let s=e.hostname.replace(/^\[|\]$/g,"").toLowerCase().replace(/\.$/,"");if(!s)return{reason:"invalid",message:"Not a valid URL"};if(A(s.split("%")[0]))return C(s)?{reason:"private",message:"The URL points at a private or local address"}:null;if(s==="localhost"||!s.includes(".")||Pt.some((o)=>s.endsWith(o)))return{reason:"private",message:"The URL points at a private or local address"};return null}async function J(r){return(await Rt(r,{all:!0,verbatim:!0})).map((e)=>e.address)}async function Tt(r,t={}){let e=kt(r,t);if(e)throw new h(e.message,e.reason);let n=new URL(r);if(t.allowPrivate)return n;let s=n.hostname.replace(/^\[|\]$/g,"");if(A(s.split("%")[0]))return n;let o;try{o=await(t.resolve??J)(s)}catch{throw new h(`Could not resolve ${s}`,"unresolvable")}if(!o.length)throw new h(`Could not resolve ${s}`,"unresolvable");if(o.some(C))throw new h("The URL points at a private or local address","private");return n}async function Ct(r,t){let e=await Tt(r,t),n=e.hostname.replace(/^\[|\]$/g,"");if(A(n.split("%")[0])||t.allowPrivate)return{parsed:e,addresses:[]};let s=await(t.resolve??J)(n);return{parsed:e,addresses:s}}var Kt=new Set([301,302,303,307,308]),Nt=["authorization","cookie","proxy-authorization"];function Bt(r,t,e){if(!t)return[r.href,e];let n=new URL(r.href);n.hostname=A(t)===6?`[${t}]`:t;let s=new Headers(e.headers);s.set("Host",r.host);let o={...e,headers:s};if(r.protocol==="https:")o.tls={serverName:r.hostname.replace(/^\[|\]$/g,"")};return[n.href,o]}async function X(r,t){let e=Number(r.headers.get("content-length")??Number.NaN);if(Number.isFinite(e)&&e>t)throw new h(`The response is ${e} bytes, over the ${t} byte limit`,"too-large");if(!r.body)return r;let n=[],s=0,o=r.body.getReader();try{for(;;){let{done:c,value:u}=await o.read();if(c)break;if(s+=u.byteLength,s>t)throw await o.cancel(),new h(`The response is over the ${t} byte limit`,"too-large");n.push(u)}}finally{o.releaseLock()}let i=new Uint8Array(s),a=0;for(let c of n)i.set(c,a),a+=c.byteLength;return new Response(i,{status:r.status,statusText:r.statusText,headers:r.headers})}async function me(r,t={},e={}){let n=e.maxRedirects??3,s=e.maxBytes??5242880,o=e.timeoutMs??1e4,i=AbortSignal.timeout(o),a=t.signal?AbortSignal.any([t.signal,i]):i,c=t.body==null||typeof t.body==="string"||t.body instanceof Uint8Array||t.body instanceof ArrayBuffer,u=r,g={...t,signal:a,redirect:"manual"},b="";for(let x=0;;x++){let{parsed:v,addresses:Q}=await Ct(u,e);if(b&&v.origin!==b){let U=new Headers(g.headers);for(let et of Nt)U.delete(et);g={...g,headers:U}}b=v.origin;let[Z,tt]=Bt(v,Q.find((U)=>!C(U)),g),d=await fetch(Z,tt);if(!Kt.has(d.status))return await X(d,s);let K=d.headers.get("location");if(!K)return await X(d,s);if(x>=n)throw new h(`The URL redirected more than ${n} times`,"redirects");if(!c)throw new h("The URL redirected, and a streaming request body cannot be sent again","unreplayable-body");let N=(g.method??"GET").toUpperCase();if(d.status===303||(d.status===301||d.status===302)&&N!=="GET"&&N!=="HEAD")g={...g,method:"GET",body:void 0};u=new URL(K,v).href}}export{f as InvalidWebhookSignature,h as OutboundUrlError,Ut as apiKeyHint,lt as argon2Encode,Tt as assertPublicOutboundUrl,Gt as base64Encode,ft as bcryptEncode,zt as check,It as decrypt,W as detectAlgorithm,_t as encrypt,me as fetchPublicUrl,ie as generateApiKey,Qt as generateAppKey,re as generateWebhookSecret,$t as hashApiKey,zt as hashCheck,W as hashDetectAlgorithm,mt as hashInfo,Yt as hashMake,Wt as hashNeedsRehash,mt as info,C as isPrivateAddress,Ot as legacyEncrypt,Yt as make,Yt as makeHash,Wt as needsRehash,kt as outboundUrlProblem,ee as signWebhook,Xt as timingSafeEqualString,bt as verifyGithub,zt as verifyHash,xt as verifyHmac,wt as verifySignedWebhook,yt as verifyStripe,ne as verifyWebhook};
|
|
2
|
+
var ne=import.meta.require;import{Buffer as y}from"buffer";import{config as M}from"@stacksjs/config";import{decrypt as K,encrypt as Ve}from"ts-security-crypto";function O(r){return r.startsWith("base64:")?r.slice(7):r}var L=1,H=2,se="stacks:encrypt:v2",I=600000,P=32,m=16,f=12,ie=64,p=new Map;function oe(r,e,t){return`${t}:${y.from(e).toString("base64")}:${r}`}async function F(r,e,t){let n=oe(r,e,t),s=p.get(n);if(s)return s.hits++,p.delete(n),p.set(n,s),s.key;let i=await crypto.subtle.importKey("raw",new TextEncoder().encode(r),{name:"PBKDF2"},!1,["deriveKey"]),o=await crypto.subtle.deriveKey({name:"PBKDF2",salt:new Uint8Array(e),iterations:t,hash:"SHA-256"},i,{name:"AES-GCM",length:P*8},!1,["encrypt","decrypt"]);if(p.set(n,{key:o,hits:1}),p.size>ie){let a=p.keys().next().value;if(a!==void 0)p.delete(a)}return o}function _(r){if(!r.startsWith("base64:"))return null;let e=y.from(r.slice(7),"base64");return e.length>=P?new Uint8Array(e):null}async function V(r,e){let t=await crypto.subtle.importKey("raw",new Uint8Array(r),"HKDF",!1,["deriveKey"]);return await crypto.subtle.deriveKey({name:"HKDF",hash:"SHA-256",salt:new Uint8Array(e),info:new TextEncoder().encode(se)},t,{name:"AES-GCM",length:P*8},!1,["encrypt","decrypt"])}async function ae(r,e){let t=crypto.getRandomValues(new Uint8Array(m)),n=crypto.getRandomValues(new Uint8Array(f)),s=await V(e,t),i=new Uint8Array(await crypto.subtle.encrypt({name:"AES-GCM",iv:n},s,new TextEncoder().encode(r))),o=new Uint8Array(1+m+f+i.byteLength);return o[0]=H,o.set(t,1),o.set(n,1+m),o.set(i,1+m+f),y.from(o).toString("base64")}async function ce(r,e){let t=y.from(r,"base64");if(t.length<1+m+f+16)return null;if(t[0]!==H)return null;let n=new Uint8Array(t.subarray(1,1+m)),s=new Uint8Array(t.subarray(1+m,1+m+f)),i=new Uint8Array(t.subarray(1+m+f)),o=await V(e,n);try{return new TextDecoder().decode(await crypto.subtle.decrypt({name:"AES-GCM",iv:s},o,i))}catch{return null}}async function ue(r,e){let t=crypto.getRandomValues(new Uint8Array(m)),n=crypto.getRandomValues(new Uint8Array(f)),s=await F(e,t,I),i=await crypto.subtle.encrypt({name:"AES-GCM",iv:n},s,new TextEncoder().encode(r)),o=new Uint8Array(i),a=new Uint8Array(1+m+f+o.byteLength);return a[0]=L,a.set(t,1),a.set(n,1+m),a.set(o,1+m+f),y.from(a).toString("base64")}async function me(r,e){let t=y.from(r,"base64");if(t.length<1+m+f+16)return null;if(t[0]!==L)return null;let n=new Uint8Array(t.subarray(1,1+m)),s=new Uint8Array(t.subarray(1+m,1+m+f)),i=new Uint8Array(t.subarray(1+m+f)),o=await F(e,n,I);try{let a=await crypto.subtle.decrypt({name:"AES-GCM",iv:s},o,i);return new TextDecoder().decode(a)}catch{return null}}async function Ge(r,e){if(!r&&r!=="")throw Error("encrypt() requires a string message");let t=e||M.app.key;if(!t)throw Error("APP_KEY is not defined");let n=_(t);if(n)return await ae(r,n);return await ue(r,O(t))}async function De(r,e){if(!r)throw Error("decrypt() requires a non-empty encrypted string");let t=e||M.app.key;if(!t)throw Error("APP_KEY is not defined");let n=O(t),s=_(t);if(s){let o=await ce(r,s);if(o!==null)return o}let i=await me(r,n);if(i!==null)return i;try{return await K(r,n)}catch(o){if(n!==t)try{return await K(r,t)}catch{}throw o}}import{timingSafeEqual as G}from"crypto";import{base64Encode as Ye,hashPassword as q,verifyPassword as D}from"ts-security-crypto";var w=null;function S(){if(w)return w;try{let{hashing:r}=ne("@stacksjs/config");return w=r||{},w}catch{return console.warn("[Security] Failed to load hashing config, using defaults"),w={driver:"bcrypt",bcrypt:{rounds:12},argon2:{memory:65536,time:2}},w}}function W(r){if(!r||typeof r!=="string")return"unknown";if(r.startsWith("$2a$")||r.startsWith("$2b$")||r.startsWith("$2y$"))return"bcrypt";if(r.startsWith("$argon2id$"))return"argon2id";if(r.startsWith("$argon2i$"))return"argon2i";if(r.startsWith("$argon2d$"))return"argon2d";return"unknown"}function le(r){let e=W(r),t={};if(e==="bcrypt"){let n=r.match(/^\$2[aby]\$(\d{2})\$/);if(n&&n[1]!==void 0)t.rounds=Number.parseInt(n[1],10)}else if(e==="argon2id"||e==="argon2i"||e==="argon2d"){let n=r.match(/v=(\d+)/),s=r.match(/m=(\d+)/),i=r.match(/t=(\d+)/),o=r.match(/p=(\d+)/);if(n&&n[1]!==void 0)t.version=Number.parseInt(n[1],10);if(s&&s[1]!==void 0)t.memory=Number.parseInt(s[1],10);if(i&&i[1]!==void 0)t.rounds=Number.parseInt(i[1],10);if(o&&o[1]!==void 0)t.parallelism=Number.parseInt(o[1],10)}return{algorithm:e,options:t}}function Je(r,e){let t=le(r),n=S(),s=e?.algorithm||n.driver||"bcrypt",i=(c)=>{if(c==="argon2")return"argon2id";return c},o=i(t.algorithm),a=i(s);if(o!==a)return!0;if(o==="bcrypt"){let c=e?.rounds||n.bcrypt?.rounds||12;if(t.options.rounds!==c)return!0}if(o.startsWith("argon2")){let c=e?.memory||n.argon2?.memory||65536,u=e?.time||n.argon2?.time||2;if(t.options.memory!==c||t.options.rounds!==u)return!0}return!1}async function Xe(r,e){let t=S(),n=e?.algorithm||t.driver||"bcrypt";if(n==="bcrypt")return await fe(r,e?.rounds);if(n==="argon2"||n==="argon2id"||n==="argon2i"||n==="argon2d")return await ge(r,{type:n==="argon2"?"argon2id":n,memory:e?.memory,time:e?.time});throw Error(`Unsupported hashing algorithm: ${n}`)}async function je(r,e){if(!r||!e)return!1;if(W(e)==="unknown")try{return await D(r,e)}catch{return!1}return await D(r,e)}async function fe(r,e){let t=S(),n=e||t.bcrypt?.rounds||12;return await q(r,{algorithm:"bcrypt",cost:n})}async function ge(r,e){let t=S(),n=e?.type||"argon2id",s=e?.memory||t.argon2?.memory||65536,i=e?.time||t.argon2?.time||2;return await q(r,{algorithm:n,memoryCost:s,timeCost:i})}function Qe(r,e){let t=Buffer.from(r),n=Buffer.from(e);if(t.length!==n.length)return G(t,t),!1;return G(t,n)}import{generateKey as he}from"ts-security-crypto";function tt(){return he(32)}import{createHmac as de,randomBytes as pe,timingSafeEqual as z}from"crypto";class l extends Error{reason;constructor(r,e="mismatch"){super(r);this.name="InvalidWebhookSignature",this.reason=e}}function T(r){if(!/^[0-9a-f]*$/i.test(r)||r.length%2!==0)throw new l("Signature is not valid hex","malformed");return Buffer.from(r,"hex")}function k(r,e){if(r.length!==e.length)return z(r,r),!1;return z(r,e)}function U(r,e,t){return de(t,r).update(e).digest()}function ye(r,e){let t=r.split(","),n=Number.NaN,s=[];for(let i of t){let o=i.indexOf("=");if(o===-1)continue;let a=i.slice(0,o).trim(),c=i.slice(o+1).trim();if(a==="t")n=Number.parseInt(c,10);else if(a==="v1")s.push(c)}if(!Number.isFinite(n)||s.length===0)throw new l(`${e} header is malformed`,"malformed");return{timestamp:n,signatures:s}}function Y(r,e,t,n,s){if(!r)throw new l(`${s.name} secret is empty`,"missing");if(!e)throw new l(`${s.header} header is missing`,"missing");let{timestamp:i,signatures:o}=ye(e,s.header),a=n.toleranceSeconds??300,c=Math.floor((n.now??Date.now())/1000);if(Math.abs(c-i)>a)throw new l(`${s.name} timestamp is outside tolerance window`,"expired");let u=typeof t==="string"?t:t.toString("utf8"),g=U(r,`${i}.${u}`,"sha256");for(let b of o){let x;try{x=T(b)}catch{continue}if(k(g,x))return!0}throw new l(`${s.name} signature did not match`,"mismatch")}function we(r,e,t,n={}){return Y(r,e,t,n,{name:"Stripe webhook",header:"Stripe-Signature"})}function be(r,e,t,n={}){return Y(r,e,t,n,{name:"Webhook",header:"Signature"})}function st(r,e,t={}){let n=(Array.isArray(r)?r:[r]).filter((a)=>typeof a==="string"&&a.length>0);if(!n.length)throw new l("Webhook signing secret is empty","missing");let s=t.timestamp??Math.floor(Date.now()/1000);if(!Number.isInteger(s)||s<0)throw new l("Webhook timestamp must be whole unix seconds","malformed");let i=typeof e==="string"?e:e.toString("utf8"),o=n.map((a)=>U(a,`${s}.${i}`,"sha256").toString("hex"));return{timestamp:s,signatures:o,header:[`t=${s}`,...o.map((a)=>`v1=${a}`)].join(",")}}function it(r="whsec_"){return`${r}${pe(32).toString("base64url")}`}function xe(r,e,t){if(!r)throw new l("GitHub webhook secret is empty","missing");if(!e)throw new l("X-Hub-Signature-256 header is missing","missing");let n="sha256=";if(!e.startsWith(n))throw new l("X-Hub-Signature-256 has wrong algorithm prefix","malformed");let s=T(e.slice(n.length)),i=U(r,t,"sha256");if(!k(i,s))throw new l("GitHub webhook signature did not match","mismatch");return!0}function ve(r,e,t,n="sha256"){if(!r)throw new l("HMAC secret is empty","missing");if(!t)throw new l("Signature header is missing","missing");let s=t.indexOf("="),i=s!==-1&&/^[a-z0-9]+$/i.test(t.slice(0,s))?t.slice(s+1):t,o=T(i),a=U(r,e,n);if(!k(a,o))throw new l("HMAC signature did not match","mismatch");return!0}function ot(r,e,t,n,s={}){switch(r){case"stripe":return we(e,t,n,s);case"github":return xe(e,t,n);case"generic":return ve(e,n,t,s.algorithm??"sha256");case"signed":return be(e,t,n,s);default:throw new l(`Unknown webhook provider: ${String(r)}`,"malformed")}}import{createHash as Ae,randomBytes as $e}from"crypto";function Re(r){return Ae("sha256").update(r,"utf8").digest("hex")}function Se(r,e=""){let t=e&&r.startsWith(e)?r.slice(e.length):r;if(t.length<=8)return`${e}\u2026`;return`${e}${t.slice(0,4)}\u2026${t.slice(-4)}`}function ut(r="",e=32){if(!Number.isInteger(e)||e<16)throw Error("An API key needs at least 16 random bytes");let t=`${r}${$e(e).toString("base64url")}`;return{key:t,hash:Re(t),hint:Se(t,r)}}import{lookup as Ue}from"dns/promises";import{isIP as v}from"net";class h extends Error{reason;constructor(r,e){super(r);this.name="OutboundUrlError",this.reason=e}}function X(r){let e=r.split(".");if(e.length!==4)return null;let t=e.map((n)=>/^\d{1,3}$/.test(n)?Number(n):Number.NaN);return t.every((n)=>Number.isInteger(n)&&n>=0&&n<=255)?t:null}function E(r){let e=X(r);if(!e)return!0;let[t,n,s]=e;return t===0||t===10||t===100&&n>=64&&n<=127||t===127||t===169&&n===254||t===172&&n>=16&&n<=31||t===192&&n===0&&(s===0||s===2)||t===192&&n===88&&s===99||t===192&&n===168||t===198&&(n===18||n===19)||t===198&&n===51&&s===100||t===203&&n===0&&s===113||t>=224}function Ee(r){let e=r.toLowerCase(),t=e.indexOf("%");if(t!==-1)e=e.slice(0,t);let n=e.match(/^(.*:)(\d+\.\d+\.\d+\.\d+)$/);if(n){let u=X(n[2]);if(!u)return null;e=`${n[1]}${(u[0]<<8|u[1]).toString(16)}:${(u[2]<<8|u[3]).toString(16)}`}let s=e.split("::");if(s.length>2)return null;let i=s[0]?s[0].split(":"):[],o=s.length===2&&s[1]?s[1].split(":"):[],a=8-i.length-o.length;if(s.length===1&&a!==0||a<0)return null;let c=[...i,...Array.from({length:s.length===2?a:0},()=>"0"),...o];if(c.length!==8||c.some((u)=>!/^[0-9a-f]{1,4}$/.test(u)))return null;return c.map((u)=>Number.parseInt(u,16))}function Pe(r){let e=Ee(r);if(!e)return!0;let t=`${e[6]>>8}.${e[6]&255}.${e[7]>>8}.${e[7]&255}`;if(e.slice(0,7).every((n)=>n===0))return!0;if(e.slice(0,5).every((n)=>n===0)&&e[5]===65535)return E(t);if(e[0]===100&&e[1]===65435&&e.slice(2,6).every((n)=>n===0))return E(t);if(e[0]===8194)return E(`${e[1]>>8}.${e[1]&255}.${e[2]>>8}.${e[2]&255}`);return(e[0]&65024)===64512||(e[0]&65472)===65152||(e[0]&65280)===65280||e[0]===8193&&e[1]===3512||e[0]===8193&&e[1]<512||e[0]===256}function B(r){let e=r.replace(/^\[|\]$/g,""),t=v(e.split("%")[0]);if(t===4)return E(e);if(t===6)return Pe(e);return!0}var Te=[".localhost",".local",".internal",".localdomain",".home.arpa"];function ke(r,e={}){let t;try{t=new URL(r)}catch{return{reason:"invalid",message:"Not a valid URL"}}if(!(e.allowHttp?["https:","http:"]:["https:"]).includes(t.protocol))return{reason:"scheme",message:e.allowHttp?"The URL must start with https:// or http://":"The URL must start with https://"};if(t.username||t.password)return{reason:"credentials",message:"The URL must not contain a username or password"};if(e.allowPrivate)return null;let s=t.hostname.replace(/^\[|\]$/g,"").toLowerCase().replace(/\.$/,"");if(!s)return{reason:"invalid",message:"Not a valid URL"};if(v(s.split("%")[0]))return B(s)?{reason:"private",message:"The URL points at a private or local address"}:null;if(s==="localhost"||!s.includes(".")||Te.some((i)=>s.endsWith(i)))return{reason:"private",message:"The URL points at a private or local address"};return null}async function j(r){return(await Ue(r,{all:!0,verbatim:!0})).map((t)=>t.address)}async function Be(r,e={}){let t=ke(r,e);if(t)throw new h(t.message,t.reason);let n=new URL(r);if(e.allowPrivate)return n;let s=n.hostname.replace(/^\[|\]$/g,"");if(v(s.split("%")[0]))return n;let i;try{i=await(e.resolve??j)(s)}catch{throw new h(`Could not resolve ${s}`,"unresolvable")}if(!i.length)throw new h(`Could not resolve ${s}`,"unresolvable");if(i.some(B))throw new h("The URL points at a private or local address","private");return n}async function Ce(r,e){let t=await Be(r,e),n=t.hostname.replace(/^\[|\]$/g,"");if(v(n.split("%")[0])||e.allowPrivate)return{parsed:t,addresses:[]};let s=await(e.resolve??j)(n);return{parsed:t,addresses:s}}var Ne=new Set([301,302,303,307,308]),Ke=["authorization","cookie","proxy-authorization"];function Me(r,e,t){if(!e)return[r.href,t];let n=new URL(r.href);n.hostname=v(e)===6?`[${e}]`:e;let s=new Headers(t.headers);s.set("Host",r.host);let i={...t,headers:s};if(r.protocol==="https:")i.tls={serverName:r.hostname.replace(/^\[|\]$/g,"")};return[n.href,i]}async function J(r,e){let t=Number(r.headers.get("content-length")??Number.NaN);if(Number.isFinite(t)&&t>e)throw new h(`The response is ${t} bytes, over the ${e} byte limit`,"too-large");if(!r.body)return r;let n=[],s=0,i=r.body.getReader();try{for(;;){let{done:c,value:u}=await i.read();if(c)break;if(s+=u.byteLength,s>e)throw await i.cancel(),new h(`The response is over the ${e} byte limit`,"too-large");n.push(u)}}finally{i.releaseLock()}let o=new Uint8Array(s),a=0;for(let c of n)o.set(c,a),a+=c.byteLength;return new Response(o,{status:r.status,statusText:r.statusText,headers:r.headers})}async function gt(r,e={},t={}){let n=t.maxRedirects??3,s=t.maxBytes??5242880,i=t.timeoutMs??1e4,o=AbortSignal.timeout(i),a=e.signal?AbortSignal.any([e.signal,o]):o,c=e.body==null||typeof e.body==="string"||e.body instanceof Uint8Array||e.body instanceof ArrayBuffer,u=r,g={...e,signal:a,redirect:"manual"},b="";for(let x=0;;x++){let{parsed:A,addresses:Z}=await Ce(u,t);if(b&&A.origin!==b){let R=new Headers(g.headers);for(let re of Ke)R.delete(re);g={...g,headers:R}}b=A.origin;let[ee,te]=Me(A,Z.find((R)=>!B(R)),g),d=await fetch(ee,te);if(!Ne.has(d.status))return await J(d,s);let C=d.headers.get("location");if(!C)return await J(d,s);if(x>=n)throw new h(`The URL redirected more than ${n} times`,"redirects");if(!c)throw new h("The URL redirected, and a streaming request body cannot be sent again","unreplayable-body");let N=(g.method??"GET").toUpperCase();if(d.status===303||(d.status===301||d.status===302)&&N!=="GET"&&N!=="HEAD")g={...g,method:"GET",body:void 0};u=new URL(C,A).href}}import{devNull as Oe}from"os";class Q extends Error{constructor(){super("Regular expression exceeded its execution budget");this.name="RegexTimeoutError"}}var Le=`
|
|
3
|
+
const { source, flags, input } = JSON.parse(await Bun.stdin.text());
|
|
4
|
+
try {
|
|
5
|
+
const match = new RegExp(source, flags).exec(input);
|
|
6
|
+
console.log(JSON.stringify({ match: match ? { value: match[0], index: match.index } : null }));
|
|
7
|
+
} catch {
|
|
8
|
+
console.log(JSON.stringify({ invalid: true }));
|
|
9
|
+
}
|
|
10
|
+
`;function pt(r,e,t={}){let n=t.timeoutMs??25;if(!Number.isInteger(n)||n<1||n>1000)throw RangeError("Regex timeout must be an integer between 1 and 1000 milliseconds");if(typeof r!=="string"||r.length>8192||typeof e!=="string"||e.length>65536)throw RangeError("Regex source or input exceeds its size limit");if(t.flags!==void 0&&typeof t.flags!=="string")throw TypeError("Regex flags must be a string");if(t.flags!==void 0&&t.flags.length>8)throw SyntaxError("Invalid regular expression flags");let s=Bun.which("bun");if(!s)throw Error("Bounded regex matching requires the Bun executable on PATH");let i=Bun.spawnSync([s,`--config=${Oe}`,"--no-env-file","--no-install","--eval",Le],{env:{},stdin:Buffer.from(JSON.stringify({source:r,flags:t.flags??"",input:e})),stdout:"pipe",stderr:"ignore",timeout:n,killSignal:"SIGKILL",maxBuffer:524288});if(i.signalCode==="SIGKILL")throw new Q;if(i.exitCode!==0)throw Error("Isolated regex execution failed");let o=JSON.parse(i.stdout.toString());if(o.invalid)throw SyntaxError("Invalid regular expression");if(o.match===null)return null;if(!o.match||typeof o.match.value!=="string"||!Number.isInteger(o.match.index))throw Error("Invalid isolated regex response");return o.match}export{l as InvalidWebhookSignature,h as OutboundUrlError,Q as RegexTimeoutError,Se as apiKeyHint,ge as argon2Encode,Be as assertPublicOutboundUrl,Ye as base64Encode,fe as bcryptEncode,je as check,De as decrypt,W as detectAlgorithm,Ge as encrypt,gt as fetchPublicUrl,ut as generateApiKey,tt as generateAppKey,it as generateWebhookSecret,Re as hashApiKey,je as hashCheck,W as hashDetectAlgorithm,le as hashInfo,Xe as hashMake,Je as hashNeedsRehash,le as info,B as isPrivateAddress,Ve as legacyEncrypt,Xe as make,Xe as makeHash,pt as matchBoundedRegex,Je as needsRehash,ke as outboundUrlProblem,st as signWebhook,Qe as timingSafeEqualString,xe as verifyGithub,je as verifyHash,ve as verifyHmac,be as verifySignedWebhook,we as verifyStripe,ot as verifyWebhook};
|
package/dist/regex.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Match an untrusted expression with an enforced process deadline.
|
|
3
|
+
*
|
|
4
|
+
* Use ordinary RegExp for trusted application constants. A synchronous engine
|
|
5
|
+
* match cannot be interrupted by a JavaScript timer, and Bun worker/vm shutdown
|
|
6
|
+
* may wait for a running regex. A separate process can be killed by the OS.
|
|
7
|
+
* This deliberately pays process startup cost on every untrusted match rather
|
|
8
|
+
* than trusting a sample input to certify every future subject.
|
|
9
|
+
*
|
|
10
|
+
* Failure throws: a failed security check must not look like a clean non-match.
|
|
11
|
+
* Matching is stateless and returns the first complete match, without captures.
|
|
12
|
+
* Source is capped at 8Ki UTF-16 code units and input at 64Ki.
|
|
13
|
+
*/
|
|
14
|
+
export declare function matchBoundedRegex(source: string, input: string, options?: BoundedRegexOptions): BoundedRegexMatch | null;
|
|
15
|
+
export declare interface BoundedRegexOptions {
|
|
16
|
+
flags?: string
|
|
17
|
+
timeoutMs?: number
|
|
18
|
+
}
|
|
19
|
+
export declare interface BoundedRegexMatch {
|
|
20
|
+
value: string
|
|
21
|
+
index: number
|
|
22
|
+
}
|
|
23
|
+
export declare class RegexTimeoutError extends Error {
|
|
24
|
+
constructor();
|
|
25
|
+
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/security",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.199",
|
|
6
6
|
"description": "The Stacks framework security.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -57,13 +57,13 @@
|
|
|
57
57
|
"prepublishOnly": "bun run build"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@stacksjs/config": "0.75.
|
|
60
|
+
"@stacksjs/config": "0.75.199",
|
|
61
61
|
"ts-security-crypto": "0.0.2"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
|
-
"@stacksjs/env": "0.75.
|
|
65
|
-
"@stacksjs/types": "0.75.
|
|
66
|
-
"@stacksjs/validation": "0.75.
|
|
64
|
+
"@stacksjs/env": "0.75.199",
|
|
65
|
+
"@stacksjs/types": "0.75.199",
|
|
66
|
+
"@stacksjs/validation": "0.75.199",
|
|
67
67
|
"better-dx": "^0.2.24"
|
|
68
68
|
}
|
|
69
69
|
}
|