@stacksjs/security 0.75.162 → 0.75.163

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.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- var T=import.meta.require;import{Buffer as d}from"buffer";import{config as E}from"@stacksjs/config";import{decrypt as A,encrypt as Z}from"ts-security-crypto";function S(r){return r.startsWith("base64:")?r.slice(7):r}var k=1,B=600000,N=32,m=16,g=12,U=64,u=new Map;function V(r,e,t){return`${t}:${d.from(e).toString("base64")}:${r}`}async function C(r,e,t){let n=V(r,e,t),i=u.get(n);if(i)return i.hits++,u.delete(n),u.set(n,i),i.key;let o=await crypto.subtle.importKey("raw",new TextEncoder().encode(r),{name:"PBKDF2"},!1,["deriveKey"]),s=await crypto.subtle.deriveKey({name:"PBKDF2",salt:new Uint8Array(e),iterations:t,hash:"SHA-256"},o,{name:"AES-GCM",length:N*8},!1,["encrypt","decrypt"]);if(u.set(n,{key:s,hits:1}),u.size>U){let a=u.keys().next().value;if(a!==void 0)u.delete(a)}return s}async function D(r,e){let t=crypto.getRandomValues(new Uint8Array(m)),n=crypto.getRandomValues(new Uint8Array(g)),i=await C(e,t,B),o=await crypto.subtle.encrypt({name:"AES-GCM",iv:n},i,new TextEncoder().encode(r)),s=new Uint8Array(o),a=new Uint8Array(1+m+g+s.byteLength);return a[0]=k,a.set(t,1),a.set(n,1+m),a.set(s,1+m+g),d.from(a).toString("base64")}async function O(r,e){let t=d.from(r,"base64");if(t.length<1+m+g+16)return null;if(t[0]!==k)return null;let n=new Uint8Array(t.subarray(1,1+m)),i=new Uint8Array(t.subarray(1+m,1+m+g)),o=new Uint8Array(t.subarray(1+m+g)),s=await C(e,n,B);try{let a=await crypto.subtle.decrypt({name:"AES-GCM",iv:i},s,o);return new TextDecoder().decode(a)}catch{return null}}async function rr(r,e){if(!r&&r!=="")throw Error("encrypt() requires a string message");let t=e||E.app.key;if(!t)throw Error("APP_KEY is not defined");return await D(r,S(t))}async function tr(r,e){if(!r)throw Error("decrypt() requires a non-empty encrypted string");let t=e||E.app.key;if(!t)throw Error("APP_KEY is not defined");let n=S(t),i=await O(r,n);if(i!==null)return i;try{return await A(r,n)}catch(o){if(n!==t)try{return await A(r,t)}catch{}throw o}}function er(){u.clear()}import{timingSafeEqual as P}from"crypto";import{base64Encode as sr,hashPassword as v,verifyPassword as K}from"ts-security-crypto";var p=null;function h(){if(p)return p;try{let{hashing:r}=T("@stacksjs/config");return p=r||{},p}catch{return console.warn("[Security] Failed to load hashing config, using defaults"),p={driver:"bcrypt",bcrypt:{rounds:12},argon2:{memory:65536,time:2}},p}}function M(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 q(r){let e=M(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+)/),i=r.match(/m=(\d+)/),o=r.match(/t=(\d+)/),s=r.match(/p=(\d+)/);if(n&&n[1]!==void 0)t.version=Number.parseInt(n[1],10);if(i&&i[1]!==void 0)t.memory=Number.parseInt(i[1],10);if(o&&o[1]!==void 0)t.rounds=Number.parseInt(o[1],10);if(s&&s[1]!==void 0)t.parallelism=Number.parseInt(s[1],10)}return{algorithm:e,options:t}}function ar(r,e){let t=q(r),n=h(),i=e?.algorithm||n.driver||"bcrypt",o=(f)=>{if(f==="argon2")return"argon2id";return f},s=o(t.algorithm),a=o(i);if(s!==a)return!0;if(s==="bcrypt"){let f=e?.rounds||n.bcrypt?.rounds||12;if(t.options.rounds!==f)return!0}if(s.startsWith("argon2")){let f=e?.memory||n.argon2?.memory||65536,y=e?.time||n.argon2?.time||2;if(t.options.memory!==f||t.options.rounds!==y)return!0}return!1}async function cr(r,e){let t=h(),n=e?.algorithm||t.driver||"bcrypt";if(n==="bcrypt")return await R(r,e?.rounds);if(n==="argon2"||n==="argon2id"||n==="argon2i"||n==="argon2d")return await Y(r,{type:n==="argon2"?"argon2id":n,memory:e?.memory,time:e?.time});throw Error(`Unsupported hashing algorithm: ${n}`)}async function mr(r,e){if(!r||!e)return!1;if(M(e)==="unknown")try{return await K(r,e)}catch{return!1}return await K(r,e)}async function R(r,e){let t=h(),n=e||t.bcrypt?.rounds||12;return await v(r,{algorithm:"bcrypt",cost:n})}async function Y(r,e){let t=h(),n=e?.type||"argon2id",i=e?.memory||t.argon2?.memory||65536,o=e?.time||t.argon2?.time||2;return await v(r,{algorithm:n,memoryCost:i,timeCost:o})}function fr(r,e){let t=Buffer.from(r),n=Buffer.from(e);if(t.length!==n.length)return P(t,t),!1;return P(t,n)}import{generateKey as F}from"ts-security-crypto";function pr(){return F(32)}import{createHmac as G,timingSafeEqual as H}from"crypto";class c extends Error{reason;constructor(r,e="mismatch"){super(r);this.name="InvalidWebhookSignature",this.reason=e}}function l(r){if(!/^[0-9a-f]*$/i.test(r)||r.length%2!==0)throw new c("Signature is not valid hex","malformed");return Buffer.from(r,"hex")}function w(r,e){if(r.length!==e.length)return H(r,r),!1;return H(r,e)}function b(r,e,t){return G(t,r).update(e).digest()}function z(r){let e=r.split(","),t=Number.NaN,n=[];for(let i of e){let o=i.indexOf("=");if(o===-1)continue;let s=i.slice(0,o).trim(),a=i.slice(o+1).trim();if(s==="t")t=Number.parseInt(a,10);else if(s==="v1")n.push(a)}if(!Number.isFinite(t)||n.length===0)throw new c("Stripe-Signature header is malformed","malformed");return{timestamp:t,signatures:n}}function I(r,e,t,n={}){if(!r)throw new c("Stripe webhook secret is empty","missing");if(!e)throw new c("Stripe-Signature header is missing","missing");let{timestamp:i,signatures:o}=z(e),s=n.toleranceSeconds??300;if(Math.floor((n.now??Date.now())/1000)-i>s)throw new c("Stripe webhook timestamp is outside tolerance window","expired");let f=typeof t==="string"?t:t.toString("utf8"),y=b(r,`${i}.${f}`,"sha256");for(let _ of o){let x;try{x=l(_)}catch{continue}if(w(y,x))return!0}throw new c("Stripe webhook signature did not match","mismatch")}function W(r,e,t){if(!r)throw new c("GitHub webhook secret is empty","missing");if(!e)throw new c("X-Hub-Signature-256 header is missing","missing");let n="sha256=";if(!e.startsWith(n))throw new c("X-Hub-Signature-256 has wrong algorithm prefix","malformed");let i=l(e.slice(n.length)),o=b(r,t,"sha256");if(!w(o,i))throw new c("GitHub webhook signature did not match","mismatch");return!0}function X(r,e,t,n="sha256"){if(!r)throw new c("HMAC secret is empty","missing");if(!t)throw new c("Signature header is missing","missing");let i=t.indexOf("="),o=i!==-1&&/^[a-z0-9]+$/i.test(t.slice(0,i))?t.slice(i+1):t,s=l(o),a=b(r,e,n);if(!w(a,s))throw new c("HMAC signature did not match","mismatch");return!0}function dr(r,e,t,n,i={}){switch(r){case"stripe":return I(e,t,n,i);case"github":return W(e,t,n);case"generic":return X(e,n,t,i.algorithm??"sha256");default:throw new c(`Unknown webhook provider: ${String(r)}`,"malformed")}}export{c as InvalidWebhookSignature,er as _resetCryptCacheForTests,Y as argon2Encode,sr as base64Encode,R as bcryptEncode,mr as check,tr as decrypt,M as detectAlgorithm,rr as encrypt,pr as generateAppKey,mr as hashCheck,M as hashDetectAlgorithm,q as hashInfo,cr as hashMake,ar as hashNeedsRehash,q as info,Z as legacyEncrypt,cr as make,cr as makeHash,ar as needsRehash,fr as timingSafeEqualString,W as verifyGithub,mr as verifyHash,X as verifyHmac,I as verifyStripe,dr as verifyWebhook};
2
+ var V=import.meta.require;import{Buffer as l}from"buffer";import{config as E}from"@stacksjs/config";import{decrypt as A,encrypt as nr}from"ts-security-crypto";function k(r){return r.startsWith("base64:")?r.slice(7):r}var S=1,B=600000,U=32,f=16,g=12,D=64,u=new Map;function O(r,e,t){return`${t}:${l.from(e).toString("base64")}:${r}`}async function C(r,e,t){let n=O(r,e,t),i=u.get(n);if(i)return i.hits++,u.delete(n),u.set(n,i),i.key;let o=await crypto.subtle.importKey("raw",new TextEncoder().encode(r),{name:"PBKDF2"},!1,["deriveKey"]),s=await crypto.subtle.deriveKey({name:"PBKDF2",salt:new Uint8Array(e),iterations:t,hash:"SHA-256"},o,{name:"AES-GCM",length:U*8},!1,["encrypt","decrypt"]);if(u.set(n,{key:s,hits:1}),u.size>D){let a=u.keys().next().value;if(a!==void 0)u.delete(a)}return s}async function W(r,e){let t=crypto.getRandomValues(new Uint8Array(f)),n=crypto.getRandomValues(new Uint8Array(g)),i=await C(e,t,B),o=await crypto.subtle.encrypt({name:"AES-GCM",iv:n},i,new TextEncoder().encode(r)),s=new Uint8Array(o),a=new Uint8Array(1+f+g+s.byteLength);return a[0]=S,a.set(t,1),a.set(n,1+f),a.set(s,1+f+g),l.from(a).toString("base64")}async function q(r,e){let t=l.from(r,"base64");if(t.length<1+f+g+16)return null;if(t[0]!==S)return null;let n=new Uint8Array(t.subarray(1,1+f)),i=new Uint8Array(t.subarray(1+f,1+f+g)),o=new Uint8Array(t.subarray(1+f+g)),s=await C(e,n,B);try{let a=await crypto.subtle.decrypt({name:"AES-GCM",iv:i},s,o);return new TextDecoder().decode(a)}catch{return null}}async function ir(r,e){if(!r&&r!=="")throw Error("encrypt() requires a string message");let t=e||E.app.key;if(!t)throw Error("APP_KEY is not defined");return await W(r,k(t))}async function or(r,e){if(!r)throw Error("decrypt() requires a non-empty encrypted string");let t=e||E.app.key;if(!t)throw Error("APP_KEY is not defined");let n=k(t),i=await q(r,n);if(i!==null)return i;try{return await A(r,n)}catch(o){if(n!==t)try{return await A(r,t)}catch{}throw o}}function sr(){u.clear()}import{timingSafeEqual as P}from"crypto";import{base64Encode as fr,hashPassword as M,verifyPassword as K}from"ts-security-crypto";var h=null;function p(){if(h)return h;try{let{hashing:r}=V("@stacksjs/config");return h=r||{},h}catch{return console.warn("[Security] Failed to load hashing config, using defaults"),h={driver:"bcrypt",bcrypt:{rounds:12},argon2:{memory:65536,time:2}},h}}function v(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 R(r){let e=v(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+)/),i=r.match(/m=(\d+)/),o=r.match(/t=(\d+)/),s=r.match(/p=(\d+)/);if(n&&n[1]!==void 0)t.version=Number.parseInt(n[1],10);if(i&&i[1]!==void 0)t.memory=Number.parseInt(i[1],10);if(o&&o[1]!==void 0)t.rounds=Number.parseInt(o[1],10);if(s&&s[1]!==void 0)t.parallelism=Number.parseInt(s[1],10)}return{algorithm:e,options:t}}function ur(r,e){let t=R(r),n=p(),i=e?.algorithm||n.driver||"bcrypt",o=(m)=>{if(m==="argon2")return"argon2id";return m},s=o(t.algorithm),a=o(i);if(s!==a)return!0;if(s==="bcrypt"){let m=e?.rounds||n.bcrypt?.rounds||12;if(t.options.rounds!==m)return!0}if(s.startsWith("argon2")){let m=e?.memory||n.argon2?.memory||65536,d=e?.time||n.argon2?.time||2;if(t.options.memory!==m||t.options.rounds!==d)return!0}return!1}async function gr(r,e){let t=p(),n=e?.algorithm||t.driver||"bcrypt";if(n==="bcrypt")return await Y(r,e?.rounds);if(n==="argon2"||n==="argon2id"||n==="argon2i"||n==="argon2d")return await F(r,{type:n==="argon2"?"argon2id":n,memory:e?.memory,time:e?.time});throw Error(`Unsupported hashing algorithm: ${n}`)}async function hr(r,e){if(!r||!e)return!1;if(v(e)==="unknown")try{return await K(r,e)}catch{return!1}return await K(r,e)}async function Y(r,e){let t=p(),n=e||t.bcrypt?.rounds||12;return await M(r,{algorithm:"bcrypt",cost:n})}async function F(r,e){let t=p(),n=e?.type||"argon2id",i=e?.memory||t.argon2?.memory||65536,o=e?.time||t.argon2?.time||2;return await M(r,{algorithm:n,memoryCost:i,timeCost:o})}function pr(r,e){let t=Buffer.from(r),n=Buffer.from(e);if(t.length!==n.length)return P(t,t),!1;return P(t,n)}import{generateKey as G}from"ts-security-crypto";function lr(){return G(32)}import{createHmac as z,randomBytes as X,timingSafeEqual as H}from"crypto";class c extends Error{reason;constructor(r,e="mismatch"){super(r);this.name="InvalidWebhookSignature",this.reason=e}}function w(r){if(!/^[0-9a-f]*$/i.test(r)||r.length%2!==0)throw new c("Signature is not valid hex","malformed");return Buffer.from(r,"hex")}function b(r,e){if(r.length!==e.length)return H(r,r),!1;return H(r,e)}function y(r,e,t){return z(t,r).update(e).digest()}function I(r,e){let t=r.split(","),n=Number.NaN,i=[];for(let o of t){let s=o.indexOf("=");if(s===-1)continue;let a=o.slice(0,s).trim(),m=o.slice(s+1).trim();if(a==="t")n=Number.parseInt(m,10);else if(a==="v1")i.push(m)}if(!Number.isFinite(n)||i.length===0)throw new c(`${e} header is malformed`,"malformed");return{timestamp:n,signatures:i}}function T(r,e,t,n,i){if(!r)throw new c(`${i.name} secret is empty`,"missing");if(!e)throw new c(`${i.header} header is missing`,"missing");let{timestamp:o,signatures:s}=I(e,i.header),a=n.toleranceSeconds??300,m=Math.floor((n.now??Date.now())/1000);if(Math.abs(m-o)>a)throw new c(`${i.name} timestamp is outside tolerance window`,"expired");let d=typeof t==="string"?t:t.toString("utf8"),_=y(r,`${o}.${d}`,"sha256");for(let N of s){let x;try{x=w(N)}catch{continue}if(b(_,x))return!0}throw new c(`${i.name} signature did not match`,"mismatch")}function L(r,e,t,n={}){return T(r,e,t,n,{name:"Stripe webhook",header:"Stripe-Signature"})}function j(r,e,t,n={}){return T(r,e,t,n,{name:"Webhook",header:"Signature"})}function xr(r,e,t={}){let n=(Array.isArray(r)?r:[r]).filter((a)=>typeof a==="string"&&a.length>0);if(!n.length)throw new c("Webhook signing secret is empty","missing");let i=t.timestamp??Math.floor(Date.now()/1000);if(!Number.isInteger(i)||i<0)throw new c("Webhook timestamp must be whole unix seconds","malformed");let o=typeof e==="string"?e:e.toString("utf8"),s=n.map((a)=>y(a,`${i}.${o}`,"sha256").toString("hex"));return{timestamp:i,signatures:s,header:[`t=${i}`,...s.map((a)=>`v1=${a}`)].join(",")}}function Ar(r="whsec_"){return`${r}${X(32).toString("base64url")}`}function J(r,e,t){if(!r)throw new c("GitHub webhook secret is empty","missing");if(!e)throw new c("X-Hub-Signature-256 header is missing","missing");let n="sha256=";if(!e.startsWith(n))throw new c("X-Hub-Signature-256 has wrong algorithm prefix","malformed");let i=w(e.slice(n.length)),o=y(r,t,"sha256");if(!b(o,i))throw new c("GitHub webhook signature did not match","mismatch");return!0}function Q(r,e,t,n="sha256"){if(!r)throw new c("HMAC secret is empty","missing");if(!t)throw new c("Signature header is missing","missing");let i=t.indexOf("="),o=i!==-1&&/^[a-z0-9]+$/i.test(t.slice(0,i))?t.slice(i+1):t,s=w(o),a=y(r,e,n);if(!b(a,s))throw new c("HMAC signature did not match","mismatch");return!0}function $r(r,e,t,n,i={}){switch(r){case"stripe":return L(e,t,n,i);case"github":return J(e,t,n);case"generic":return Q(e,n,t,i.algorithm??"sha256");case"signed":return j(e,t,n,i);default:throw new c(`Unknown webhook provider: ${String(r)}`,"malformed")}}export{c as InvalidWebhookSignature,sr as _resetCryptCacheForTests,F as argon2Encode,fr as base64Encode,Y as bcryptEncode,hr as check,or as decrypt,v as detectAlgorithm,ir as encrypt,lr as generateAppKey,Ar as generateWebhookSecret,hr as hashCheck,v as hashDetectAlgorithm,R as hashInfo,gr as hashMake,ur as hashNeedsRehash,R as info,nr as legacyEncrypt,gr as make,gr as makeHash,ur as needsRehash,xr as signWebhook,pr as timingSafeEqualString,J as verifyGithub,hr as verifyHash,Q as verifyHmac,j as verifySignedWebhook,L as verifyStripe,$r as verifyWebhook};
package/dist/webhook.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  * 1. Parse `t` and `v1=` from the header.
8
8
  * 2. Compute HMAC-SHA256(`${t}.${body}`, secret).
9
9
  * 3. Compare in constant time against any v1 entry.
10
- * 4. Reject if `t` is older than `toleranceSeconds` to thwart replay.
10
+ * 4. Reject if `t` is further than `toleranceSeconds` from now, to thwart replay.
11
11
  *
12
12
  * @example
13
13
  * ```ts
@@ -15,6 +15,43 @@
15
15
  * ```
16
16
  */
17
17
  export declare function verifyStripe(secret: string, signatureHeader: string, body: string | Buffer, options?: VerifyWebhookOptions): true;
18
+ /**
19
+ * Verify a webhook signed by {@link signWebhook} (or any sender using the same
20
+ * `t=<unix>,v1=<hex>` scheme over `${t}.${body}`).
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * verifySignedWebhook(secret, request.headers.get('x-signature')!, rawBody)
25
+ * ```
26
+ */
27
+ export declare function verifySignedWebhook(secret: string, signatureHeader: string, body: string | Buffer, options?: VerifyWebhookOptions): true;
28
+ /**
29
+ * Sign an outgoing webhook body, for the receiver to check with
30
+ * {@link verifySignedWebhook} (or {@link verifyStripe}, which accepts the
31
+ * same scheme).
32
+ *
33
+ * The signed payload is `${timestamp}.${body}`, so a captured request cannot
34
+ * be replayed later with a fresh timestamp. Pass several secrets while
35
+ * rotating one: the header carries a `v1` entry for each, and a receiver that
36
+ * knows either secret accepts it.
37
+ *
38
+ * Sign the exact string you send: re-serializing the JSON on either side
39
+ * breaks the signature.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * const body = JSON.stringify(event)
44
+ * const { header } = signWebhook(endpoint.secret, body)
45
+ * await fetch(endpoint.url, { method: 'POST', body, headers: { 'Content-Type': 'application/json', 'X-Signature': header } })
46
+ * ```
47
+ */
48
+ export declare function signWebhook(secret: string | string[], body: string | Buffer, options?: { timestamp?: number }): SignedWebhook;
49
+ /**
50
+ * A new random webhook signing secret: `prefix` + 32 random bytes, base64url.
51
+ * Show it to its owner once; store it encrypted (the sender needs the
52
+ * plaintext to sign), never hashed.
53
+ */
54
+ export declare function generateWebhookSecret(prefix?: string): string;
18
55
  /**
19
56
  * Verify a GitHub webhook signature.
20
57
  *
@@ -50,7 +87,7 @@ export declare function verifyHmac(secret: string, body: string | Buffer, signat
50
87
  * {@link InvalidWebhookSignature} on any failure (malformed header, expired
51
88
  * timestamp, mismatched HMAC, or missing input).
52
89
  *
53
- * @param provider 'stripe' | 'github' | 'generic'
90
+ * @param provider 'stripe' | 'github' | 'generic' | 'signed' ({@link signWebhook}'s scheme)
54
91
  * @param secret Provider's signing secret (`whsec_...` for Stripe, etc.)
55
92
  * @param signature Raw header value as received from the request
56
93
  * @param body Raw request body — must be the bytes-on-the-wire, not parsed JSON
@@ -79,10 +116,18 @@ export declare interface VerifyWebhookOptions {
79
116
  algorithm?: 'sha256' | 'sha512' | 'sha1'
80
117
  now?: number
81
118
  }
119
+ /**
120
+ * The signature a sender attaches to an outgoing webhook.
121
+ */
122
+ export declare interface SignedWebhook {
123
+ timestamp: number
124
+ signatures: string[]
125
+ header: string
126
+ }
82
127
  /**
83
128
  * Supported webhook providers with built-in signature schemes.
84
129
  */
85
- export type WebhookProvider = 'stripe' | 'github' | 'generic';
130
+ export type WebhookProvider = 'stripe' | 'github' | 'generic' | 'signed';
86
131
  /**
87
132
  * Thrown when a webhook signature fails verification.
88
133
  *
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.162",
5
+ "version": "0.75.163",
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.162",
60
+ "@stacksjs/config": "0.75.163",
61
61
  "ts-security-crypto": "0.0.2"
62
62
  },
63
63
  "devDependencies": {
64
- "@stacksjs/env": "0.75.162",
65
- "@stacksjs/types": "0.75.162",
66
- "@stacksjs/validation": "0.75.162",
64
+ "@stacksjs/env": "0.75.163",
65
+ "@stacksjs/types": "0.75.163",
66
+ "@stacksjs/validation": "0.75.163",
67
67
  "better-dx": "^0.2.24"
68
68
  }
69
69
  }