@stacksjs/env 0.74.45 → 0.74.47

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 CHANGED
@@ -379,6 +379,15 @@ loadEnv({
379
379
  })
380
380
  ```
381
381
 
382
+ ### Lazy reads across projects
383
+
384
+ `resolvePrivateKey({ env, cwd })` and `decryptEnvValue(value, { env, cwd })`
385
+ cache the last lookup by environment and resolved project directory. Switching
386
+ projects resolves that project's key instead of reusing the previous project's
387
+ key or cached missing-key result. Equivalent directory paths share the warm
388
+ lookup. Key-file changes within the same project still require an explicit
389
+ `resetPrivateKeyCache()` before the next read.
390
+
382
391
  ### `encryptEnv(options)`
383
392
 
384
393
  Encrypt a .env file.
package/dist/cli.js CHANGED
@@ -1,32 +1,32 @@
1
1
  // @bun
2
- import{existsSync as K,readFileSync as k,renameSync as ne,rmSync as re,writeFileSync as b}from"fs";import{basename as se,dirname as j,resolve as E}from"path";import{createCipheriv as X,createDecipheriv as H,createHash as I,createPrivateKey as fe,createPublicKey as G,diffieHellman as J,generateKeyPairSync as M,hkdfSync as z,randomBytes as S}from"crypto";var Y="x25519-public:",A="x25519-private:",C="encrypted:v2:",U="encrypted:",q=Buffer.from("stacks-env:v2","utf8"),Z="Environment decryption failed: authentication or format error";function Q(t){if(t.length===32)return t;return I("sha256").update(t).digest()}function Be(t,n){let e=S(16),s=X("aes-256-gcm",Q(n),e);return{ciphertext:Buffer.concat([s.update(t,"utf8"),s.final()]).toString("hex"),iv:e.toString("hex"),authTag:s.getAuthTag().toString("hex")}}function le(t,n,e,s){try{let r=H("aes-256-gcm",Q(n),Buffer.from(e,"hex"));return r.setAuthTag(Buffer.from(s,"hex")),Buffer.concat([r.update(Buffer.from(t,"hex")),r.final()]).toString("utf8")}catch{throw Error("Decryption failed: authentication or format error")}}function x(t){return t.toString("base64url")}function V(t,n,e){if(typeof t!=="string"||t.length>0&&!/^[A-Za-z0-9_-]+$/.test(t))throw Error(`${n} is not canonical base64url`);let s=Buffer.from(t,"base64url");if(x(s)!==t)throw Error(`${n} is not canonical base64url`);if(e!==void 0&&s.length!==e)throw Error(`${n} has an invalid length`);return s}function pe(t){if(!t.startsWith(Y))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let n=G({key:V(t.slice(Y.length),"public key"),format:"der",type:"spki"});if(n.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return n}function de(t){if(!t.startsWith(A))throw Error("Private key is not a version 2 X25519 key");let n=fe({key:V(t.slice(A.length),"private key"),format:"der",type:"pkcs8"});if(n.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return n}function ee(t){return Buffer.from(`stacks-env:v2;${t.epk};${t.salt};${t.nonce}`,"utf8")}function F(){let{publicKey:t,privateKey:n}=M("x25519"),e=t.export({format:"der",type:"spki"}),s=n.export({format:"der",type:"pkcs8"});return{publicKey:`${Y}${x(e)}`,privateKey:`${A}${x(s)}`}}function W(t,n){let e=pe(n),s=M("x25519"),r=x(s.publicKey.export({format:"der",type:"spki"})),i=S(16),o=S(12),a=J({privateKey:s.privateKey,publicKey:e}),u=Buffer.from(z("sha256",a,i,q,32)),f={epk:r,salt:x(i),nonce:x(o)};try{let c=X("aes-256-gcm",u,o);c.setAAD(ee(f));let l=Buffer.concat([c.update(t,"utf8"),c.final()]),p={v:2,...f,ciphertext:x(l),tag:x(c.getAuthTag())};return`${C}${x(Buffer.from(JSON.stringify(p),"utf8"))}`}finally{a.fill(0),u.fill(0)}}function ye(t){let n=V(t.slice(C.length),"envelope").toString("utf8"),e=JSON.parse(n);if(!e||typeof e!=="object"||Array.isArray(e))throw Error("envelope is not an object");let s=Object.keys(e).sort(),r=["ciphertext","epk","nonce","salt","tag","v"];if(s.length!==r.length||s.some((i,o)=>i!==r[o]))throw Error("envelope fields are invalid");if(e.v!==2)throw Error("envelope version is unsupported");for(let i of["epk","salt","nonce","ciphertext","tag"])if(typeof e[i]!=="string")throw Error(`${i} is invalid`);return e}function ge(t,n){try{let e=ye(t),s=de(n),r=G({key:V(e.epk,"ephemeral public key"),format:"der",type:"spki"});if(r.asymmetricKeyType!=="x25519")throw Error("ephemeral key is not X25519");let i=V(e.salt,"salt",16),o=V(e.nonce,"nonce",12),a=V(e.ciphertext,"ciphertext"),u=V(e.tag,"tag",16),f=J({privateKey:s,publicKey:r}),c=Buffer.from(z("sha256",f,i,q,32));try{let l=H("aes-256-gcm",c,o);return l.setAAD(ee(e)),l.setAuthTag(u),Buffer.concat([l.update(a),l.final()]).toString("utf8")}finally{f.fill(0),c.fill(0)}}catch{throw Error(Z)}}function he(t,n){try{if(!/^[0-9a-f]{64}$/.test(n))throw Error("invalid legacy private key");let e=Buffer.from(t.slice(U.length),"base64");if(e.length<32)throw Error("invalid legacy payload");let s=I("sha256").update(Buffer.from(n,"hex")).digest(),r=I("sha256").update(s).digest();return le(e.subarray(32).toString("hex"),r,e.subarray(0,16).toString("hex"),e.subarray(16,32).toString("hex"))}catch{throw Error(Z)}}function Ie(t){return t.startsWith(U)&&!t.startsWith(C)}function R(t,n){if(!t.startsWith(U))return t;return t.startsWith(C)?ge(t,n):he(t,n)}function Se(t,n,e){return W(R(t,n),e)}function Ye(t){let n=t.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return n&&n[1]?n[1].toLowerCase():""}function Ae(t=""){return t?process.env[`DOTENV_PRIVATE_KEY_${t.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}import{spawnSync as me}from"child_process";import{readFileSync as ve}from"fs";import{arch as Ee,hostname as $e,platform as te,release as ke,userInfo as Ke}from"os";import{basename as we,dirname as be,resolve as Ne}from"path";function T(t,n={}){let e={},s=[],r=[],i=t.split(`
3
- `);for(let o of i){let a=o.trim();if(!a||a.startsWith("#"))continue;if(a.startsWith("DOTENV_PUBLIC_KEY=")){let l=a.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(l&&l[1]!==void 0)e.DOTENV_PUBLIC_KEY=l[1];continue}let u=a.match(/^([^=]+)=(.*)$/);if(!u||u[1]===void 0||u[2]===void 0)continue;let f=u[1].trim(),c=u[2].trim();if(c.startsWith('"')&&c.endsWith('"')||c.startsWith("'")&&c.endsWith("'")){if(c=c.slice(1,-1),c.includes("\\n"))c=c.replace(/\\n/g,`
4
- `)}if(c.startsWith("encrypted:")||c.startsWith("enc:")){if(!n.privateKey){r.push(f);continue}try{let l=c.startsWith("enc:")?`encrypted:${c.slice(4)}`:c;c=R(l,n.privateKey)}catch(l){s.push(`Failed to decrypt ${f}: ${l instanceof Error?l.message:"Unknown error"}`)}}c=_e(c,{...n.processEnv||process.env,...e}),c=Te(c),e[f]=c}return{parsed:e,errors:s,skippedEncrypted:r}}function _e(t,n){return t.replace(/\$\{([^}]+)\}/g,(e,s)=>{let r=s.match(/^([^:\-+]+)(:-|-)(.+)$/);if(r){let[,o,a,u]=r,f=n[o];if(a===":-")return f||u;else return f!==void 0?f:u}let i=s.match(/^([^:\-+]+)(:?\+)(.+)$/);if(i){let[,o,a,u]=i,f=n[o];if(a===":+")return f?u:"";else return f!==void 0?u:""}return n[s]||""})}var xe=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function Pe(t){let n=[],e="",s=null,r=!1;for(let i of t.trim()){if(r){e+=i,r=!1;continue}if(i==="\\"&&s!=="'"){r=!0;continue}if(s){if(i===s)s=null;else e+=i;continue}if(i==='"'||i==="'"){s=i;continue}if(/\s/.test(i)){if(e)n.push(e),e="";continue}e+=i}if(r||s)throw Error("Command substitution contains an unterminated escape or quote");if(e)n.push(e);return n}function Ve(t){let[n,...e]=t,s=e.some((r)=>r.startsWith("-"));if(n==="echo"&&!s)return e.join(" ");if(n==="printf"&&e.length===1&&!e[0]?.includes("%"))return e[0];if(n==="hostname"&&e.length===0)return $e();if(n==="whoami"&&e.length===0)return Ke().username;if(n==="pwd"&&e.length===0)return process.cwd();if(n==="basename"&&e.length===1)return we(e[0]??"");if(n==="dirname"&&e.length===1)return be(e[0]??"");if(n==="cat"&&e.length>0&&!s)return e.map((r)=>ve(Ne(process.cwd(),r),"utf8")).join("");if(n==="uname"){let r=e[0]??"-s";if(e.length<=1&&r==="-m")return Ee();if(e.length<=1&&r==="-r")return ke();if(e.length<=1&&r==="-s")return{aix:"AIX",android:"Android",cygwin:"CYGWIN",darwin:"Darwin",freebsd:"FreeBSD",haiku:"Haiku",linux:"Linux",netbsd:"NetBSD",openbsd:"OpenBSD",sunos:"SunOS",win32:"Windows_NT"}[te()]??te()}return}function Te(t){return t.replace(/\$\(([^)]+)\)/g,(n,e)=>{try{let s=Pe(e),r=s[0];if(!r||!xe.has(r))return console.warn(`[env] Blocked command substitution for disallowed command: ${r}`),"";let i=Ve(s);if(i!==void 0)return i.trim();let o=me(r,s.slice(1),{encoding:"utf8",env:process.env});if(o.status===0)return o.stdout.trim();let a=o.stderr.trim();console.warn(`[env] Command substitution failed (exit code ${o.status??"unknown"}): ${r}${a?`: ${a}`:""}`)}catch(s){console.warn(`[env] Command substitution error: ${s instanceof Error?s.message:String(s)}`)}return""})}async function Je(t,n={}){let e={},s=[],r=[];for(let i of t)try{let o=Bun.file(i);if(!o.size)continue;let a=await o.text(),{parsed:u,errors:f,skippedEncrypted:c}=T(a,n);s.push(...f),r.push(...c);for(let[l,p]of Object.entries(u)){if(!n.overload&&e[l]!==void 0)continue;e[l]=p}}catch(o){if(o?.code==="ENOENT")continue;s.push(`Failed to read ${i}: ${o instanceof Error?o.message:String(o)}`)}return{parsed:e,errors:s,skippedEncrypted:r}}function et(t,n){if(t)return t;let e=String(n??"").trim();if(!e||e==="development"||e==="dev"||e==="local")return"";if(!/^[\w.-]+$/.test(e))return"";return`.env.${e}`}function oe(t,n){return(!n.key||t.includes(n.key))&&(!n.excludeKey||!t.includes(n.excludeKey))}function Oe(t,n){let e=[];for(let s of t.split(`
5
- `)){let r=s.trim();if(!r||r.startsWith("#"))continue;if(r.startsWith("DOTENV_PUBLIC_KEY"))continue;let i=r.match(/^([^=]+)=(.*)$/);if(!i||i[1]===void 0)continue;let o=i[1].trim();if(oe(o,n))e.push(o)}return e}function ce(t,n,e,s){let r=["#/-------------------[DOTENV_PUBLIC_KEY]--------------------/","#/ versioned X25519 encryption for .env files /","#/ [how it works](https://stacksjs.com/encryption) /","#/----------------------------------------------------------/",`${e}="${n}"`,""],i=!1;for(let o of t.split(`
6
- `)){let a=o.trim();if(a.startsWith("#/"))continue;if(a.startsWith("DOTENV_PUBLIC_KEY"))continue;if(!a&&!i)continue;if(!a||a.startsWith("#")){i=!0,r.push(o);continue}i=!0;let u=a.match(/^([^=]+)=(.*)$/);if(!u||u[1]===void 0||u[2]===void 0){r.push(o);continue}let f=u[1].trim(),c=u[2].trim();if(c.startsWith('"')&&c.endsWith('"')||c.startsWith("'")&&c.endsWith("'"))c=c.slice(1,-1);if(oe(f,s)&&!c.startsWith("encrypted:"))c=W(c,n);r.push(`${f}="${c}"`)}return r.join(`
7
- `)}function D(t){let e=((t||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase();return{publicKeyName:e?`DOTENV_PUBLIC_KEY_${e}`:"DOTENV_PUBLIC_KEY",privateKeyName:e?`DOTENV_PRIVATE_KEY_${e}`:"DOTENV_PRIVATE_KEY"}}function ae(t){let{publicKeyName:n}=D(t);return n==="DOTENV_PUBLIC_KEY"?[n]:[n,"DOTENV_PUBLIC_KEY"]}function L(t){let{privateKeyName:n}=D(t);return n==="DOTENV_PRIVATE_KEY"?[n]:[n,"DOTENV_PRIVATE_KEY"]}function ie(t){for(let n of t.split(`
8
- `)){let e=n.trim();if(!e||e.startsWith("#"))continue;let s=e.match(/^([^=]+)=(.*)$/);if(!s?.[1]||s[2]===void 0)continue;let r=s[2].trim().replace(/^["']|["']$/g,"");if(r.startsWith("encrypted:")||r.startsWith("enc:"))return!0}return!1}function De(t,n){let e=ae(n);for(let s of t.split(`
9
- `)){let r=s.trim();if(!r||r.startsWith("#"))continue;let i=r.match(/^([^=]+)=["']?([^"'\n]+)["']?/);if(!i?.[1]||!i[2])continue;let o=i[1].trim();if(e.includes(o))return{name:o,key:i[2]}}return}function Fe(t,n){let e,s=!1;for(let r of t.split(`
10
- `)){let i=r.trim();if(!i||i.startsWith("#"))continue;let o=i.match(/^([^=]+)=(.*)$/);if(!o?.[1]||o[2]===void 0)continue;let a=o[1].trim(),u=o[2].trim().replace(/^["']|["']$/g,"");if(a===n)e=u;else if(u.startsWith("encrypted:")||u.startsWith("enc:"))s=!0}return e&&s?e:void 0}function tt(t={}){let n=t.cwd||process.cwd(),e=E(n,t.file||".env"),s=E(n,t.keysFile||".env.keys");if(!K(e))return{success:!1,error:`File not found: ${e}`};try{let r,i,o=Fe(k(e,"utf-8"),D(t.file).publicKeyName);if(o)r=o;else if(K(s)){let f=k(s,"utf-8"),{parsed:c}=T(f),{publicKeyName:l,privateKeyName:p}=D(t.file);if(r=c[l]||"",i=c[p]||"",!r||!i){let g=F();r=g.publicKey,i=g.privateKey;let d=`
11
- ${l}="${r}"
2
+ import{existsSync as K,readFileSync as k,renameSync as re,rmSync as se,writeFileSync as b}from"fs";import{basename as ie,dirname as j,resolve as E}from"path";import{createCipheriv as X,createDecipheriv as H,createHash as I,createPrivateKey as le,createPublicKey as G,diffieHellman as J,generateKeyPairSync as M,hkdfSync as z,randomBytes as S}from"crypto";var Y="x25519-public:",A="x25519-private:",W="encrypted:v2:",U="encrypted:",q=Buffer.from("stacks-env:v2","utf8"),Z="Environment decryption failed: authentication or format error";function Q(t){if(t.length===32)return t;return I("sha256").update(t).digest()}function Se(t,n){let e=S(16),s=X("aes-256-gcm",Q(n),e);return{ciphertext:Buffer.concat([s.update(t,"utf8"),s.final()]).toString("hex"),iv:e.toString("hex"),authTag:s.getAuthTag().toString("hex")}}function pe(t,n,e,s){try{let r=H("aes-256-gcm",Q(n),Buffer.from(e,"hex"));return r.setAuthTag(Buffer.from(s,"hex")),Buffer.concat([r.update(Buffer.from(t,"hex")),r.final()]).toString("utf8")}catch{throw Error("Decryption failed: authentication or format error")}}function _(t){return t.toString("base64url")}function V(t,n,e){if(typeof t!=="string"||t.length>0&&!/^[A-Za-z0-9_-]+$/.test(t))throw Error(`${n} is not canonical base64url`);let s=Buffer.from(t,"base64url");if(_(s)!==t)throw Error(`${n} is not canonical base64url`);if(e!==void 0&&s.length!==e)throw Error(`${n} has an invalid length`);return s}function de(t){if(!t.startsWith(Y))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let n=G({key:V(t.slice(Y.length),"public key"),format:"der",type:"spki"});if(n.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return n}function ye(t){if(!t.startsWith(A))throw Error("Private key is not a version 2 X25519 key");let n=le({key:V(t.slice(A.length),"private key"),format:"der",type:"pkcs8"});if(n.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return n}function ee(t){return Buffer.from(`stacks-env:v2;${t.epk};${t.salt};${t.nonce}`,"utf8")}function R(){let{publicKey:t,privateKey:n}=M("x25519"),e=t.export({format:"der",type:"spki"}),s=n.export({format:"der",type:"pkcs8"});return{publicKey:`${Y}${_(e)}`,privateKey:`${A}${_(s)}`}}function C(t,n){let e=de(n),s=M("x25519"),r=_(s.publicKey.export({format:"der",type:"spki"})),i=S(16),o=S(12),u=J({privateKey:s.privateKey,publicKey:e}),a=Buffer.from(z("sha256",u,i,q,32)),c={epk:r,salt:_(i),nonce:_(o)};try{let l=X("aes-256-gcm",a,o);l.setAAD(ee(c));let f=Buffer.concat([l.update(t,"utf8"),l.final()]),p={v:2,...c,ciphertext:_(f),tag:_(l.getAuthTag())};return`${W}${_(Buffer.from(JSON.stringify(p),"utf8"))}`}finally{u.fill(0),a.fill(0)}}function ge(t){let n=V(t.slice(W.length),"envelope").toString("utf8"),e=JSON.parse(n);if(!e||typeof e!=="object"||Array.isArray(e))throw Error("envelope is not an object");let s=Object.keys(e).sort(),r=["ciphertext","epk","nonce","salt","tag","v"];if(s.length!==r.length||s.some((i,o)=>i!==r[o]))throw Error("envelope fields are invalid");if(e.v!==2)throw Error("envelope version is unsupported");for(let i of["epk","salt","nonce","ciphertext","tag"])if(typeof e[i]!=="string")throw Error(`${i} is invalid`);return e}function he(t,n){try{let e=ge(t),s=ye(n),r=G({key:V(e.epk,"ephemeral public key"),format:"der",type:"spki"});if(r.asymmetricKeyType!=="x25519")throw Error("ephemeral key is not X25519");let i=V(e.salt,"salt",16),o=V(e.nonce,"nonce",12),u=V(e.ciphertext,"ciphertext"),a=V(e.tag,"tag",16),c=J({privateKey:s,publicKey:r}),l=Buffer.from(z("sha256",c,i,q,32));try{let f=H("aes-256-gcm",l,o);return f.setAAD(ee(e)),f.setAuthTag(a),Buffer.concat([f.update(u),f.final()]).toString("utf8")}finally{c.fill(0),l.fill(0)}}catch{throw Error(Z)}}function me(t,n){try{if(!/^[0-9a-f]{64}$/.test(n))throw Error("invalid legacy private key");let e=Buffer.from(t.slice(U.length),"base64");if(e.length<32)throw Error("invalid legacy payload");let s=I("sha256").update(Buffer.from(n,"hex")).digest(),r=I("sha256").update(s).digest();return pe(e.subarray(32).toString("hex"),r,e.subarray(0,16).toString("hex"),e.subarray(16,32).toString("hex"))}catch{throw Error(Z)}}function Ye(t){return t.startsWith(U)&&!t.startsWith(W)}function F(t,n){if(!t.startsWith(U))return t;return t.startsWith(W)?he(t,n):me(t,n)}function Ae(t,n,e){return C(F(t,n),e)}function Ue(t){let n=t.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return n&&n[1]?n[1].toLowerCase():""}function Le(t=""){return t?process.env[`DOTENV_PRIVATE_KEY_${t.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}import{spawnSync as Ee}from"child_process";import{readFileSync as $e}from"fs";import{arch as ke,hostname as Ke,platform as ne,release as we,userInfo as be}from"os";import{basename as Ne,dirname as xe,resolve as _e}from"path";function ve(t){return typeof t==="string"&&(t.startsWith("encrypted:")||t.startsWith("enc:"))}function te(t,n=Object.keys(t)){let e=n.map((s)=>t[s]).filter((s)=>ve(s));return Object.fromEntries(Object.entries(t).filter(([,s])=>s===void 0||!e.some((r)=>s.includes(r))))}function O(t,n={}){let e={},s=[],r=[],i=t.split(`
3
+ `),o=te(n.processEnv||process.env);for(let u of i){let a=u.trim();if(!a||a.startsWith("#"))continue;if(a.startsWith("DOTENV_PUBLIC_KEY=")){let p=a.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(p&&p[1]!==void 0)e.DOTENV_PUBLIC_KEY=p[1];continue}let c=a.match(/^([^=]+)=(.*)$/);if(!c||c[1]===void 0||c[2]===void 0)continue;let l=c[1].trim(),f=c[2].trim();if(f.startsWith('"')&&f.endsWith('"')||f.startsWith("'")&&f.endsWith("'")){if(f=f.slice(1,-1),f.includes("\\n"))f=f.replace(/\\n/g,`
4
+ `)}if(f.startsWith("encrypted:")||f.startsWith("enc:")){if(!n.privateKey){r.push(l);continue}try{let p=f.startsWith("enc:")?`encrypted:${f.slice(4)}`:f;f=F(p,n.privateKey)}catch(p){s.push(`Failed to decrypt ${l}: ${p instanceof Error?p.message:"Unknown error"}`),r.push(l);continue}}f=Pe(f,{...o,...e}),f=De(f),e[l]=f}return{parsed:e,errors:s,skippedEncrypted:r}}function Pe(t,n){return t.replace(/\$\{([^}]+)\}/g,(e,s)=>{let r=s.match(/^([^:\-+]+)(:-|-)(.+)$/);if(r){let[,o,u,a]=r,c=n[o];if(u===":-")return c||a;else return c!==void 0?c:a}let i=s.match(/^([^:\-+]+)(:?\+)(.+)$/);if(i){let[,o,u,a]=i,c=n[o];if(u===":+")return c?a:"";else return c!==void 0?a:""}return n[s]||""})}var Ve=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function Oe(t){let n=[],e="",s=null,r=!1;for(let i of t.trim()){if(r){e+=i,r=!1;continue}if(i==="\\"&&s!=="'"){r=!0;continue}if(s){if(i===s)s=null;else e+=i;continue}if(i==='"'||i==="'"){s=i;continue}if(/\s/.test(i)){if(e)n.push(e),e="";continue}e+=i}if(r||s)throw Error("Command substitution contains an unterminated escape or quote");if(e)n.push(e);return n}function Te(t){let[n,...e]=t,s=e.some((r)=>r.startsWith("-"));if(n==="echo"&&!s)return e.join(" ");if(n==="printf"&&e.length===1&&!e[0]?.includes("%"))return e[0];if(n==="hostname"&&e.length===0)return Ke();if(n==="whoami"&&e.length===0)return be().username;if(n==="pwd"&&e.length===0)return process.cwd();if(n==="basename"&&e.length===1)return Ne(e[0]??"");if(n==="dirname"&&e.length===1)return xe(e[0]??"");if(n==="cat"&&e.length>0&&!s)return e.map((r)=>$e(_e(process.cwd(),r),"utf8")).join("");if(n==="uname"){let r=e[0]??"-s";if(e.length<=1&&r==="-m")return ke();if(e.length<=1&&r==="-r")return we();if(e.length<=1&&r==="-s")return{aix:"AIX",android:"Android",cygwin:"CYGWIN",darwin:"Darwin",freebsd:"FreeBSD",haiku:"Haiku",linux:"Linux",netbsd:"NetBSD",openbsd:"OpenBSD",sunos:"SunOS",win32:"Windows_NT"}[ne()]??ne()}return}function De(t){return t.replace(/\$\(([^)]+)\)/g,(n,e)=>{try{let s=Oe(e),r=s[0];if(!r||!Ve.has(r))return console.warn(`[env] Blocked command substitution for disallowed command: ${r}`),"";let i=Te(s);if(i!==void 0)return i.trim();let o=Ee(r,s.slice(1),{encoding:"utf8",env:process.env});if(o.status===0)return o.stdout.trim();let u=o.stderr.trim();console.warn(`[env] Command substitution failed (exit code ${o.status??"unknown"}): ${r}${u?`: ${u}`:""}`)}catch(s){console.warn(`[env] Command substitution error: ${s instanceof Error?s.message:String(s)}`)}return""})}async function Ze(t,n={}){let e={},s=[],r=[];for(let i of t)try{let o=Bun.file(i);if(!o.size)continue;let u=await o.text(),{parsed:a,errors:c,skippedEncrypted:l}=O(u,n);s.push(...c),r.push(...l);for(let[f,p]of Object.entries(a)){if(!n.overload&&e[f]!==void 0)continue;e[f]=p}}catch(o){if(o?.code==="ENOENT")continue;s.push(`Failed to read ${i}: ${o instanceof Error?o.message:String(o)}`)}return{parsed:e,errors:s,skippedEncrypted:r}}function st(t,n){if(t)return t;let e=String(n??"").trim();if(!e||e==="development"||e==="dev"||e==="local")return"";if(!/^[\w.-]+$/.test(e))return"";return`.env.${e}`}function ce(t,n){return(!n.key||t.includes(n.key))&&(!n.excludeKey||!t.includes(n.excludeKey))}function Re(t,n){let e=[];for(let s of t.split(`
5
+ `)){let r=s.trim();if(!r||r.startsWith("#"))continue;if(r.startsWith("DOTENV_PUBLIC_KEY"))continue;let i=r.match(/^([^=]+)=(.*)$/);if(!i||i[1]===void 0)continue;let o=i[1].trim();if(ce(o,n))e.push(o)}return e}function ae(t,n,e,s){let r=["#/-------------------[DOTENV_PUBLIC_KEY]--------------------/","#/ versioned X25519 encryption for .env files /","#/ [how it works](https://stacksjs.com/encryption) /","#/----------------------------------------------------------/",`${e}="${n}"`,""],i=!1;for(let o of t.split(`
6
+ `)){let u=o.trim();if(u.startsWith("#/"))continue;if(u.startsWith("DOTENV_PUBLIC_KEY"))continue;if(!u&&!i)continue;if(!u||u.startsWith("#")){i=!0,r.push(o);continue}i=!0;let a=u.match(/^([^=]+)=(.*)$/);if(!a||a[1]===void 0||a[2]===void 0){r.push(o);continue}let c=a[1].trim(),l=a[2].trim();if(l.startsWith('"')&&l.endsWith('"')||l.startsWith("'")&&l.endsWith("'"))l=l.slice(1,-1);if(ce(c,s)&&!l.startsWith("encrypted:"))l=C(l,n);r.push(`${c}="${l}"`)}return r.join(`
7
+ `)}function D(t){let e=((t||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase();return{publicKeyName:e?`DOTENV_PUBLIC_KEY_${e}`:"DOTENV_PUBLIC_KEY",privateKeyName:e?`DOTENV_PRIVATE_KEY_${e}`:"DOTENV_PRIVATE_KEY"}}function ue(t){let{publicKeyName:n}=D(t);return n==="DOTENV_PUBLIC_KEY"?[n]:[n,"DOTENV_PUBLIC_KEY"]}function L(t){let{privateKeyName:n}=D(t);return n==="DOTENV_PRIVATE_KEY"?[n]:[n,"DOTENV_PRIVATE_KEY"]}function oe(t){for(let n of t.split(`
8
+ `)){let e=n.trim();if(!e||e.startsWith("#"))continue;let s=e.match(/^([^=]+)=(.*)$/);if(!s?.[1]||s[2]===void 0)continue;let r=s[2].trim().replace(/^["']|["']$/g,"");if(r.startsWith("encrypted:")||r.startsWith("enc:"))return!0}return!1}function Fe(t,n){let e=ue(n);for(let s of t.split(`
9
+ `)){let r=s.trim();if(!r||r.startsWith("#"))continue;let i=r.match(/^([^=]+)=["']?([^"'\n]+)["']?/);if(!i?.[1]||!i[2])continue;let o=i[1].trim();if(e.includes(o))return{name:o,key:i[2]}}return}function We(t,n){let e,s=!1;for(let r of t.split(`
10
+ `)){let i=r.trim();if(!i||i.startsWith("#"))continue;let o=i.match(/^([^=]+)=(.*)$/);if(!o?.[1]||o[2]===void 0)continue;let u=o[1].trim(),a=o[2].trim().replace(/^["']|["']$/g,"");if(u===n)e=a;else if(a.startsWith("encrypted:")||a.startsWith("enc:"))s=!0}return e&&s?e:void 0}function it(t={}){let n=t.cwd||process.cwd(),e=E(n,t.file||".env"),s=E(n,t.keysFile||".env.keys");if(!K(e))return{success:!1,error:`File not found: ${e}`};try{let r,i,o=We(k(e,"utf-8"),D(t.file).publicKeyName);if(o)r=o;else if(K(s)){let c=k(s,"utf-8"),{parsed:l}=O(c),{publicKeyName:f,privateKeyName:p}=D(t.file);if(r=l[f]||"",i=l[p]||"",!r||!i){let g=R();r=g.publicKey,i=g.privateKey;let d=`
11
+ ${f}="${r}"
12
12
  ${p}="${i}"
13
- `;b(s,f+d,"utf-8")}}else{let f=F();r=f.publicKey,i=f.privateKey;let{publicKeyName:c,privateKeyName:l}=D(t.file),p=`# .env.keys - Keep this file secure and never commit to source control
14
- ${c}="${r}"
15
- ${l}="${i}"
16
- `;b(s,p,"utf-8")}let a=k(e,"utf-8"),u=ce(a,r,D(t.file).publicKeyName,t);if(t.stdout)return{success:!0,output:u};return b(e,u,"utf-8"),{success:!0,output:`\u2714 encrypted (${t.file||".env"})
17
- \u2714 key added to ${t.keysFile||".env.keys"}`}}catch(r){return{success:!1,error:`Failed to encrypt: ${r instanceof Error?r.message:"Unknown error"}`}}}function Re(t={}){let n=t.cwd||process.cwd(),e=E(n,t.file||".env"),s=E(n,t.keysFile||".env.keys");if(!K(e))return{success:!1,error:`File not found: ${e}`};if(!K(s))return{success:!1,error:`Keys file not found: ${s}`};try{let r=k(s,"utf-8"),{parsed:i}=T(r),u=((t.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),f=u?`DOTENV_PRIVATE_KEY_${u}`:"DOTENV_PRIVATE_KEY",c=i[f];if(!c)return{success:!1,error:`Private key not found: ${f}`};let l=[c];if(f!=="DOTENV_PRIVATE_KEY"&&i.DOTENV_PRIVATE_KEY&&i.DOTENV_PRIVATE_KEY!==c)l.push(i.DOTENV_PRIVATE_KEY);let g=k(e,"utf-8").split(`
18
- `),d=[];for(let N of g){let h=N.trim();if(!h||h.startsWith("#")){d.push(N);continue}if(h.startsWith("DOTENV_PUBLIC_KEY"))continue;let O=h.match(/^([^=]+)=(.*)$/);if(!O||O[1]===void 0||O[2]===void 0){d.push(N);continue}let v=O[1].trim(),y=O[2].trim();if(y.startsWith('"')&&y.endsWith('"')||y.startsWith("'")&&y.endsWith("'"))y=y.slice(1,-1);let _=y.startsWith("encrypted:");if(t.key&&!v.includes(t.key))_=!1;if(_){let w,P;for(let B of l)try{w=R(y,B);break}catch(ue){P=ue}if(w===void 0)throw P instanceof Error?P:Error(`Unable to decrypt ${v}`);y=w}d.push(`${v}="${y}"`)}let m=d.join(`
19
- `);if(t.stdout)return{success:!0,output:m};return b(e,m,"utf-8"),{success:!0,output:`\u2714 decrypted (${t.file||".env"})`}}catch(r){return{success:!1,error:`Failed to decrypt: ${r instanceof Error?r.message:"Unknown error"}`}}}function nt(t,n,e={}){let s=e.cwd||process.cwd(),r=E(s,e.file||".env"),i=e.keysFile?E(s,e.keysFile):E(j(r),".env.keys");try{let o="";if(K(r))o=k(r,"utf-8");let a=o.split(`
20
- `),u=!1,f,p=((e.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),g=p?`DOTENV_PUBLIC_KEY_${p}`:"DOTENV_PUBLIC_KEY",d=p?`DOTENV_PRIVATE_KEY_${p}`:"DOTENV_PRIVATE_KEY",m=De(o,e.file),N=m?.name??g;if(f=m?.key,f){let v=!1;if(K(i)){let _=k(i,"utf-8"),{parsed:w}=T(_);v=L(e.file).some((P)=>Boolean(w[P]))}let y=L(e.file).some((_)=>Boolean(process.env[_]));if(!v&&!y&&!ie(o))f=void 0}if(!e.plain&&!f&&ie(o))return{success:!1,error:`${e.file||".env"} already contains encrypted values, but no key that can be used to add another was found.
13
+ `;b(s,c+d,"utf-8")}}else{let c=R();r=c.publicKey,i=c.privateKey;let{publicKeyName:l,privateKeyName:f}=D(t.file),p=`# .env.keys - Keep this file secure and never commit to source control
14
+ ${l}="${r}"
15
+ ${f}="${i}"
16
+ `;b(s,p,"utf-8")}let u=k(e,"utf-8"),a=ae(u,r,D(t.file).publicKeyName,t);if(t.stdout)return{success:!0,output:a};return b(e,a,"utf-8"),{success:!0,output:`\u2714 encrypted (${t.file||".env"})
17
+ \u2714 key added to ${t.keysFile||".env.keys"}`}}catch(r){return{success:!1,error:`Failed to encrypt: ${r instanceof Error?r.message:"Unknown error"}`}}}function Ce(t={}){let n=t.cwd||process.cwd(),e=E(n,t.file||".env"),s=E(n,t.keysFile||".env.keys");if(!K(e))return{success:!1,error:`File not found: ${e}`};if(!K(s))return{success:!1,error:`Keys file not found: ${s}`};try{let r=k(s,"utf-8"),{parsed:i}=O(r),a=((t.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),c=a?`DOTENV_PRIVATE_KEY_${a}`:"DOTENV_PRIVATE_KEY",l=i[c];if(!l)return{success:!1,error:`Private key not found: ${c}`};let f=[l];if(c!=="DOTENV_PRIVATE_KEY"&&i.DOTENV_PRIVATE_KEY&&i.DOTENV_PRIVATE_KEY!==l)f.push(i.DOTENV_PRIVATE_KEY);let g=k(e,"utf-8").split(`
18
+ `),d=[];for(let N of g){let h=N.trim();if(!h||h.startsWith("#")){d.push(N);continue}if(h.startsWith("DOTENV_PUBLIC_KEY"))continue;let T=h.match(/^([^=]+)=(.*)$/);if(!T||T[1]===void 0||T[2]===void 0){d.push(N);continue}let v=T[1].trim(),y=T[2].trim();if(y.startsWith('"')&&y.endsWith('"')||y.startsWith("'")&&y.endsWith("'"))y=y.slice(1,-1);let x=y.startsWith("encrypted:");if(t.key&&!v.includes(t.key))x=!1;if(x){let w,P;for(let B of f)try{w=F(y,B);break}catch(fe){P=fe}if(w===void 0)throw P instanceof Error?P:Error(`Unable to decrypt ${v}`);y=w}d.push(`${v}="${y}"`)}let m=d.join(`
19
+ `);if(t.stdout)return{success:!0,output:m};return b(e,m,"utf-8"),{success:!0,output:`\u2714 decrypted (${t.file||".env"})`}}catch(r){return{success:!1,error:`Failed to decrypt: ${r instanceof Error?r.message:"Unknown error"}`}}}function ot(t,n,e={}){let s=e.cwd||process.cwd(),r=E(s,e.file||".env"),i=e.keysFile?E(s,e.keysFile):E(j(r),".env.keys");try{let o="";if(K(r))o=k(r,"utf-8");let u=o.split(`
20
+ `),a=!1,c,p=((e.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),g=p?`DOTENV_PUBLIC_KEY_${p}`:"DOTENV_PUBLIC_KEY",d=p?`DOTENV_PRIVATE_KEY_${p}`:"DOTENV_PRIVATE_KEY",m=Fe(o,e.file),N=m?.name??g;if(c=m?.key,c){let v=!1;if(K(i)){let x=k(i,"utf-8"),{parsed:w}=O(x);v=L(e.file).some((P)=>Boolean(w[P]))}let y=L(e.file).some((x)=>Boolean(process.env[x]));if(!v&&!y&&!oe(o))c=void 0}if(!e.plain&&!c&&oe(o))return{success:!1,error:`${e.file||".env"} already contains encrypted values, but no key that can be used to add another was found.
21
21
  Generating one would leave every existing encrypted value unreadable.
22
- Provide the private key as ${L(e.file)[0]} in the environment, restore ${i}, or use --plain to write this value unencrypted.`};if(!e.plain&&!f){let v=F();f=v.publicKey;let y="";if(K(i))y=k(i,"utf-8");y+=`
22
+ Provide the private key as ${L(e.file)[0]} in the environment, restore ${i}, or use --plain to write this value unencrypted.`};if(!e.plain&&!c){let v=R();c=v.publicKey;let y="";if(K(i))y=k(i,"utf-8");y+=`
23
23
  ${g}="${v.publicKey}"
24
24
  ${d}="${v.privateKey}"
25
- `,b(i,y,"utf-8");let _=ae(e.file);for(let w=a.length-1;w>=0;w--){let P=a[w];if(P!==void 0&&_.some((B)=>P.trim().startsWith(`${B}=`)))a.splice(w,1)}a.unshift(`${N}="${f}"`)}let h=n;if(!e.plain&&f)h=W(n,f);for(let v=0;v<a.length;v++){let y=a[v];if(y===void 0)continue;if(y.trim().startsWith(`${t}=`)){a[v]=`${t}="${h}"`,u=!0;break}}if(!u)a.push(`${t}="${h}"`);let O=a.join(`
26
- `);return b(r,O,"utf-8"),{success:!0,output:`set ${t}${e.plain?"":" with encryption"} (${e.file||".env"})`}}catch(o){return{success:!1,error:`Failed to set: ${o instanceof Error?o.message:"Unknown error"}`}}}function rt(t,n={}){let e=n.cwd||process.cwd(),s=E(e,n.file||".env");if(!K(s))return{success:!1,error:`File not found: ${s}`};try{let r,i=E(e,n.keysFile||".env.keys"),u=((n.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),f=u?`DOTENV_PRIVATE_KEY_${u}`:"DOTENV_PRIVATE_KEY";if(K(i)){let d=k(i,"utf-8"),{parsed:m}=T(d);r=m[f]}if(!r)r=process.env[f];let c=k(s,"utf-8"),{parsed:l}=T(c,{privateKey:r});if(t&&!n.all){let d=l[t];if(d===void 0)return{success:!1,error:`Key not found: ${t}`};return{success:!0,output:d}}let p=n.all?{...process.env,...l}:l,g;switch(n.format){case"shell":g=Object.entries(p).map(([d,m])=>`${d}=${m}`).join(" ");break;case"eval":g=Object.entries(p).map(([d,m])=>`${d}="${m}"`).join(`
27
- `);break;case"json":default:g=n.prettyPrint?JSON.stringify(p,null,2):JSON.stringify(p);break}return{success:!0,output:g}}catch(r){return{success:!1,error:`Failed to get: ${r instanceof Error?r.message:"Unknown error"}`}}}function st(t,n={}){let e=n.cwd||process.cwd(),s=E(e,n.keysFile||".env.keys");if(!K(s))return{success:!1,error:`Keys file not found: ${s}`};try{let r=k(s,"utf-8"),{parsed:i}=T(r),u=((n.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),f=u?`DOTENV_PUBLIC_KEY_${u}`:"DOTENV_PUBLIC_KEY",c=u?`DOTENV_PRIVATE_KEY_${u}`:"DOTENV_PRIVATE_KEY";if(t){let g=i[t];if(!g)return{success:!1,error:`Key not found: ${t}`};return{success:!0,output:g}}let l={[f]:i[f],[c]:i[c]};return{success:!0,output:n.format==="shell"?`${f}=${l[f]} ${c}=${l[c]}`:JSON.stringify(l)}}catch(r){return{success:!1,error:`Failed to get keypair: ${r instanceof Error?r.message:"Unknown error"}`}}}function Ce(t,n,e,s){let r=t.split(`
28
- `),i=!1,o=!1;for(let a=0;a<r.length;a++){let u=r[a];if(u===void 0)continue;if(u.startsWith(`${e}=`))r[a]=`${e}="${n.publicKey}"`,i=!0;else if(u.startsWith(`${s}=`))r[a]=`${s}="${n.privateKey}"`,o=!0}if(!i)r.push(`${e}="${n.publicKey}"`);if(!o)r.push(`${s}="${n.privateKey}"`);return`${r.join(`
25
+ `,b(i,y,"utf-8");let x=ue(e.file);for(let w=u.length-1;w>=0;w--){let P=u[w];if(P!==void 0&&x.some((B)=>P.trim().startsWith(`${B}=`)))u.splice(w,1)}u.unshift(`${N}="${c}"`)}let h=n;if(!e.plain&&c)h=C(n,c);for(let v=0;v<u.length;v++){let y=u[v];if(y===void 0)continue;if(y.trim().startsWith(`${t}=`)){u[v]=`${t}="${h}"`,a=!0;break}}if(!a)u.push(`${t}="${h}"`);let T=u.join(`
26
+ `);return b(r,T,"utf-8"),{success:!0,output:`set ${t}${e.plain?"":" with encryption"} (${e.file||".env"})`}}catch(o){return{success:!1,error:`Failed to set: ${o instanceof Error?o.message:"Unknown error"}`}}}function ct(t,n={}){let e=n.cwd||process.cwd(),s=E(e,n.file||".env");if(!K(s))return{success:!1,error:`File not found: ${s}`};try{let r,i=E(e,n.keysFile||".env.keys"),a=((n.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),c=a?`DOTENV_PRIVATE_KEY_${a}`:"DOTENV_PRIVATE_KEY";if(K(i)){let d=k(i,"utf-8"),{parsed:m}=O(d);r=m[c]}if(!r)r=process.env[c];let l=k(s,"utf-8"),{parsed:f}=O(l,{privateKey:r});if(t&&!n.all){let d=f[t];if(d===void 0)return{success:!1,error:`Key not found: ${t}`};return{success:!0,output:d}}let p=n.all?{...process.env,...f}:f,g;switch(n.format){case"shell":g=Object.entries(p).map(([d,m])=>`${d}=${m}`).join(" ");break;case"eval":g=Object.entries(p).map(([d,m])=>`${d}="${m}"`).join(`
27
+ `);break;case"json":default:g=n.prettyPrint?JSON.stringify(p,null,2):JSON.stringify(p);break}return{success:!0,output:g}}catch(r){return{success:!1,error:`Failed to get: ${r instanceof Error?r.message:"Unknown error"}`}}}function at(t,n={}){let e=n.cwd||process.cwd(),s=E(e,n.keysFile||".env.keys");if(!K(s))return{success:!1,error:`Keys file not found: ${s}`};try{let r=k(s,"utf-8"),{parsed:i}=O(r),a=((n.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),c=a?`DOTENV_PUBLIC_KEY_${a}`:"DOTENV_PUBLIC_KEY",l=a?`DOTENV_PRIVATE_KEY_${a}`:"DOTENV_PRIVATE_KEY";if(t){let g=i[t];if(!g)return{success:!1,error:`Key not found: ${t}`};return{success:!0,output:g}}let f={[c]:i[c],[l]:i[l]};return{success:!0,output:n.format==="shell"?`${c}=${f[c]} ${l}=${f[l]}`:JSON.stringify(f)}}catch(r){return{success:!1,error:`Failed to get keypair: ${r instanceof Error?r.message:"Unknown error"}`}}}function Be(t,n,e,s){let r=t.split(`
28
+ `),i=!1,o=!1;for(let u=0;u<r.length;u++){let a=r[u];if(a===void 0)continue;if(a.startsWith(`${e}=`))r[u]=`${e}="${n.publicKey}"`,i=!0;else if(a.startsWith(`${s}=`))r[u]=`${s}="${n.privateKey}"`,o=!0}if(!i)r.push(`${e}="${n.publicKey}"`);if(!o)r.push(`${s}="${n.privateKey}"`);return`${r.join(`
29
29
  `).replace(/\n+$/,"")}
30
- `}function it(t={}){let n=Re({file:t.file,keysFile:t.keysFile,stdout:!0,cwd:t.cwd});if(!n.success)return n;if(n.output===void 0)return{success:!1,error:"Rotation produced no decrypted content"};let e=t.cwd||process.cwd(),s=t.file||".env",r=t.keysFile||".env.keys",i=E(e,s),o=E(e,r),{publicKeyName:a,privateKeyName:u}=D(t.file);if(t.dryRun){let h=Oe(n.output,t);return{success:!0,output:["Dry run: nothing was written.",`${s}: ${h.length} value${h.length===1?"":"s"} would be re-encrypted under a new ${a}.`,`${r}: ${a} and ${u} would be replaced.`,`The current ${u} decrypts nothing once that happens, so keep a copy until the rotation is verified.`].join(`
31
- `)}}let f=F(),c=ce(n.output,f.publicKey,a,t);if(t.stdout)return{success:!0,output:c,notice:[`Nothing was written. To adopt the output above, put this keypair in ${r}:`,"",`${a}="${f.publicKey}"`,`${u}="${f.privateKey}"`,"","Without it that output cannot be decrypted. Run without --stdout to have both applied together."].join(`
32
- `)};let l=k(i),p=k(o),g=Ce(p.toString("utf8"),f,a,u),d=`${process.pid}-${Date.now()}`,m=E(j(i),`.${se(i)}.${d}.rotate`),N=E(j(o),`.${se(o)}.${d}.rotate`);try{b(m,c,{encoding:"utf8",mode:384}),b(N,g,{encoding:"utf8",mode:384}),ne(m,i);try{ne(N,o)}catch(h){throw b(i,l),h}return{success:!0,output:`\u2714 rotated (${s}) to encrypted:v2`}}catch(h){if(!K(o))b(o,p,{mode:384});return{success:!1,error:`Failed to rotate without exposing plaintext: ${h instanceof Error?h.message:"Unknown error"}`}}finally{re(m,{force:!0}),re(N,{force:!0})}}export{De as declaredEnvPublicKey,Re as decryptEnv,tt as encryptEnv,ie as envHasCiphertext,D as envKeyNames,L as envPrivateKeyNames,ae as envPublicKeyNames,rt as getEnv,st as getKeypair,et as resolveEnvFile,Fe as reusableEnvPublicKey,it as rotateKeypair,nt as setEnv};
30
+ `}function ut(t={}){let n=Ce({file:t.file,keysFile:t.keysFile,stdout:!0,cwd:t.cwd});if(!n.success)return n;if(n.output===void 0)return{success:!1,error:"Rotation produced no decrypted content"};let e=t.cwd||process.cwd(),s=t.file||".env",r=t.keysFile||".env.keys",i=E(e,s),o=E(e,r),{publicKeyName:u,privateKeyName:a}=D(t.file);if(t.dryRun){let h=Re(n.output,t);return{success:!0,output:["Dry run: nothing was written.",`${s}: ${h.length} value${h.length===1?"":"s"} would be re-encrypted under a new ${u}.`,`${r}: ${u} and ${a} would be replaced.`,`The current ${a} decrypts nothing once that happens, so keep a copy until the rotation is verified.`].join(`
31
+ `)}}let c=R(),l=ae(n.output,c.publicKey,u,t);if(t.stdout)return{success:!0,output:l,notice:[`Nothing was written. To adopt the output above, put this keypair in ${r}:`,"",`${u}="${c.publicKey}"`,`${a}="${c.privateKey}"`,"","Without it that output cannot be decrypted. Run without --stdout to have both applied together."].join(`
32
+ `)};let f=k(i),p=k(o),g=Be(p.toString("utf8"),c,u,a),d=`${process.pid}-${Date.now()}`,m=E(j(i),`.${ie(i)}.${d}.rotate`),N=E(j(o),`.${ie(o)}.${d}.rotate`);try{b(m,l,{encoding:"utf8",mode:384}),b(N,g,{encoding:"utf8",mode:384}),re(m,i);try{re(N,o)}catch(h){throw b(i,f),h}return{success:!0,output:`\u2714 rotated (${s}) to encrypted:v2`}}catch(h){if(!K(o))b(o,p,{mode:384});return{success:!1,error:`Failed to rotate without exposing plaintext: ${h instanceof Error?h.message:"Unknown error"}`}}finally{se(m,{force:!0}),se(N,{force:!0})}}export{Fe as declaredEnvPublicKey,Ce as decryptEnv,it as encryptEnv,oe as envHasCiphertext,D as envKeyNames,L as envPrivateKeyNames,ue as envPublicKeyNames,ct as getEnv,at as getKeypair,st as resolveEnvFile,We as reusableEnvPublicKey,ut as rotateKeypair,ot as setEnv};
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Hosts where plain HTTP and an ungated dashboard are a development reality
3
+ * rather than a misconfiguration.
4
+ */
5
+ export declare function isLoopbackHost(hostname: string): boolean;
6
+ /**
7
+ * Whether this deployment is demonstrably local, decided from what the app
8
+ * IS rather than what its environment is called.
9
+ *
10
+ * The environment name cannot carry this decision on its own: `.env.example`
11
+ * ships `APP_ENV=development`, so an app whose env file descends from it -
12
+ * which is every app that never edited that line - claims to be development
13
+ * on the public internet. `@stacksjs/auth`'s cookie policy hit this exact
14
+ * shape in stacksjs/stacks#2275 and moved to the URL for the same reason.
15
+ *
16
+ * - an explicit deployment name (`production`, `prod`, `staging`) is never
17
+ * local, whatever the URL says
18
+ * - otherwise a configured `APP_URL` decides, by whether its host is
19
+ * loopback. A scheme-less value (`stacks.localhost`, which `buddy dev`
20
+ * writes) is still parsed for its host.
21
+ * - with no URL at all, only the unambiguous names opt out. `development`
22
+ * is deliberately absent: it is the one that ships in a template.
23
+ */
24
+ export declare function isLocalDeployment(): boolean;
package/dist/index.d.ts CHANGED
@@ -18,6 +18,7 @@ export declare function validateEnv(envProxy?: StacksEnv): string[];
18
18
  */
19
19
  export declare function requireEnv(keys: ReadonlyArray<string>, envProxy?: StacksEnv): StacksEnv;
20
20
  export declare const env: StacksEnv;
21
+ export * from './deployment';
21
22
  export * from './types';
22
23
  export * from './crypto';
23
24
  export * from './parser';
package/dist/index.js CHANGED
@@ -1,35 +1,35 @@
1
1
  // @bun
2
- import Rn from"process";import{projectPath as vn}from"@stacksjs/path";import Ce from"fs";import{existsSync as F}from"fs";import{readFileSync as Z}from"fs";import{resolve as Y}from"path";import{createCipheriv as se,createDecipheriv as oe,createHash as X,createPrivateKey as ke,createPublicKey as de,diffieHellman as ce,generateKeyPairSync as ae,hkdfSync as ue,randomBytes as Q}from"crypto";var q="x25519-public:",z="x25519-private:",B="encrypted:v2:",J="encrypted:",fe=Buffer.from("stacks-env:v2","utf8"),le="Environment decryption failed: authentication or format error";function Ee(e){if(e.length===32)return e;return X("sha256").update(e).digest()}function Nn(e,n){let t=Q(16),r=se("aes-256-gcm",Ee(n),t);return{ciphertext:Buffer.concat([r.update(e,"utf8"),r.final()]).toString("hex"),iv:t.toString("hex"),authTag:r.getAuthTag().toString("hex")}}function xe(e,n,t,r){try{let i=oe("aes-256-gcm",Ee(n),Buffer.from(t,"hex"));return i.setAuthTag(Buffer.from(r,"hex")),Buffer.concat([i.update(Buffer.from(e,"hex")),i.final()]).toString("utf8")}catch{throw Error("Decryption failed: authentication or format error")}}function C(e){return e.toString("base64url")}function b(e,n,t){if(typeof e!=="string"||e.length>0&&!/^[A-Za-z0-9_-]+$/.test(e))throw Error(`${n} is not canonical base64url`);let r=Buffer.from(e,"base64url");if(C(r)!==e)throw Error(`${n} is not canonical base64url`);if(t!==void 0&&r.length!==t)throw Error(`${n} has an invalid length`);return r}function Ue(e){if(!e.startsWith(q))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let n=de({key:b(e.slice(q.length),"public key"),format:"der",type:"spki"});if(n.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return n}function $e(e){if(!e.startsWith(z))throw Error("Private key is not a version 2 X25519 key");let n=ke({key:b(e.slice(z.length),"private key"),format:"der",type:"pkcs8"});if(n.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return n}function pe(e){return Buffer.from(`stacks-env:v2;${e.epk};${e.salt};${e.nonce}`,"utf8")}function x(){let{publicKey:e,privateKey:n}=ae("x25519"),t=e.export({format:"der",type:"spki"}),r=n.export({format:"der",type:"pkcs8"});return{publicKey:`${q}${C(t)}`,privateKey:`${z}${C(r)}`}}function V(e,n){let t=Ue(n),r=ae("x25519"),i=C(r.publicKey.export({format:"der",type:"spki"})),s=Q(16),o=Q(12),d=ce({privateKey:r.privateKey,publicKey:t}),c=Buffer.from(ue("sha256",d,s,fe,32)),a={epk:i,salt:C(s),nonce:C(o)};try{let u=se("aes-256-gcm",c,o);u.setAAD(pe(a));let f=Buffer.concat([u.update(e,"utf8"),u.final()]),E={v:2,...a,ciphertext:C(f),tag:C(u.getAuthTag())};return`${B}${C(Buffer.from(JSON.stringify(E),"utf8"))}`}finally{d.fill(0),c.fill(0)}}function we(e){let n=b(e.slice(B.length),"envelope").toString("utf8"),t=JSON.parse(n);if(!t||typeof t!=="object"||Array.isArray(t))throw Error("envelope is not an object");let r=Object.keys(t).sort(),i=["ciphertext","epk","nonce","salt","tag","v"];if(r.length!==i.length||r.some((s,o)=>s!==i[o]))throw Error("envelope fields are invalid");if(t.v!==2)throw Error("envelope version is unsupported");for(let s of["epk","salt","nonce","ciphertext","tag"])if(typeof t[s]!=="string")throw Error(`${s} is invalid`);return t}function Be(e,n){try{let t=we(e),r=$e(n),i=de({key:b(t.epk,"ephemeral public key"),format:"der",type:"spki"});if(i.asymmetricKeyType!=="x25519")throw Error("ephemeral key is not X25519");let s=b(t.salt,"salt",16),o=b(t.nonce,"nonce",12),d=b(t.ciphertext,"ciphertext"),c=b(t.tag,"tag",16),a=ce({privateKey:r,publicKey:i}),u=Buffer.from(ue("sha256",a,s,fe,32));try{let f=oe("aes-256-gcm",u,o);return f.setAAD(pe(t)),f.setAuthTag(c),Buffer.concat([f.update(d),f.final()]).toString("utf8")}finally{a.fill(0),u.fill(0)}}catch{throw Error(le)}}function Ve(e,n){try{if(!/^[0-9a-f]{64}$/.test(n))throw Error("invalid legacy private key");let t=Buffer.from(e.slice(J.length),"base64");if(t.length<32)throw Error("invalid legacy payload");let r=X("sha256").update(Buffer.from(n,"hex")).digest(),i=X("sha256").update(r).digest();return xe(t.subarray(32).toString("hex"),i,t.subarray(0,16).toString("hex"),t.subarray(16,32).toString("hex"))}catch{throw Error(le)}}function hn(e){return e.startsWith(J)&&!e.startsWith(B)}function L(e,n){if(!e.startsWith(J))return e;return e.startsWith(B)?Be(e,n):Ve(e,n)}function Pn(e,n,t){return V(L(e,n),t)}function Dn(e){let n=e.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return n&&n[1]?n[1].toLowerCase():""}function M(e=""){return e?process.env[`DOTENV_PRIVATE_KEY_${e.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}import{spawnSync as Me}from"child_process";import{readFileSync as Fe}from"fs";import{arch as Ye,hostname as We,platform as ge,release as Ge,userInfo as He}from"os";import{basename as je,dirname as Xe,resolve as Qe}from"path";function h(e,n={}){let t={},r=[],i=[],s=e.split(`
3
- `);for(let o of s){let d=o.trim();if(!d||d.startsWith("#"))continue;if(d.startsWith("DOTENV_PUBLIC_KEY=")){let f=d.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(f&&f[1]!==void 0)t.DOTENV_PUBLIC_KEY=f[1];continue}let c=d.match(/^([^=]+)=(.*)$/);if(!c||c[1]===void 0||c[2]===void 0)continue;let a=c[1].trim(),u=c[2].trim();if(u.startsWith('"')&&u.endsWith('"')||u.startsWith("'")&&u.endsWith("'")){if(u=u.slice(1,-1),u.includes("\\n"))u=u.replace(/\\n/g,`
4
- `)}if(u.startsWith("encrypted:")||u.startsWith("enc:")){if(!n.privateKey){i.push(a);continue}try{let f=u.startsWith("enc:")?`encrypted:${u.slice(4)}`:u;u=L(f,n.privateKey)}catch(f){r.push(`Failed to decrypt ${a}: ${f instanceof Error?f.message:"Unknown error"}`)}}u=qe(u,{...n.processEnv||process.env,...t}),u=en(u),t[a]=u}return{parsed:t,errors:r,skippedEncrypted:i}}function qe(e,n){return e.replace(/\$\{([^}]+)\}/g,(t,r)=>{let i=r.match(/^([^:\-+]+)(:-|-)(.+)$/);if(i){let[,o,d,c]=i,a=n[o];if(d===":-")return a||c;else return a!==void 0?a:c}let s=r.match(/^([^:\-+]+)(:?\+)(.+)$/);if(s){let[,o,d,c]=s,a=n[o];if(d===":+")return a?c:"";else return a!==void 0?c:""}return n[r]||""})}var ze=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function Je(e){let n=[],t="",r=null,i=!1;for(let s of e.trim()){if(i){t+=s,i=!1;continue}if(s==="\\"&&r!=="'"){i=!0;continue}if(r){if(s===r)r=null;else t+=s;continue}if(s==='"'||s==="'"){r=s;continue}if(/\s/.test(s)){if(t)n.push(t),t="";continue}t+=s}if(i||r)throw Error("Command substitution contains an unterminated escape or quote");if(t)n.push(t);return n}function Ze(e){let[n,...t]=e,r=t.some((i)=>i.startsWith("-"));if(n==="echo"&&!r)return t.join(" ");if(n==="printf"&&t.length===1&&!t[0]?.includes("%"))return t[0];if(n==="hostname"&&t.length===0)return We();if(n==="whoami"&&t.length===0)return He().username;if(n==="pwd"&&t.length===0)return process.cwd();if(n==="basename"&&t.length===1)return je(t[0]??"");if(n==="dirname"&&t.length===1)return Xe(t[0]??"");if(n==="cat"&&t.length>0&&!r)return t.map((i)=>Fe(Qe(process.cwd(),i),"utf8")).join("");if(n==="uname"){let i=t[0]??"-s";if(t.length<=1&&i==="-m")return Ye();if(t.length<=1&&i==="-r")return Ge();if(t.length<=1&&i==="-s")return{aix:"AIX",android:"Android",cygwin:"CYGWIN",darwin:"Darwin",freebsd:"FreeBSD",haiku:"Haiku",linux:"Linux",netbsd:"NetBSD",openbsd:"OpenBSD",sunos:"SunOS",win32:"Windows_NT"}[ge()]??ge()}return}function en(e){return e.replace(/\$\(([^)]+)\)/g,(n,t)=>{try{let r=Je(t),i=r[0];if(!i||!ze.has(i))return console.warn(`[env] Blocked command substitution for disallowed command: ${i}`),"";let s=Ze(r);if(s!==void 0)return s.trim();let o=Me(i,r.slice(1),{encoding:"utf8",env:process.env});if(o.status===0)return o.stdout.trim();let d=o.stderr.trim();console.warn(`[env] Command substitution failed (exit code ${o.status??"unknown"}): ${i}${d?`: ${d}`:""}`)}catch(r){console.warn(`[env] Command substitution error: ${r instanceof Error?r.message:String(r)}`)}return""})}async function Un(e,n={}){let t={},r=[],i=[];for(let s of e)try{let o=Bun.file(s);if(!o.size)continue;let d=await o.text(),{parsed:c,errors:a,skippedEncrypted:u}=h(d,n);r.push(...a),i.push(...u);for(let[f,E]of Object.entries(c)){if(!n.overload&&t[f]!==void 0)continue;t[f]=E}}catch(o){if(o?.code==="ENOENT")continue;r.push(`Failed to read ${s}: ${o instanceof Error?o.message:String(o)}`)}return{parsed:t,errors:r,skippedEncrypted:i}}function U(e){return typeof e==="string"&&(e.startsWith("encrypted:")||e.startsWith("enc:"))}function K(e){if(!e||U(e))return;let n=e.trim().toLowerCase();if(!/^[a-z0-9_-]+$/.test(n))return;if(n==="prod")return"production";if(n==="stage")return"staging";if(n==="dev")return"development";return n}var W=null,G,H;function nn(e,n,t=".env.keys"){let r=Y(n,t);if(!F(r))return;try{let{parsed:i}=h(Z(r,"utf-8"));if(e){let s=i[`DOTENV_PRIVATE_KEY_${e.toUpperCase()}`];if(s)return s}return i.DOTENV_PRIVATE_KEY}catch{return}}function ee(e={}){return K(e.env)||H||K("development")||K(process.env.DOTENV_ENV)||K(process.env.APP_ENV)||"development"}function tn(e={}){let n=ee(e),t=e.cwd||process.cwd();if(W===n)return G;let r=M(n);if(!r)r=process.env.DOTENV_PRIVATE_KEY;if(!r)r=nn(n,t);return W=n,G=r,r}function Yn(){W=null,G=void 0,H=void 0}function _e(e,n={}){if(!U(e))return e;let t=tn(n);if(!t)return;try{let r=e.startsWith("enc:")?`encrypted:${e.slice(4)}`:e;return L(r,t)}catch{return}}function Te(e={}){let{path:n=[".env"],overload:t=!1,env:r,privateKey:i,keysFile:s=".env.keys",quiet:o=!1,cwd:d=process.cwd()}=e,c=Array.isArray(n)?n:[n],a=[],u=0,f=new Set(Object.entries(process.env).filter(([,g])=>g!==void 0&&!U(g)).map(([g])=>g)),E=i;if(!E&&s){let g=Y(d,s);if(F(g))try{let p=Z(g,"utf-8"),{parsed:T}=h(p);if(r){let v=`DOTENV_PRIVATE_KEY_${r.toUpperCase()}`;E=T[v]||T.DOTENV_PRIVATE_KEY}else E=T.DOTENV_PRIVATE_KEY}catch(p){a.push(`Failed to load keys file: ${p instanceof Error?p.message:"Unknown error"}`)}}if(!E)E=M(r||"")||M();for(let g of c){let p=Y(d,g);if(!F(p)){if(!o)a.push(`File not found: ${p}`);continue}try{let T=Z(p,"utf-8"),{parsed:v,errors:y,skippedEncrypted:O}=h(T,{privateKey:E,processEnv:process.env});if(a.push(...y),O.length>0){for(let I of O)if(U(process.env[I]))delete process.env[I];let _=r?`DOTENV_PRIVATE_KEY_${r.toUpperCase()}`:"DOTENV_PRIVATE_KEY",A=O.slice(0,5).join(", "),R=O.length>5?`, \u2026 +${O.length-5} more`:"";console.warn(`[env] warning: skipped ${O.length} encrypted value(s) in ${g} (${_} not set; defaults apply): ${A}${R}`)}let m=0;for(let[_,A]of Object.entries(v)){if(_==="DOTENV_PUBLIC_KEY")continue;let R=process.env[_],I=U(R)&&A!==R;if(t||!f.has(_)||I)process.env[_]=A,u++,m++}if(!o&&!process.env.__ENV_LOADED__)console.error(`[env] loaded ${m}/${Object.keys(v).length} variables from ${g}`),process.env.__ENV_LOADED__="1"}catch(T){a.push(`Failed to load ${p}: ${T instanceof Error?T.message:"Unknown error"}`)}}return{loaded:u,errors:a}}function rn(e={}){return{name:"env-plugin",setup(n){Te(e)}}}function Wn(e={}){let n=K(e.env)||K("development")||K(process.env.DOTENV_ENV)||K(process.env.APP_ENV)||"development",t=e.cwd||process.cwd();if(H!==n)H=n,W=null,G=void 0;let r=[],i=(d)=>{if(!r.includes(d)&&F(Y(t,d)))r.push(d)};i(".env"),i(".env.local");let s=`.env.${n}`,o=`.env.${n}.local`;return i(s),i(o),Te({...e,path:r,env:n})}var Hn=rn;var ye={APP_ENV:["local","dev","development","staging","prod","production"],DB_CONNECTION:["mysql","sqlite","postgres","singlestore","vitess","dynamodb"],DB_MIGRATE_FRESH:["allow","confirm","disabled"],MAIL_MAILER:["smtp","mailgun","ses","log","sendgrid","mailtrap","capture"],SEARCH_ENGINE_DRIVER:["opensearch","meilisearch","algolia","typesense"],FRONTEND_APP_ENV:["development","staging","production"]};function jn(e){return e}import{existsSync as P,readFileSync as N,renameSync as me,rmSync as Re,writeFileSync as D}from"fs";import{basename as ve,dirname as te,resolve as S}from"path";function Zn(e,n){if(e)return e;let t=String(n??"").trim();if(!t||t==="development"||t==="dev"||t==="local")return"";if(!/^[\w.-]+$/.test(t))return"";return`.env.${t}`}function Ae(e,n){return(!n.key||e.includes(n.key))&&(!n.excludeKey||!e.includes(n.excludeKey))}function sn(e,n){let t=[];for(let r of e.split(`
5
- `)){let i=r.trim();if(!i||i.startsWith("#"))continue;if(i.startsWith("DOTENV_PUBLIC_KEY"))continue;let s=i.match(/^([^=]+)=(.*)$/);if(!s||s[1]===void 0)continue;let o=s[1].trim();if(Ae(o,n))t.push(o)}return t}function Se(e,n,t,r){let i=["#/-------------------[DOTENV_PUBLIC_KEY]--------------------/","#/ versioned X25519 encryption for .env files /","#/ [how it works](https://stacksjs.com/encryption) /","#/----------------------------------------------------------/",`${t}="${n}"`,""],s=!1;for(let o of e.split(`
6
- `)){let d=o.trim();if(d.startsWith("#/"))continue;if(d.startsWith("DOTENV_PUBLIC_KEY"))continue;if(!d&&!s)continue;if(!d||d.startsWith("#")){s=!0,i.push(o);continue}s=!0;let c=d.match(/^([^=]+)=(.*)$/);if(!c||c[1]===void 0||c[2]===void 0){i.push(o);continue}let a=c[1].trim(),u=c[2].trim();if(u.startsWith('"')&&u.endsWith('"')||u.startsWith("'")&&u.endsWith("'"))u=u.slice(1,-1);if(Ae(a,r)&&!u.startsWith("encrypted:"))u=V(u,n);i.push(`${a}="${u}"`)}return i.join(`
7
- `)}function k(e){let t=((e||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase();return{publicKeyName:t?`DOTENV_PUBLIC_KEY_${t}`:"DOTENV_PUBLIC_KEY",privateKeyName:t?`DOTENV_PRIVATE_KEY_${t}`:"DOTENV_PRIVATE_KEY"}}function Ie(e){let{publicKeyName:n}=k(e);return n==="DOTENV_PUBLIC_KEY"?[n]:[n,"DOTENV_PUBLIC_KEY"]}function ne(e){let{privateKeyName:n}=k(e);return n==="DOTENV_PRIVATE_KEY"?[n]:[n,"DOTENV_PRIVATE_KEY"]}function Oe(e){for(let n of e.split(`
8
- `)){let t=n.trim();if(!t||t.startsWith("#"))continue;let r=t.match(/^([^=]+)=(.*)$/);if(!r?.[1]||r[2]===void 0)continue;let i=r[2].trim().replace(/^["']|["']$/g,"");if(i.startsWith("encrypted:")||i.startsWith("enc:"))return!0}return!1}function on(e,n){let t=Ie(n);for(let r of e.split(`
9
- `)){let i=r.trim();if(!i||i.startsWith("#"))continue;let s=i.match(/^([^=]+)=["']?([^"'\n]+)["']?/);if(!s?.[1]||!s[2])continue;let o=s[1].trim();if(t.includes(o))return{name:o,key:s[2]}}return}function dn(e,n){let t,r=!1;for(let i of e.split(`
10
- `)){let s=i.trim();if(!s||s.startsWith("#"))continue;let o=s.match(/^([^=]+)=(.*)$/);if(!o?.[1]||o[2]===void 0)continue;let d=o[1].trim(),c=o[2].trim().replace(/^["']|["']$/g,"");if(d===n)t=c;else if(c.startsWith("encrypted:")||c.startsWith("enc:"))r=!0}return t&&r?t:void 0}function et(e={}){let n=e.cwd||process.cwd(),t=S(n,e.file||".env"),r=S(n,e.keysFile||".env.keys");if(!P(t))return{success:!1,error:`File not found: ${t}`};try{let i,s,o=dn(N(t,"utf-8"),k(e.file).publicKeyName);if(o)i=o;else if(P(r)){let a=N(r,"utf-8"),{parsed:u}=h(a),{publicKeyName:f,privateKeyName:E}=k(e.file);if(i=u[f]||"",s=u[E]||"",!i||!s){let g=x();i=g.publicKey,s=g.privateKey;let p=`
11
- ${f}="${i}"
12
- ${E}="${s}"
13
- `;D(r,a+p,"utf-8")}}else{let a=x();i=a.publicKey,s=a.privateKey;let{publicKeyName:u,privateKeyName:f}=k(e.file),E=`# .env.keys - Keep this file secure and never commit to source control
2
+ import Sn from"process";import{projectPath as In}from"@stacksjs/path";import xe from"fs";import{existsSync as H}from"fs";import{readFileSync as te}from"fs";import{resolve as V}from"path";import{createCipheriv as ae,createDecipheriv as ue,createHash as z,createPrivateKey as $e,createPublicKey as fe,diffieHellman as le,generateKeyPairSync as Ee,hkdfSync as pe,randomBytes as J}from"crypto";var Z="x25519-public:",ee="x25519-private:",Y="encrypted:v2:",ne="encrypted:",ge=Buffer.from("stacks-env:v2","utf8"),_e="Environment decryption failed: authentication or format error";function ye(e){if(e.length===32)return e;return z("sha256").update(e).digest()}function Cn(e,n){let t=J(16),r=ae("aes-256-gcm",ye(n),t);return{ciphertext:Buffer.concat([r.update(e,"utf8"),r.final()]).toString("hex"),iv:t.toString("hex"),authTag:r.getAuthTag().toString("hex")}}function we(e,n,t,r){try{let i=ue("aes-256-gcm",ye(n),Buffer.from(t,"hex"));return i.setAuthTag(Buffer.from(r,"hex")),Buffer.concat([i.update(Buffer.from(e,"hex")),i.final()]).toString("utf8")}catch{throw Error("Decryption failed: authentication or format error")}}function C(e){return e.toString("base64url")}function b(e,n,t){if(typeof e!=="string"||e.length>0&&!/^[A-Za-z0-9_-]+$/.test(e))throw Error(`${n} is not canonical base64url`);let r=Buffer.from(e,"base64url");if(C(r)!==e)throw Error(`${n} is not canonical base64url`);if(t!==void 0&&r.length!==t)throw Error(`${n} has an invalid length`);return r}function Be(e){if(!e.startsWith(Z))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let n=fe({key:b(e.slice(Z.length),"public key"),format:"der",type:"spki"});if(n.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return n}function Ve(e){if(!e.startsWith(ee))throw Error("Private key is not a version 2 X25519 key");let n=$e({key:b(e.slice(ee.length),"private key"),format:"der",type:"pkcs8"});if(n.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return n}function me(e){return Buffer.from(`stacks-env:v2;${e.epk};${e.salt};${e.nonce}`,"utf8")}function U(){let{publicKey:e,privateKey:n}=Ee("x25519"),t=e.export({format:"der",type:"spki"}),r=n.export({format:"der",type:"pkcs8"});return{publicKey:`${Z}${C(t)}`,privateKey:`${ee}${C(r)}`}}function W(e,n){let t=Be(n),r=Ee("x25519"),i=C(r.publicKey.export({format:"der",type:"spki"})),s=J(16),o=J(12),d=le({privateKey:r.privateKey,publicKey:t}),a=Buffer.from(pe("sha256",d,s,ge,32)),c={epk:i,salt:C(s),nonce:C(o)};try{let f=ae("aes-256-gcm",a,o);f.setAAD(me(c));let u=Buffer.concat([f.update(e,"utf8"),f.final()]),l={v:2,...c,ciphertext:C(u),tag:C(f.getAuthTag())};return`${Y}${C(Buffer.from(JSON.stringify(l),"utf8"))}`}finally{d.fill(0),a.fill(0)}}function Me(e){let n=b(e.slice(Y.length),"envelope").toString("utf8"),t=JSON.parse(n);if(!t||typeof t!=="object"||Array.isArray(t))throw Error("envelope is not an object");let r=Object.keys(t).sort(),i=["ciphertext","epk","nonce","salt","tag","v"];if(r.length!==i.length||r.some((s,o)=>s!==i[o]))throw Error("envelope fields are invalid");if(t.v!==2)throw Error("envelope version is unsupported");for(let s of["epk","salt","nonce","ciphertext","tag"])if(typeof t[s]!=="string")throw Error(`${s} is invalid`);return t}function Fe(e,n){try{let t=Me(e),r=Ve(n),i=fe({key:b(t.epk,"ephemeral public key"),format:"der",type:"spki"});if(i.asymmetricKeyType!=="x25519")throw Error("ephemeral key is not X25519");let s=b(t.salt,"salt",16),o=b(t.nonce,"nonce",12),d=b(t.ciphertext,"ciphertext"),a=b(t.tag,"tag",16),c=le({privateKey:r,publicKey:i}),f=Buffer.from(pe("sha256",c,s,ge,32));try{let u=ue("aes-256-gcm",f,o);return u.setAAD(me(t)),u.setAuthTag(a),Buffer.concat([u.update(d),u.final()]).toString("utf8")}finally{c.fill(0),f.fill(0)}}catch{throw Error(_e)}}function Ye(e,n){try{if(!/^[0-9a-f]{64}$/.test(n))throw Error("invalid legacy private key");let t=Buffer.from(e.slice(ne.length),"base64");if(t.length<32)throw Error("invalid legacy payload");let r=z("sha256").update(Buffer.from(n,"hex")).digest(),i=z("sha256").update(r).digest();return we(t.subarray(32).toString("hex"),i,t.subarray(0,16).toString("hex"),t.subarray(16,32).toString("hex"))}catch{throw Error(_e)}}function bn(e){return e.startsWith(ne)&&!e.startsWith(Y)}function K(e,n){if(!e.startsWith(ne))return e;return e.startsWith(Y)?Fe(e,n):Ye(e,n)}function Ln(e,n,t){return W(K(e,n),t)}function Kn(e){let n=e.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return n&&n[1]?n[1].toLowerCase():""}function G(e=""){return e?process.env[`DOTENV_PRIVATE_KEY_${e.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}import{spawnSync as We}from"child_process";import{readFileSync as Ge}from"fs";import{arch as He,hostname as je,platform as Te,release as Xe,userInfo as Qe}from"os";import{basename as qe,dirname as ze,resolve as Je}from"path";function w(e){return typeof e==="string"&&(e.startsWith("encrypted:")||e.startsWith("enc:"))}function B(e,n=Object.keys(e)){let t=n.map((r)=>e[r]).filter((r)=>w(r));return Object.fromEntries(Object.entries(e).filter(([,r])=>r===void 0||!t.some((i)=>r.includes(i))))}function h(e,n={}){let t={},r=[],i=[],s=e.split(`
3
+ `),o=B(n.processEnv||process.env);for(let d of s){let a=d.trim();if(!a||a.startsWith("#"))continue;if(a.startsWith("DOTENV_PUBLIC_KEY=")){let l=a.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(l&&l[1]!==void 0)t.DOTENV_PUBLIC_KEY=l[1];continue}let c=a.match(/^([^=]+)=(.*)$/);if(!c||c[1]===void 0||c[2]===void 0)continue;let f=c[1].trim(),u=c[2].trim();if(u.startsWith('"')&&u.endsWith('"')||u.startsWith("'")&&u.endsWith("'")){if(u=u.slice(1,-1),u.includes("\\n"))u=u.replace(/\\n/g,`
4
+ `)}if(u.startsWith("encrypted:")||u.startsWith("enc:")){if(!n.privateKey){i.push(f);continue}try{let l=u.startsWith("enc:")?`encrypted:${u.slice(4)}`:u;u=K(l,n.privateKey)}catch(l){r.push(`Failed to decrypt ${f}: ${l instanceof Error?l.message:"Unknown error"}`),i.push(f);continue}}u=Ze(u,{...o,...t}),u=rn(u),t[f]=u}return{parsed:t,errors:r,skippedEncrypted:i}}function Ze(e,n){return e.replace(/\$\{([^}]+)\}/g,(t,r)=>{let i=r.match(/^([^:\-+]+)(:-|-)(.+)$/);if(i){let[,o,d,a]=i,c=n[o];if(d===":-")return c||a;else return c!==void 0?c:a}let s=r.match(/^([^:\-+]+)(:?\+)(.+)$/);if(s){let[,o,d,a]=s,c=n[o];if(d===":+")return c?a:"";else return c!==void 0?a:""}return n[r]||""})}var en=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function nn(e){let n=[],t="",r=null,i=!1;for(let s of e.trim()){if(i){t+=s,i=!1;continue}if(s==="\\"&&r!=="'"){i=!0;continue}if(r){if(s===r)r=null;else t+=s;continue}if(s==='"'||s==="'"){r=s;continue}if(/\s/.test(s)){if(t)n.push(t),t="";continue}t+=s}if(i||r)throw Error("Command substitution contains an unterminated escape or quote");if(t)n.push(t);return n}function tn(e){let[n,...t]=e,r=t.some((i)=>i.startsWith("-"));if(n==="echo"&&!r)return t.join(" ");if(n==="printf"&&t.length===1&&!t[0]?.includes("%"))return t[0];if(n==="hostname"&&t.length===0)return je();if(n==="whoami"&&t.length===0)return Qe().username;if(n==="pwd"&&t.length===0)return process.cwd();if(n==="basename"&&t.length===1)return qe(t[0]??"");if(n==="dirname"&&t.length===1)return ze(t[0]??"");if(n==="cat"&&t.length>0&&!r)return t.map((i)=>Ge(Je(process.cwd(),i),"utf8")).join("");if(n==="uname"){let i=t[0]??"-s";if(t.length<=1&&i==="-m")return He();if(t.length<=1&&i==="-r")return Xe();if(t.length<=1&&i==="-s")return{aix:"AIX",android:"Android",cygwin:"CYGWIN",darwin:"Darwin",freebsd:"FreeBSD",haiku:"Haiku",linux:"Linux",netbsd:"NetBSD",openbsd:"OpenBSD",sunos:"SunOS",win32:"Windows_NT"}[Te()]??Te()}return}function rn(e){return e.replace(/\$\(([^)]+)\)/g,(n,t)=>{try{let r=nn(t),i=r[0];if(!i||!en.has(i))return console.warn(`[env] Blocked command substitution for disallowed command: ${i}`),"";let s=tn(r);if(s!==void 0)return s.trim();let o=We(i,r.slice(1),{encoding:"utf8",env:process.env});if(o.status===0)return o.stdout.trim();let d=o.stderr.trim();console.warn(`[env] Command substitution failed (exit code ${o.status??"unknown"}): ${i}${d?`: ${d}`:""}`)}catch(r){console.warn(`[env] Command substitution error: ${r instanceof Error?r.message:String(r)}`)}return""})}async function Fn(e,n={}){let t={},r=[],i=[];for(let s of e)try{let o=Bun.file(s);if(!o.size)continue;let d=await o.text(),{parsed:a,errors:c,skippedEncrypted:f}=h(d,n);r.push(...c),i.push(...f);for(let[u,l]of Object.entries(a)){if(!n.overload&&t[u]!==void 0)continue;t[u]=l}}catch(o){if(o?.code==="ENOENT")continue;r.push(`Failed to read ${s}: ${o instanceof Error?o.message:String(o)}`)}return{parsed:t,errors:r,skippedEncrypted:i}}function L(e){if(!e||w(e))return;let n=e.trim().toLowerCase();if(!/^[a-z0-9_-]+$/.test(n))return;if(n==="prod")return"production";if(n==="stage")return"staging";if(n==="dev")return"development";return n}var j=null,X=null,Q,q;function sn(e,n,t=".env.keys"){let r=V(n,t);if(!H(r))return;try{let{parsed:i}=h(te(r,"utf-8"));if(e){let s=i[`DOTENV_PRIVATE_KEY_${e.toUpperCase()}`];if(s)return s}return i.DOTENV_PRIVATE_KEY}catch{return}}function re(e={}){return L(e.env)||q||L("development")||L(process.env.DOTENV_ENV)||L(process.env.APP_ENV)||"development"}function on(e={}){let n=re(e),t=V(e.cwd||process.cwd());if(j===n&&X===t)return Q;let r=G(n);if(!r)r=process.env.DOTENV_PRIVATE_KEY;if(!r)r=sn(n,t);return j=n,X=t,Q=r,r}function qn(){j=null,X=null,Q=void 0,q=void 0}function Re(e,n={}){if(!w(e))return e;let t=on(n);if(!t)return;try{let r=e.startsWith("enc:")?`encrypted:${e.slice(4)}`:e;return K(r,t)}catch{return}}function ve(e={}){let{path:n=[".env"],overload:t=!1,env:r,privateKey:i,keysFile:s=".env.keys",quiet:o=!1,cwd:d=process.cwd()}=e,a=Array.isArray(n)?n:[n],c=[],f=0,u=new Set(Object.entries(B(process.env)).filter(([,g])=>g!==void 0).map(([g])=>g)),l=i;if(!l&&s){let g=V(d,s);if(H(g))try{let p=te(g,"utf-8"),{parsed:y}=h(p);if(r){let v=`DOTENV_PRIVATE_KEY_${r.toUpperCase()}`;l=y[v]||y.DOTENV_PRIVATE_KEY}else l=y.DOTENV_PRIVATE_KEY}catch(p){c.push(`Failed to load keys file: ${p instanceof Error?p.message:"Unknown error"}`)}}if(!l)l=G(r||"")||G();for(let g of a){let p=V(d,g);if(!H(p)){if(!o)c.push(`File not found: ${p}`);continue}try{let y=te(p,"utf-8"),{parsed:v,errors:m,skippedEncrypted:O}=h(y,{privateKey:l,processEnv:process.env});if(c.push(...m),O.length>0){let _=B(process.env,O);for(let F of Object.keys(process.env))if(!Object.hasOwn(_,F))delete process.env[F];let A=r?`DOTENV_PRIVATE_KEY_${r.toUpperCase()}`:"DOTENV_PRIVATE_KEY",R=l?"decryption failed":`${A} not set`,N=O.slice(0,5).join(", "),k=O.length>5?`, \u2026 +${O.length-5} more`:"";console.warn(`[env] warning: skipped ${O.length} encrypted value(s) in ${g} (${R}; existing plaintext or defaults apply): ${N}${k}`)}let T=0;for(let[_,A]of Object.entries(v)){if(_==="DOTENV_PUBLIC_KEY")continue;let R=process.env[_],N=w(R)&&A!==R;if(t||!u.has(_)||N)process.env[_]=A,f++,T++}if(!o&&!process.env.__ENV_LOADED__)console.error(`[env] loaded ${T}/${Object.keys(v).length} variables from ${g}`),process.env.__ENV_LOADED__="1"}catch(y){c.push(`Failed to load ${p}: ${y instanceof Error?y.message:"Unknown error"}`)}}return{loaded:f,errors:c}}function dn(e={}){return{name:"env-plugin",setup(n){ve(e)}}}function zn(e={}){let n=L(e.env)||L("development")||L(process.env.DOTENV_ENV)||L(process.env.APP_ENV)||"development",t=e.cwd||process.cwd();if(q!==n)q=n,j=null,X=null,Q=void 0;let r=[],i=(d)=>{if(!r.includes(d)&&H(V(t,d)))r.push(d)};i(".env"),i(".env.local");let s=`.env.${n}`,o=`.env.${n}.local`;return i(s),i(o),ve({...e,path:r,env:n})}var Zn=dn;var Oe={APP_ENV:["local","dev","development","staging","prod","production"],DB_CONNECTION:["mysql","sqlite","postgres","singlestore","vitess","dynamodb"],DB_MIGRATE_FRESH:["allow","confirm","disabled"],MAIL_MAILER:["smtp","mailgun","ses","log","sendgrid","mailtrap","capture"],SEARCH_ENGINE_DRIVER:["opensearch","meilisearch","algolia","typesense"],FRONTEND_APP_ENV:["development","staging","production"]};function et(e){return e}import ie from"process";function cn(e){let n=e.toLowerCase();return n==="localhost"||n.endsWith(".localhost")||n==="127.0.0.1"||n==="0.0.0.0"||n==="::1"||n==="[::1]"}function rt(){let e=(ie.env.APP_ENV??ie.env.NODE_ENV??"").toLowerCase();if(e==="production"||e==="prod"||e==="staging")return!1;let n=(ie.env.APP_URL??"").trim();if(n)try{return cn(new URL(n.includes("://")?n:`https://${n}`).hostname)}catch{}return e===""||e==="local"||e==="dev"||e==="test"||e==="testing"}import{existsSync as P,readFileSync as I,renameSync as Ae,rmSync as Se,writeFileSync as D}from"fs";import{basename as Ie,dirname as oe,resolve as S}from"path";function at(e,n){if(e)return e;let t=String(n??"").trim();if(!t||t==="development"||t==="dev"||t==="local")return"";if(!/^[\w.-]+$/.test(t))return"";return`.env.${t}`}function he(e,n){return(!n.key||e.includes(n.key))&&(!n.excludeKey||!e.includes(n.excludeKey))}function an(e,n){let t=[];for(let r of e.split(`
5
+ `)){let i=r.trim();if(!i||i.startsWith("#"))continue;if(i.startsWith("DOTENV_PUBLIC_KEY"))continue;let s=i.match(/^([^=]+)=(.*)$/);if(!s||s[1]===void 0)continue;let o=s[1].trim();if(he(o,n))t.push(o)}return t}function Pe(e,n,t,r){let i=["#/-------------------[DOTENV_PUBLIC_KEY]--------------------/","#/ versioned X25519 encryption for .env files /","#/ [how it works](https://stacksjs.com/encryption) /","#/----------------------------------------------------------/",`${t}="${n}"`,""],s=!1;for(let o of e.split(`
6
+ `)){let d=o.trim();if(d.startsWith("#/"))continue;if(d.startsWith("DOTENV_PUBLIC_KEY"))continue;if(!d&&!s)continue;if(!d||d.startsWith("#")){s=!0,i.push(o);continue}s=!0;let a=d.match(/^([^=]+)=(.*)$/);if(!a||a[1]===void 0||a[2]===void 0){i.push(o);continue}let c=a[1].trim(),f=a[2].trim();if(f.startsWith('"')&&f.endsWith('"')||f.startsWith("'")&&f.endsWith("'"))f=f.slice(1,-1);if(he(c,r)&&!f.startsWith("encrypted:"))f=W(f,n);i.push(`${c}="${f}"`)}return i.join(`
7
+ `)}function x(e){let t=((e||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase();return{publicKeyName:t?`DOTENV_PUBLIC_KEY_${t}`:"DOTENV_PUBLIC_KEY",privateKeyName:t?`DOTENV_PRIVATE_KEY_${t}`:"DOTENV_PRIVATE_KEY"}}function De(e){let{publicKeyName:n}=x(e);return n==="DOTENV_PUBLIC_KEY"?[n]:[n,"DOTENV_PUBLIC_KEY"]}function se(e){let{privateKeyName:n}=x(e);return n==="DOTENV_PRIVATE_KEY"?[n]:[n,"DOTENV_PRIVATE_KEY"]}function Ne(e){for(let n of e.split(`
8
+ `)){let t=n.trim();if(!t||t.startsWith("#"))continue;let r=t.match(/^([^=]+)=(.*)$/);if(!r?.[1]||r[2]===void 0)continue;let i=r[2].trim().replace(/^["']|["']$/g,"");if(i.startsWith("encrypted:")||i.startsWith("enc:"))return!0}return!1}function un(e,n){let t=De(n);for(let r of e.split(`
9
+ `)){let i=r.trim();if(!i||i.startsWith("#"))continue;let s=i.match(/^([^=]+)=["']?([^"'\n]+)["']?/);if(!s?.[1]||!s[2])continue;let o=s[1].trim();if(t.includes(o))return{name:o,key:s[2]}}return}function fn(e,n){let t,r=!1;for(let i of e.split(`
10
+ `)){let s=i.trim();if(!s||s.startsWith("#"))continue;let o=s.match(/^([^=]+)=(.*)$/);if(!o?.[1]||o[2]===void 0)continue;let d=o[1].trim(),a=o[2].trim().replace(/^["']|["']$/g,"");if(d===n)t=a;else if(a.startsWith("encrypted:")||a.startsWith("enc:"))r=!0}return t&&r?t:void 0}function ut(e={}){let n=e.cwd||process.cwd(),t=S(n,e.file||".env"),r=S(n,e.keysFile||".env.keys");if(!P(t))return{success:!1,error:`File not found: ${t}`};try{let i,s,o=fn(I(t,"utf-8"),x(e.file).publicKeyName);if(o)i=o;else if(P(r)){let c=I(r,"utf-8"),{parsed:f}=h(c),{publicKeyName:u,privateKeyName:l}=x(e.file);if(i=f[u]||"",s=f[l]||"",!i||!s){let g=U();i=g.publicKey,s=g.privateKey;let p=`
14
11
  ${u}="${i}"
15
- ${f}="${s}"
16
- `;D(r,E,"utf-8")}let d=N(t,"utf-8"),c=Se(d,i,k(e.file).publicKeyName,e);if(e.stdout)return{success:!0,output:c};return D(t,c,"utf-8"),{success:!0,output:`\u2714 encrypted (${e.file||".env"})
17
- \u2714 key added to ${e.keysFile||".env.keys"}`}}catch(i){return{success:!1,error:`Failed to encrypt: ${i instanceof Error?i.message:"Unknown error"}`}}}function cn(e={}){let n=e.cwd||process.cwd(),t=S(n,e.file||".env"),r=S(n,e.keysFile||".env.keys");if(!P(t))return{success:!1,error:`File not found: ${t}`};if(!P(r))return{success:!1,error:`Keys file not found: ${r}`};try{let i=N(r,"utf-8"),{parsed:s}=h(i),c=((e.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),a=c?`DOTENV_PRIVATE_KEY_${c}`:"DOTENV_PRIVATE_KEY",u=s[a];if(!u)return{success:!1,error:`Private key not found: ${a}`};let f=[u];if(a!=="DOTENV_PRIVATE_KEY"&&s.DOTENV_PRIVATE_KEY&&s.DOTENV_PRIVATE_KEY!==u)f.push(s.DOTENV_PRIVATE_KEY);let g=N(t,"utf-8").split(`
18
- `),p=[];for(let v of g){let y=v.trim();if(!y||y.startsWith("#")){p.push(v);continue}if(y.startsWith("DOTENV_PUBLIC_KEY"))continue;let O=y.match(/^([^=]+)=(.*)$/);if(!O||O[1]===void 0||O[2]===void 0){p.push(v);continue}let m=O[1].trim(),_=O[2].trim();if(_.startsWith('"')&&_.endsWith('"')||_.startsWith("'")&&_.endsWith("'"))_=_.slice(1,-1);let A=_.startsWith("encrypted:");if(e.key&&!m.includes(e.key))A=!1;if(A){let R,I;for(let j of f)try{R=L(_,j);break}catch(Le){I=Le}if(R===void 0)throw I instanceof Error?I:Error(`Unable to decrypt ${m}`);_=R}p.push(`${m}="${_}"`)}let T=p.join(`
19
- `);if(e.stdout)return{success:!0,output:T};return D(t,T,"utf-8"),{success:!0,output:`\u2714 decrypted (${e.file||".env"})`}}catch(i){return{success:!1,error:`Failed to decrypt: ${i instanceof Error?i.message:"Unknown error"}`}}}function nt(e,n,t={}){let r=t.cwd||process.cwd(),i=S(r,t.file||".env"),s=t.keysFile?S(r,t.keysFile):S(te(i),".env.keys");try{let o="";if(P(i))o=N(i,"utf-8");let d=o.split(`
20
- `),c=!1,a,E=((t.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),g=E?`DOTENV_PUBLIC_KEY_${E}`:"DOTENV_PUBLIC_KEY",p=E?`DOTENV_PRIVATE_KEY_${E}`:"DOTENV_PRIVATE_KEY",T=on(o,t.file),v=T?.name??g;if(a=T?.key,a){let m=!1;if(P(s)){let A=N(s,"utf-8"),{parsed:R}=h(A);m=ne(t.file).some((I)=>Boolean(R[I]))}let _=ne(t.file).some((A)=>Boolean(process.env[A]));if(!m&&!_&&!Oe(o))a=void 0}if(!t.plain&&!a&&Oe(o))return{success:!1,error:`${t.file||".env"} already contains encrypted values, but no key that can be used to add another was found.
12
+ ${l}="${s}"
13
+ `;D(r,c+p,"utf-8")}}else{let c=U();i=c.publicKey,s=c.privateKey;let{publicKeyName:f,privateKeyName:u}=x(e.file),l=`# .env.keys - Keep this file secure and never commit to source control
14
+ ${f}="${i}"
15
+ ${u}="${s}"
16
+ `;D(r,l,"utf-8")}let d=I(t,"utf-8"),a=Pe(d,i,x(e.file).publicKeyName,e);if(e.stdout)return{success:!0,output:a};return D(t,a,"utf-8"),{success:!0,output:`\u2714 encrypted (${e.file||".env"})
17
+ \u2714 key added to ${e.keysFile||".env.keys"}`}}catch(i){return{success:!1,error:`Failed to encrypt: ${i instanceof Error?i.message:"Unknown error"}`}}}function ln(e={}){let n=e.cwd||process.cwd(),t=S(n,e.file||".env"),r=S(n,e.keysFile||".env.keys");if(!P(t))return{success:!1,error:`File not found: ${t}`};if(!P(r))return{success:!1,error:`Keys file not found: ${r}`};try{let i=I(r,"utf-8"),{parsed:s}=h(i),a=((e.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),c=a?`DOTENV_PRIVATE_KEY_${a}`:"DOTENV_PRIVATE_KEY",f=s[c];if(!f)return{success:!1,error:`Private key not found: ${c}`};let u=[f];if(c!=="DOTENV_PRIVATE_KEY"&&s.DOTENV_PRIVATE_KEY&&s.DOTENV_PRIVATE_KEY!==f)u.push(s.DOTENV_PRIVATE_KEY);let g=I(t,"utf-8").split(`
18
+ `),p=[];for(let v of g){let m=v.trim();if(!m||m.startsWith("#")){p.push(v);continue}if(m.startsWith("DOTENV_PUBLIC_KEY"))continue;let O=m.match(/^([^=]+)=(.*)$/);if(!O||O[1]===void 0||O[2]===void 0){p.push(v);continue}let T=O[1].trim(),_=O[2].trim();if(_.startsWith('"')&&_.endsWith('"')||_.startsWith("'")&&_.endsWith("'"))_=_.slice(1,-1);let A=_.startsWith("encrypted:");if(e.key&&!T.includes(e.key))A=!1;if(A){let R,N;for(let k of u)try{R=K(_,k);break}catch(F){N=F}if(R===void 0)throw N instanceof Error?N:Error(`Unable to decrypt ${T}`);_=R}p.push(`${T}="${_}"`)}let y=p.join(`
19
+ `);if(e.stdout)return{success:!0,output:y};return D(t,y,"utf-8"),{success:!0,output:`\u2714 decrypted (${e.file||".env"})`}}catch(i){return{success:!1,error:`Failed to decrypt: ${i instanceof Error?i.message:"Unknown error"}`}}}function ft(e,n,t={}){let r=t.cwd||process.cwd(),i=S(r,t.file||".env"),s=t.keysFile?S(r,t.keysFile):S(oe(i),".env.keys");try{let o="";if(P(i))o=I(i,"utf-8");let d=o.split(`
20
+ `),a=!1,c,l=((t.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),g=l?`DOTENV_PUBLIC_KEY_${l}`:"DOTENV_PUBLIC_KEY",p=l?`DOTENV_PRIVATE_KEY_${l}`:"DOTENV_PRIVATE_KEY",y=un(o,t.file),v=y?.name??g;if(c=y?.key,c){let T=!1;if(P(s)){let A=I(s,"utf-8"),{parsed:R}=h(A);T=se(t.file).some((N)=>Boolean(R[N]))}let _=se(t.file).some((A)=>Boolean(process.env[A]));if(!T&&!_&&!Ne(o))c=void 0}if(!t.plain&&!c&&Ne(o))return{success:!1,error:`${t.file||".env"} already contains encrypted values, but no key that can be used to add another was found.
21
21
  Generating one would leave every existing encrypted value unreadable.
22
- Provide the private key as ${ne(t.file)[0]} in the environment, restore ${s}, or use --plain to write this value unencrypted.`};if(!t.plain&&!a){let m=x();a=m.publicKey;let _="";if(P(s))_=N(s,"utf-8");_+=`
23
- ${g}="${m.publicKey}"
24
- ${p}="${m.privateKey}"
25
- `,D(s,_,"utf-8");let A=Ie(t.file);for(let R=d.length-1;R>=0;R--){let I=d[R];if(I!==void 0&&A.some((j)=>I.trim().startsWith(`${j}=`)))d.splice(R,1)}d.unshift(`${v}="${a}"`)}let y=n;if(!t.plain&&a)y=V(n,a);for(let m=0;m<d.length;m++){let _=d[m];if(_===void 0)continue;if(_.trim().startsWith(`${e}=`)){d[m]=`${e}="${y}"`,c=!0;break}}if(!c)d.push(`${e}="${y}"`);let O=d.join(`
26
- `);return D(i,O,"utf-8"),{success:!0,output:`set ${e}${t.plain?"":" with encryption"} (${t.file||".env"})`}}catch(o){return{success:!1,error:`Failed to set: ${o instanceof Error?o.message:"Unknown error"}`}}}function tt(e,n={}){let t=n.cwd||process.cwd(),r=S(t,n.file||".env");if(!P(r))return{success:!1,error:`File not found: ${r}`};try{let i,s=S(t,n.keysFile||".env.keys"),c=((n.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),a=c?`DOTENV_PRIVATE_KEY_${c}`:"DOTENV_PRIVATE_KEY";if(P(s)){let p=N(s,"utf-8"),{parsed:T}=h(p);i=T[a]}if(!i)i=process.env[a];let u=N(r,"utf-8"),{parsed:f}=h(u,{privateKey:i});if(e&&!n.all){let p=f[e];if(p===void 0)return{success:!1,error:`Key not found: ${e}`};return{success:!0,output:p}}let E=n.all?{...process.env,...f}:f,g;switch(n.format){case"shell":g=Object.entries(E).map(([p,T])=>`${p}=${T}`).join(" ");break;case"eval":g=Object.entries(E).map(([p,T])=>`${p}="${T}"`).join(`
27
- `);break;case"json":default:g=n.prettyPrint?JSON.stringify(E,null,2):JSON.stringify(E);break}return{success:!0,output:g}}catch(i){return{success:!1,error:`Failed to get: ${i instanceof Error?i.message:"Unknown error"}`}}}function rt(e,n={}){let t=n.cwd||process.cwd(),r=S(t,n.keysFile||".env.keys");if(!P(r))return{success:!1,error:`Keys file not found: ${r}`};try{let i=N(r,"utf-8"),{parsed:s}=h(i),c=((n.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),a=c?`DOTENV_PUBLIC_KEY_${c}`:"DOTENV_PUBLIC_KEY",u=c?`DOTENV_PRIVATE_KEY_${c}`:"DOTENV_PRIVATE_KEY";if(e){let g=s[e];if(!g)return{success:!1,error:`Key not found: ${e}`};return{success:!0,output:g}}let f={[a]:s[a],[u]:s[u]};return{success:!0,output:n.format==="shell"?`${a}=${f[a]} ${u}=${f[u]}`:JSON.stringify(f)}}catch(i){return{success:!1,error:`Failed to get keypair: ${i instanceof Error?i.message:"Unknown error"}`}}}function an(e,n,t,r){let i=e.split(`
28
- `),s=!1,o=!1;for(let d=0;d<i.length;d++){let c=i[d];if(c===void 0)continue;if(c.startsWith(`${t}=`))i[d]=`${t}="${n.publicKey}"`,s=!0;else if(c.startsWith(`${r}=`))i[d]=`${r}="${n.privateKey}"`,o=!0}if(!s)i.push(`${t}="${n.publicKey}"`);if(!o)i.push(`${r}="${n.privateKey}"`);return`${i.join(`
22
+ Provide the private key as ${se(t.file)[0]} in the environment, restore ${s}, or use --plain to write this value unencrypted.`};if(!t.plain&&!c){let T=U();c=T.publicKey;let _="";if(P(s))_=I(s,"utf-8");_+=`
23
+ ${g}="${T.publicKey}"
24
+ ${p}="${T.privateKey}"
25
+ `,D(s,_,"utf-8");let A=De(t.file);for(let R=d.length-1;R>=0;R--){let N=d[R];if(N!==void 0&&A.some((k)=>N.trim().startsWith(`${k}=`)))d.splice(R,1)}d.unshift(`${v}="${c}"`)}let m=n;if(!t.plain&&c)m=W(n,c);for(let T=0;T<d.length;T++){let _=d[T];if(_===void 0)continue;if(_.trim().startsWith(`${e}=`)){d[T]=`${e}="${m}"`,a=!0;break}}if(!a)d.push(`${e}="${m}"`);let O=d.join(`
26
+ `);return D(i,O,"utf-8"),{success:!0,output:`set ${e}${t.plain?"":" with encryption"} (${t.file||".env"})`}}catch(o){return{success:!1,error:`Failed to set: ${o instanceof Error?o.message:"Unknown error"}`}}}function lt(e,n={}){let t=n.cwd||process.cwd(),r=S(t,n.file||".env");if(!P(r))return{success:!1,error:`File not found: ${r}`};try{let i,s=S(t,n.keysFile||".env.keys"),a=((n.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),c=a?`DOTENV_PRIVATE_KEY_${a}`:"DOTENV_PRIVATE_KEY";if(P(s)){let p=I(s,"utf-8"),{parsed:y}=h(p);i=y[c]}if(!i)i=process.env[c];let f=I(r,"utf-8"),{parsed:u}=h(f,{privateKey:i});if(e&&!n.all){let p=u[e];if(p===void 0)return{success:!1,error:`Key not found: ${e}`};return{success:!0,output:p}}let l=n.all?{...process.env,...u}:u,g;switch(n.format){case"shell":g=Object.entries(l).map(([p,y])=>`${p}=${y}`).join(" ");break;case"eval":g=Object.entries(l).map(([p,y])=>`${p}="${y}"`).join(`
27
+ `);break;case"json":default:g=n.prettyPrint?JSON.stringify(l,null,2):JSON.stringify(l);break}return{success:!0,output:g}}catch(i){return{success:!1,error:`Failed to get: ${i instanceof Error?i.message:"Unknown error"}`}}}function Et(e,n={}){let t=n.cwd||process.cwd(),r=S(t,n.keysFile||".env.keys");if(!P(r))return{success:!1,error:`Keys file not found: ${r}`};try{let i=I(r,"utf-8"),{parsed:s}=h(i),a=((n.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),c=a?`DOTENV_PUBLIC_KEY_${a}`:"DOTENV_PUBLIC_KEY",f=a?`DOTENV_PRIVATE_KEY_${a}`:"DOTENV_PRIVATE_KEY";if(e){let g=s[e];if(!g)return{success:!1,error:`Key not found: ${e}`};return{success:!0,output:g}}let u={[c]:s[c],[f]:s[f]};return{success:!0,output:n.format==="shell"?`${c}=${u[c]} ${f}=${u[f]}`:JSON.stringify(u)}}catch(i){return{success:!1,error:`Failed to get keypair: ${i instanceof Error?i.message:"Unknown error"}`}}}function En(e,n,t,r){let i=e.split(`
28
+ `),s=!1,o=!1;for(let d=0;d<i.length;d++){let a=i[d];if(a===void 0)continue;if(a.startsWith(`${t}=`))i[d]=`${t}="${n.publicKey}"`,s=!0;else if(a.startsWith(`${r}=`))i[d]=`${r}="${n.privateKey}"`,o=!0}if(!s)i.push(`${t}="${n.publicKey}"`);if(!o)i.push(`${r}="${n.privateKey}"`);return`${i.join(`
29
29
  `).replace(/\n+$/,"")}
30
- `}function it(e={}){let n=cn({file:e.file,keysFile:e.keysFile,stdout:!0,cwd:e.cwd});if(!n.success)return n;if(n.output===void 0)return{success:!1,error:"Rotation produced no decrypted content"};let t=e.cwd||process.cwd(),r=e.file||".env",i=e.keysFile||".env.keys",s=S(t,r),o=S(t,i),{publicKeyName:d,privateKeyName:c}=k(e.file);if(e.dryRun){let y=sn(n.output,e);return{success:!0,output:["Dry run: nothing was written.",`${r}: ${y.length} value${y.length===1?"":"s"} would be re-encrypted under a new ${d}.`,`${i}: ${d} and ${c} would be replaced.`,`The current ${c} decrypts nothing once that happens, so keep a copy until the rotation is verified.`].join(`
31
- `)}}let a=x(),u=Se(n.output,a.publicKey,d,e);if(e.stdout)return{success:!0,output:u,notice:[`Nothing was written. To adopt the output above, put this keypair in ${i}:`,"",`${d}="${a.publicKey}"`,`${c}="${a.privateKey}"`,"","Without it that output cannot be decrypted. Run without --stdout to have both applied together."].join(`
32
- `)};let f=N(s),E=N(o),g=an(E.toString("utf8"),a,d,c),p=`${process.pid}-${Date.now()}`,T=S(te(s),`.${ve(s)}.${p}.rotate`),v=S(te(o),`.${ve(o)}.${p}.rotate`);try{D(T,u,{encoding:"utf8",mode:384}),D(v,g,{encoding:"utf8",mode:384}),me(T,s);try{me(v,o)}catch(y){throw D(s,f),y}return{success:!0,output:`\u2714 rotated (${r}) to encrypted:v2`}}catch(y){if(!P(o))D(o,E,{mode:384});return{success:!1,error:`Failed to rotate without exposing plaintext: ${y instanceof Error?y.message:"Unknown error"}`}}finally{Re(T,{force:!0}),Re(v,{force:!0})}}function un(e){return typeof e==="string"&&(e.startsWith("encrypted:")||e.startsWith("enc:"))}var fn=/(?:^|_)(?:PASSWORD|PASSWD|SECRET|TOKEN|APIKEY|API_KEY|CREDENTIAL|CREDENTIALS|PRIVATE|SALT|DSN|CERT|CIPHER|SIGNATURE|WEBHOOK)(?:_|$)|(?:^|_)(?:ACCESS|SECRET)_KEY(?:_|$)|_KEY$/,ln=/^DOTENV_(?:PUBLIC|PRIVATE)_KEY/,En=/^\$\{[^}]+\}$/;function ot(e,n={}){let{placeholders:t={},strict:r=!1}=n,i=[];for(let[s,o]of Object.entries(e)){if(!o||o.trim()==="")continue;if(un(o))continue;if(ln.test(s))continue;if(En.test(o.trim()))continue;let d=fn.test(s),c=Object.prototype.hasOwnProperty.call(t,s)&&t[s]===o;if(d&&!c){i.push({key:s,reason:"plaintext-secret"});continue}if(r)i.push({key:s,reason:"plaintext-value"})}return i}function dt(e){return e.split(`
33
- `).map((n)=>n.trim()).filter(Boolean).filter((n)=>n!==".env.example").filter((n)=>/(?:^|\/)\.env(?:\.|$)/.test(n)).filter((n)=>!n.endsWith(".env.keys"))}function Ne(e){return`${e.replace(/[^a-z0-9]/gi,"").toUpperCase()}_`}function pn(e,n={}){let t=n.self?Ne(n.self):void 0,r=(n.tenants??[]).filter((o)=>typeof o==="string"&&o.trim().length>0).map((o)=>({slug:o,prefix:Ne(o)})).filter(({prefix:o})=>o!==t).sort((o,d)=>d.prefix.length-o.prefix.length),i={},s={};for(let[o,d]of Object.entries(e)){let c=r.find(({prefix:a})=>o.startsWith(a));if(c){(s[c.slug]??={})[o]=d;continue}i[o]=d}return{own:i,foreign:s}}function at(e,n={}){return pn(e,n).own}function ut(e){return Object.entries(e.foreign).map(([n,t])=>({tenant:n,keys:Object.keys(t).sort()})).sort((n,t)=>n.tenant.localeCompare(t.tenant))}import l from"process";import{platform as gn}from"os";var he=typeof Bun<"u",Pe=typeof l<"u"&&l.versions?.node!==void 0,_n=he?"bun":Pe?"node":"unknown",pt={name:_n,version:he?Bun.version:Pe?l.version:void 0},ie=gn(),gt=ie==="win32",_t=ie==="darwin",Tt=ie==="linux",De=Boolean(l.stdout?.isTTY),yt=typeof globalThis.window<"u";function w(){return l.env.APP_ENV||l.env.NODE_ENV||"local"}function mt(){return w()==="local"}function Rt(){let e=w();return e==="development"||e==="dev"||e==="local"}function vt(){return w()==="staging"}function Ot(){let e=w();return e==="production"||e==="prod"}function At(){let e=w();return e==="test"||e==="testing"}var Tn=Boolean(l.env.CI||l.env.CONTINUOUS_INTEGRATION||l.env.BUILD_NUMBER||l.env.RUN_ID),St=Boolean(l.env.DEBUG||l.env.VERBOSE||l.argv.includes("--debug")||l.argv.includes("--verbose")),yn=Tn||!De,It=Boolean(!yn&&(De||l.env.COLORTERM||l.env.FORCE_COLOR||l.env.TERM&&l.env.TERM!=="dumb")),re={github:{name:"GitHub Actions",detected:Boolean(l.env.GITHUB_ACTIONS)},gitlab:{name:"GitLab CI",detected:Boolean(l.env.GITLAB_CI)},circle:{name:"CircleCI",detected:Boolean(l.env.CIRCLECI)},travis:{name:"Travis CI",detected:Boolean(l.env.TRAVIS)},jenkins:{name:"Jenkins",detected:Boolean(l.env.JENKINS_URL)},vercel:{name:"Vercel",detected:Boolean(l.env.VERCEL)},netlify:{name:"Netlify",detected:Boolean(l.env.NETLIFY)},heroku:{name:"Heroku",detected:Boolean(l.env.DYNO)},aws:{name:"AWS",detected:Boolean(l.env.AWS_REGION||l.env.AWS_LAMBDA_FUNCTION_NAME)},azure:{name:"Azure",detected:Boolean(l.env.AZURE_HTTP_USER_AGENT)},cloudflare:{name:"Cloudflare",detected:Boolean(l.env.CF_PAGES)},railway:{name:"Railway",detected:Boolean(l.env.RAILWAY_ENVIRONMENT)},render:{name:"Render",detected:Boolean(l.env.RENDER)}},mn=Object.keys(re).find((e)=>{let n=re[e];return n!==void 0&&n.detected})||"unknown",Nt=re[mn]||{name:"Unknown",detected:!1};var be=new Set;function On(e){if(be.has(e))return;be.add(e);let n=ee();console.warn(`[env] warning: "${e}" is encrypted but no usable private key was found for the "${n}" environment; falling back to its default. Set DOTENV_PRIVATE_KEY_${n.toUpperCase()} or ship .env.keys.`)}var An={get:(e,n)=>{let t=e,r=t[n];if(typeof r==="string"&&(r.startsWith("encrypted:")||r.startsWith("enc:"))){let s=_e(r);if(s===void 0){On(n),delete t[n];return}t[n]=s,r=s}let i=["_PORT","_TIMEOUT","_TTL","_SIZE","_LIMIT","_MAX","_MIN","_INTERVAL","_RETRIES","_CONCURRENCY","_WORKERS","_CONNECTIONS"];if(typeof r==="string"&&/^\d+$/.test(r)&&!r.startsWith("0")&&i.some((s)=>n.endsWith(s)))return Number(r);if(typeof r==="string"){let s=r.toLowerCase();if(s==="true")return!0;if(s==="false")return!1}return r},set:(e,n,t)=>Reflect.set(e,n,t,e),deleteProperty:(e,n)=>Reflect.deleteProperty(e,n)};function Sn(){return typeof Bun<"u"?Bun.env:Rn.env}var Ke=new Proxy(Sn(),An);function Lt(e,n,t){let r=t?.path||vn(".env"),s=Ce.readFileSync(r,"utf-8").split(`
34
- `),o=s.findIndex((a)=>a.startsWith(`${e}=`)),c=/[\s"'#$\\]/.test(n)?`"${n.replace(/"/g,"\\\"")}"`:n;if(o!==-1)s[o]=`${e}=${c}`;else s.push(`${e}=${c}`);Ce.writeFileSync(r,s.join(`
35
- `))}function kt(e=Ke){let n=[],t=e;for(let[r,i]of Object.entries(ye)){let s=t[r];if(s!==void 0&&s!==""&&!i.includes(String(s)))n.push(`${r}="${s}" is not valid. Allowed values: ${i.join(", ")}`)}return n}function xt(e,n=Ke){let t=[],r=n;for(let i of e){let s=r[i];if(s===void 0||s===""||s===null)t.push(i)}if(t.length>0)throw Error(`[env] Missing required environment variable(s): ${t.join(", ")}. Set them in .env or your process environment before booting.`);return n}export{fn as SECRET_NAME_PATTERN,ee as activeEnvName,xe as aesDecrypt,Nn as aesEncrypt,w as appEnv,Wn as autoLoadEnv,on as declaredEnvPublicKey,cn as decryptEnv,_e as decryptEnvValue,L as decryptValue,jn as defineEnv,et as encryptEnv,V as encryptValue,Ke as env,ye as envEnum,Oe as envHasCiphertext,k as envKeyNames,rn as envPlugin,ne as envPrivateKeyNames,Ie as envPublicKeyNames,ut as foreignTenantKeys,x as generateKeypair,tt as getEnv,rt as getKeypair,M as getPrivateKey,De as hasTTY,yt as hasWindow,he as isBun,Tn as isCI,It as isColorSupported,St as isDebug,Rt as isDevelopment,un as isEncryptedValue,hn as isLegacyEncryptedValue,Tt as isLinux,mt as isLocal,_t as isMacOS,yn as isMinimal,Pe as isNode,Ot as isProduction,vt as isStaging,At as isTesting,gt as isWindows,Te as loadEnv,Un as loadEnvFiles,Pn as migrateEncryptedValue,h as parse,Dn as parseEnvFromKey,pn as partitionTenantEnv,ot as plaintextSecrets,ie as platform,Sn as process,mn as provider,Nt as providerInfo,xt as requireEnv,Yn as resetPrivateKeyCache,Zn as resolveEnvFile,tn as resolvePrivateKey,dn as reusableEnvPublicKey,it as rotateKeypair,_n as runtime,pt as runtimeInfo,nt as setEnv,at as stripForeignTenantEnv,Ne as tenantEnvPrefix,dt as trackedEnvFiles,kt as validateEnv,Lt as writeEnv};
30
+ `}function pt(e={}){let n=ln({file:e.file,keysFile:e.keysFile,stdout:!0,cwd:e.cwd});if(!n.success)return n;if(n.output===void 0)return{success:!1,error:"Rotation produced no decrypted content"};let t=e.cwd||process.cwd(),r=e.file||".env",i=e.keysFile||".env.keys",s=S(t,r),o=S(t,i),{publicKeyName:d,privateKeyName:a}=x(e.file);if(e.dryRun){let m=an(n.output,e);return{success:!0,output:["Dry run: nothing was written.",`${r}: ${m.length} value${m.length===1?"":"s"} would be re-encrypted under a new ${d}.`,`${i}: ${d} and ${a} would be replaced.`,`The current ${a} decrypts nothing once that happens, so keep a copy until the rotation is verified.`].join(`
31
+ `)}}let c=U(),f=Pe(n.output,c.publicKey,d,e);if(e.stdout)return{success:!0,output:f,notice:[`Nothing was written. To adopt the output above, put this keypair in ${i}:`,"",`${d}="${c.publicKey}"`,`${a}="${c.privateKey}"`,"","Without it that output cannot be decrypted. Run without --stdout to have both applied together."].join(`
32
+ `)};let u=I(s),l=I(o),g=En(l.toString("utf8"),c,d,a),p=`${process.pid}-${Date.now()}`,y=S(oe(s),`.${Ie(s)}.${p}.rotate`),v=S(oe(o),`.${Ie(o)}.${p}.rotate`);try{D(y,f,{encoding:"utf8",mode:384}),D(v,g,{encoding:"utf8",mode:384}),Ae(y,s);try{Ae(v,o)}catch(m){throw D(s,u),m}return{success:!0,output:`\u2714 rotated (${r}) to encrypted:v2`}}catch(m){if(!P(o))D(o,l,{mode:384});return{success:!1,error:`Failed to rotate without exposing plaintext: ${m instanceof Error?m.message:"Unknown error"}`}}finally{Se(y,{force:!0}),Se(v,{force:!0})}}function pn(e){return typeof e==="string"&&(e.startsWith("encrypted:")||e.startsWith("enc:"))}var gn=/(?:^|_)(?:PASSWORD|PASSWD|SECRET|TOKEN|APIKEY|API_KEY|CREDENTIAL|CREDENTIALS|PRIVATE|SALT|DSN|CERT|CIPHER|SIGNATURE|WEBHOOK)(?:_|$)|(?:^|_)(?:ACCESS|SECRET)_KEY(?:_|$)|_KEY$/,_n=/^DOTENV_(?:PUBLIC|PRIVATE)_KEY/,yn=/^\$\{[^}]+\}$/;function _t(e,n={}){let{placeholders:t={},strict:r=!1}=n,i=[];for(let[s,o]of Object.entries(e)){if(!o||o.trim()==="")continue;if(pn(o))continue;if(_n.test(s))continue;if(yn.test(o.trim()))continue;let d=gn.test(s),a=Object.prototype.hasOwnProperty.call(t,s)&&t[s]===o;if(d&&!a){i.push({key:s,reason:"plaintext-secret"});continue}if(r)i.push({key:s,reason:"plaintext-value"})}return i}function yt(e){return e.split(`
33
+ `).map((n)=>n.trim()).filter(Boolean).filter((n)=>n!==".env.example").filter((n)=>/(?:^|\/)\.env(?:\.|$)/.test(n)).filter((n)=>!n.endsWith(".env.keys"))}function Ce(e){return`${e.replace(/[^a-z0-9]/gi,"").toUpperCase()}_`}function mn(e,n={}){let t=n.self?Ce(n.self):void 0,r=(n.tenants??[]).filter((o)=>typeof o==="string"&&o.trim().length>0).map((o)=>({slug:o,prefix:Ce(o)})).filter(({prefix:o})=>o!==t).sort((o,d)=>d.prefix.length-o.prefix.length),i={},s={};for(let[o,d]of Object.entries(e)){let a=r.find(({prefix:c})=>o.startsWith(c));if(a){(s[a.slug]??={})[o]=d;continue}i[o]=d}return{own:i,foreign:s}}function Tt(e,n={}){return mn(e,n).own}function Rt(e){return Object.entries(e.foreign).map(([n,t])=>({tenant:n,keys:Object.keys(t).sort()})).sort((n,t)=>n.tenant.localeCompare(t.tenant))}import E from"process";import{platform as Tn}from"os";var be=typeof Bun<"u",Le=typeof E<"u"&&E.versions?.node!==void 0,Rn=be?"bun":Le?"node":"unknown",St={name:Rn,version:be?Bun.version:Le?E.version:void 0},ce=Tn(),It=ce==="win32",Nt=ce==="darwin",ht=ce==="linux",Ke=Boolean(E.stdout?.isTTY),Pt=typeof globalThis.window<"u";function M(){return E.env.APP_ENV||E.env.NODE_ENV||"local"}function Dt(){return M()==="local"}function Ct(){let e=M();return e==="development"||e==="dev"||e==="local"}function bt(){return M()==="staging"}function Lt(){let e=M();return e==="production"||e==="prod"}function Kt(){let e=M();return e==="test"||e==="testing"}var vn=Boolean(E.env.CI||E.env.CONTINUOUS_INTEGRATION||E.env.BUILD_NUMBER||E.env.RUN_ID),xt=Boolean(E.env.DEBUG||E.env.VERBOSE||E.argv.includes("--debug")||E.argv.includes("--verbose")),On=vn||!Ke,kt=Boolean(!On&&(Ke||E.env.COLORTERM||E.env.FORCE_COLOR||E.env.TERM&&E.env.TERM!=="dumb")),de={github:{name:"GitHub Actions",detected:Boolean(E.env.GITHUB_ACTIONS)},gitlab:{name:"GitLab CI",detected:Boolean(E.env.GITLAB_CI)},circle:{name:"CircleCI",detected:Boolean(E.env.CIRCLECI)},travis:{name:"Travis CI",detected:Boolean(E.env.TRAVIS)},jenkins:{name:"Jenkins",detected:Boolean(E.env.JENKINS_URL)},vercel:{name:"Vercel",detected:Boolean(E.env.VERCEL)},netlify:{name:"Netlify",detected:Boolean(E.env.NETLIFY)},heroku:{name:"Heroku",detected:Boolean(E.env.DYNO)},aws:{name:"AWS",detected:Boolean(E.env.AWS_REGION||E.env.AWS_LAMBDA_FUNCTION_NAME)},azure:{name:"Azure",detected:Boolean(E.env.AZURE_HTTP_USER_AGENT)},cloudflare:{name:"Cloudflare",detected:Boolean(E.env.CF_PAGES)},railway:{name:"Railway",detected:Boolean(E.env.RAILWAY_ENVIRONMENT)},render:{name:"Render",detected:Boolean(E.env.RENDER)}},An=Object.keys(de).find((e)=>{let n=de[e];return n!==void 0&&n.detected})||"unknown",Ut=de[An]||{name:"Unknown",detected:!1};var ke=new Set;function Nn(e){if(ke.has(e))return;ke.add(e);let n=re();console.warn(`[env] warning: "${e}" is encrypted but no usable private key was found for the "${n}" environment; falling back to its default. Set DOTENV_PRIVATE_KEY_${n.toUpperCase()} or ship .env.keys.`)}var hn={get:(e,n)=>{let t=e,r=t[n];if(typeof r==="string"&&(r.startsWith("encrypted:")||r.startsWith("enc:"))){let s=Re(r);if(s===void 0){Nn(n),delete t[n];return}t[n]=s,r=s}let i=["_PORT","_TIMEOUT","_TTL","_SIZE","_LIMIT","_MAX","_MIN","_INTERVAL","_RETRIES","_CONCURRENCY","_WORKERS","_CONNECTIONS"];if(typeof r==="string"&&/^\d+$/.test(r)&&!r.startsWith("0")&&i.some((s)=>n.endsWith(s)))return Number(r);if(typeof r==="string"){let s=r.toLowerCase();if(s==="true")return!0;if(s==="false")return!1}return r},set:(e,n,t)=>Reflect.set(e,n,t,e),deleteProperty:(e,n)=>Reflect.deleteProperty(e,n)};function Pn(){return typeof Bun<"u"?Bun.env:Sn.env}var Ue=new Proxy(Pn(),hn);function Yt(e,n,t){let r=t?.path||In(".env"),s=xe.readFileSync(r,"utf-8").split(`
34
+ `),o=s.findIndex((c)=>c.startsWith(`${e}=`)),a=/[\s"'#$\\]/.test(n)?`"${n.replace(/"/g,"\\\"")}"`:n;if(o!==-1)s[o]=`${e}=${a}`;else s.push(`${e}=${a}`);xe.writeFileSync(r,s.join(`
35
+ `))}function Wt(e=Ue){let n=[],t=e;for(let[r,i]of Object.entries(Oe)){let s=t[r];if(s!==void 0&&s!==""&&!i.includes(String(s)))n.push(`${r}="${s}" is not valid. Allowed values: ${i.join(", ")}`)}return n}function Gt(e,n=Ue){let t=[],r=n;for(let i of e){let s=r[i];if(s===void 0||s===""||s===null)t.push(i)}if(t.length>0)throw Error(`[env] Missing required environment variable(s): ${t.join(", ")}. Set them in .env or your process environment before booting.`);return n}export{gn as SECRET_NAME_PATTERN,re as activeEnvName,we as aesDecrypt,Cn as aesEncrypt,M as appEnv,zn as autoLoadEnv,un as declaredEnvPublicKey,ln as decryptEnv,Re as decryptEnvValue,K as decryptValue,et as defineEnv,ut as encryptEnv,W as encryptValue,Ue as env,Oe as envEnum,Ne as envHasCiphertext,x as envKeyNames,dn as envPlugin,se as envPrivateKeyNames,De as envPublicKeyNames,Rt as foreignTenantKeys,U as generateKeypair,lt as getEnv,Et as getKeypair,G as getPrivateKey,Ke as hasTTY,Pt as hasWindow,be as isBun,vn as isCI,kt as isColorSupported,xt as isDebug,Ct as isDevelopment,pn as isEncryptedValue,bn as isLegacyEncryptedValue,ht as isLinux,Dt as isLocal,rt as isLocalDeployment,cn as isLoopbackHost,Nt as isMacOS,On as isMinimal,Le as isNode,Lt as isProduction,bt as isStaging,Kt as isTesting,It as isWindows,ve as loadEnv,Fn as loadEnvFiles,Ln as migrateEncryptedValue,h as parse,Kn as parseEnvFromKey,mn as partitionTenantEnv,_t as plaintextSecrets,ce as platform,Pn as process,An as provider,Ut as providerInfo,Gt as requireEnv,qn as resetPrivateKeyCache,at as resolveEnvFile,on as resolvePrivateKey,fn as reusableEnvPublicKey,pt as rotateKeypair,Rn as runtime,St as runtimeInfo,ft as setEnv,Tt as stripForeignTenantEnv,Ce as tenantEnvPrefix,yt as trackedEnvFiles,Wt as validateEnv,Yt as writeEnv};
package/dist/parser.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // @bun
2
- import{spawnSync as Y}from"child_process";import{readFileSync as X}from"fs";import{arch as j,hostname as U,platform as O,release as H,userInfo as z}from"os";import{basename as M,dirname as q,resolve as G}from"path";import{createCipheriv as k,createDecipheriv as b,createHash as m,createPrivateKey as T,createPublicKey as K,diffieHellman as B,generateKeyPairSync as P,hkdfSync as _,randomBytes as y}from"crypto";var E="x25519-public:",w="x25519-private:",g="encrypted:v2:",v="encrypted:",S=Buffer.from("stacks-env:v2","utf8"),R="Environment decryption failed: authentication or format error";function V(t){if(t.length===32)return t;return m("sha256").update(t).digest()}function ie(t,r){let e=y(16),n=k("aes-256-gcm",V(r),e);return{ciphertext:Buffer.concat([n.update(t,"utf8"),n.final()]).toString("hex"),iv:e.toString("hex"),authTag:n.getAuthTag().toString("hex")}}function D(t,r,e,n){try{let i=b("aes-256-gcm",V(r),Buffer.from(e,"hex"));return i.setAuthTag(Buffer.from(n,"hex")),Buffer.concat([i.update(Buffer.from(t,"hex")),i.final()]).toString("utf8")}catch{throw Error("Decryption failed: authentication or format error")}}function l(t){return t.toString("base64url")}function d(t,r,e){if(typeof t!=="string"||t.length>0&&!/^[A-Za-z0-9_-]+$/.test(t))throw Error(`${r} is not canonical base64url`);let n=Buffer.from(t,"base64url");if(l(n)!==t)throw Error(`${r} is not canonical base64url`);if(e!==void 0&&n.length!==e)throw Error(`${r} has an invalid length`);return n}function A(t){if(!t.startsWith(E))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let r=K({key:d(t.slice(E.length),"public key"),format:"der",type:"spki"});if(r.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return r}function W(t){if(!t.startsWith(w))throw Error("Private key is not a version 2 X25519 key");let r=T({key:d(t.slice(w.length),"private key"),format:"der",type:"pkcs8"});if(r.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return r}function N(t){return Buffer.from(`stacks-env:v2;${t.epk};${t.salt};${t.nonce}`,"utf8")}function se(){let{publicKey:t,privateKey:r}=P("x25519"),e=t.export({format:"der",type:"spki"}),n=r.export({format:"der",type:"pkcs8"});return{publicKey:`${E}${l(e)}`,privateKey:`${w}${l(n)}`}}function C(t,r){let e=A(r),n=P("x25519"),i=l(n.publicKey.export({format:"der",type:"spki"})),s=y(16),a=y(12),c=B({privateKey:n.privateKey,publicKey:e}),f=Buffer.from(_("sha256",c,s,S,32)),u={epk:i,salt:l(s),nonce:l(a)};try{let o=k("aes-256-gcm",f,a);o.setAAD(N(u));let p=Buffer.concat([o.update(t,"utf8"),o.final()]),h={v:2,...u,ciphertext:l(p),tag:l(o.getAuthTag())};return`${g}${l(Buffer.from(JSON.stringify(h),"utf8"))}`}finally{c.fill(0),f.fill(0)}}function I(t){let r=d(t.slice(g.length),"envelope").toString("utf8"),e=JSON.parse(r);if(!e||typeof e!=="object"||Array.isArray(e))throw Error("envelope is not an object");let n=Object.keys(e).sort(),i=["ciphertext","epk","nonce","salt","tag","v"];if(n.length!==i.length||n.some((s,a)=>s!==i[a]))throw Error("envelope fields are invalid");if(e.v!==2)throw Error("envelope version is unsupported");for(let s of["epk","salt","nonce","ciphertext","tag"])if(typeof e[s]!=="string")throw Error(`${s} is invalid`);return e}function F(t,r){try{let e=I(t),n=W(r),i=K({key:d(e.epk,"ephemeral public key"),format:"der",type:"spki"});if(i.asymmetricKeyType!=="x25519")throw Error("ephemeral key is not X25519");let s=d(e.salt,"salt",16),a=d(e.nonce,"nonce",12),c=d(e.ciphertext,"ciphertext"),f=d(e.tag,"tag",16),u=B({privateKey:n,publicKey:i}),o=Buffer.from(_("sha256",u,s,S,32));try{let p=b("aes-256-gcm",o,a);return p.setAAD(N(e)),p.setAuthTag(f),Buffer.concat([p.update(c),p.final()]).toString("utf8")}finally{u.fill(0),o.fill(0)}}catch{throw Error(R)}}function L(t,r){try{if(!/^[0-9a-f]{64}$/.test(r))throw Error("invalid legacy private key");let e=Buffer.from(t.slice(v.length),"base64");if(e.length<32)throw Error("invalid legacy payload");let n=m("sha256").update(Buffer.from(r,"hex")).digest(),i=m("sha256").update(n).digest();return D(e.subarray(32).toString("hex"),i,e.subarray(0,16).toString("hex"),e.subarray(16,32).toString("hex"))}catch{throw Error(R)}}function oe(t){return t.startsWith(v)&&!t.startsWith(g)}function x(t,r){if(!t.startsWith(v))return t;return t.startsWith(g)?F(t,r):L(t,r)}function ae(t,r,e){return C(x(t,r),e)}function ce(t){let r=t.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return r&&r[1]?r[1].toLowerCase():""}function fe(t=""){return t?process.env[`DOTENV_PRIVATE_KEY_${t.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}function J(t,r={}){let e={},n=[],i=[],s=t.split(`
3
- `);for(let a of s){let c=a.trim();if(!c||c.startsWith("#"))continue;if(c.startsWith("DOTENV_PUBLIC_KEY=")){let p=c.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(p&&p[1]!==void 0)e.DOTENV_PUBLIC_KEY=p[1];continue}let f=c.match(/^([^=]+)=(.*)$/);if(!f||f[1]===void 0||f[2]===void 0)continue;let u=f[1].trim(),o=f[2].trim();if(o.startsWith('"')&&o.endsWith('"')||o.startsWith("'")&&o.endsWith("'")){if(o=o.slice(1,-1),o.includes("\\n"))o=o.replace(/\\n/g,`
4
- `)}if(o.startsWith("encrypted:")||o.startsWith("enc:")){if(!r.privateKey){i.push(u);continue}try{let p=o.startsWith("enc:")?`encrypted:${o.slice(4)}`:o;o=x(p,r.privateKey)}catch(p){n.push(`Failed to decrypt ${u}: ${p instanceof Error?p.message:"Unknown error"}`)}}o=Z(o,{...r.processEnv||process.env,...e}),o=re(o),e[u]=o}return{parsed:e,errors:n,skippedEncrypted:i}}function Z(t,r){return t.replace(/\$\{([^}]+)\}/g,(e,n)=>{let i=n.match(/^([^:\-+]+)(:-|-)(.+)$/);if(i){let[,a,c,f]=i,u=r[a];if(c===":-")return u||f;else return u!==void 0?u:f}let s=n.match(/^([^:\-+]+)(:?\+)(.+)$/);if(s){let[,a,c,f]=s,u=r[a];if(c===":+")return u?f:"";else return u!==void 0?f:""}return r[n]||""})}var Q=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function ee(t){let r=[],e="",n=null,i=!1;for(let s of t.trim()){if(i){e+=s,i=!1;continue}if(s==="\\"&&n!=="'"){i=!0;continue}if(n){if(s===n)n=null;else e+=s;continue}if(s==='"'||s==="'"){n=s;continue}if(/\s/.test(s)){if(e)r.push(e),e="";continue}e+=s}if(i||n)throw Error("Command substitution contains an unterminated escape or quote");if(e)r.push(e);return r}function te(t){let[r,...e]=t,n=e.some((i)=>i.startsWith("-"));if(r==="echo"&&!n)return e.join(" ");if(r==="printf"&&e.length===1&&!e[0]?.includes("%"))return e[0];if(r==="hostname"&&e.length===0)return U();if(r==="whoami"&&e.length===0)return z().username;if(r==="pwd"&&e.length===0)return process.cwd();if(r==="basename"&&e.length===1)return M(e[0]??"");if(r==="dirname"&&e.length===1)return q(e[0]??"");if(r==="cat"&&e.length>0&&!n)return e.map((i)=>X(G(process.cwd(),i),"utf8")).join("");if(r==="uname"){let i=e[0]??"-s";if(e.length<=1&&i==="-m")return j();if(e.length<=1&&i==="-r")return H();if(e.length<=1&&i==="-s")return{aix:"AIX",android:"Android",cygwin:"CYGWIN",darwin:"Darwin",freebsd:"FreeBSD",haiku:"Haiku",linux:"Linux",netbsd:"NetBSD",openbsd:"OpenBSD",sunos:"SunOS",win32:"Windows_NT"}[O()]??O()}return}function re(t){return t.replace(/\$\(([^)]+)\)/g,(r,e)=>{try{let n=ee(e),i=n[0];if(!i||!Q.has(i))return console.warn(`[env] Blocked command substitution for disallowed command: ${i}`),"";let s=te(n);if(s!==void 0)return s.trim();let a=Y(i,n.slice(1),{encoding:"utf8",env:process.env});if(a.status===0)return a.stdout.trim();let c=a.stderr.trim();console.warn(`[env] Command substitution failed (exit code ${a.status??"unknown"}): ${i}${c?`: ${c}`:""}`)}catch(n){console.warn(`[env] Command substitution error: ${n instanceof Error?n.message:String(n)}`)}return""})}async function me(t,r={}){let e={},n=[],i=[];for(let s of t)try{let a=Bun.file(s);if(!a.size)continue;let c=await a.text(),{parsed:f,errors:u,skippedEncrypted:o}=J(c,r);n.push(...u),i.push(...o);for(let[p,h]of Object.entries(f)){if(!r.overload&&e[p]!==void 0)continue;e[p]=h}}catch(a){if(a?.code==="ENOENT")continue;n.push(`Failed to read ${s}: ${a instanceof Error?a.message:String(a)}`)}return{parsed:e,errors:n,skippedEncrypted:i}}export{me as loadEnvFiles,J as parse};
2
+ import{spawnSync as X}from"child_process";import{readFileSync as U}from"fs";import{arch as H,hostname as z,platform as T,release as M,userInfo as q}from"os";import{basename as G,dirname as J,resolve as Z}from"path";import{createCipheriv as k,createDecipheriv as b,createHash as m,createPrivateKey as D,createPublicKey as K,diffieHellman as B,generateKeyPairSync as P,hkdfSync as _,randomBytes as y}from"crypto";var E="x25519-public:",w="x25519-private:",h="encrypted:v2:",v="encrypted:",R=Buffer.from("stacks-env:v2","utf8"),S="Environment decryption failed: authentication or format error";function O(e){if(e.length===32)return e;return m("sha256").update(e).digest()}function ot(e,r){let t=y(16),n=k("aes-256-gcm",O(r),t);return{ciphertext:Buffer.concat([n.update(e,"utf8"),n.final()]).toString("hex"),iv:t.toString("hex"),authTag:n.getAuthTag().toString("hex")}}function W(e,r,t,n){try{let i=b("aes-256-gcm",O(r),Buffer.from(t,"hex"));return i.setAuthTag(Buffer.from(n,"hex")),Buffer.concat([i.update(Buffer.from(e,"hex")),i.final()]).toString("utf8")}catch{throw Error("Decryption failed: authentication or format error")}}function d(e){return e.toString("base64url")}function g(e,r,t){if(typeof e!=="string"||e.length>0&&!/^[A-Za-z0-9_-]+$/.test(e))throw Error(`${r} is not canonical base64url`);let n=Buffer.from(e,"base64url");if(d(n)!==e)throw Error(`${r} is not canonical base64url`);if(t!==void 0&&n.length!==t)throw Error(`${r} has an invalid length`);return n}function A(e){if(!e.startsWith(E))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let r=K({key:g(e.slice(E.length),"public key"),format:"der",type:"spki"});if(r.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return r}function C(e){if(!e.startsWith(w))throw Error("Private key is not a version 2 X25519 key");let r=D({key:g(e.slice(w.length),"private key"),format:"der",type:"pkcs8"});if(r.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return r}function V(e){return Buffer.from(`stacks-env:v2;${e.epk};${e.salt};${e.nonce}`,"utf8")}function at(){let{publicKey:e,privateKey:r}=P("x25519"),t=e.export({format:"der",type:"spki"}),n=r.export({format:"der",type:"pkcs8"});return{publicKey:`${E}${d(t)}`,privateKey:`${w}${d(n)}`}}function I(e,r){let t=A(r),n=P("x25519"),i=d(n.publicKey.export({format:"der",type:"spki"})),s=y(16),a=y(12),u=B({privateKey:n.privateKey,publicKey:t}),f=Buffer.from(_("sha256",u,s,R,32)),c={epk:i,salt:d(s),nonce:d(a)};try{let p=k("aes-256-gcm",f,a);p.setAAD(V(c));let o=Buffer.concat([p.update(e,"utf8"),p.final()]),l={v:2,...c,ciphertext:d(o),tag:d(p.getAuthTag())};return`${h}${d(Buffer.from(JSON.stringify(l),"utf8"))}`}finally{u.fill(0),f.fill(0)}}function F(e){let r=g(e.slice(h.length),"envelope").toString("utf8"),t=JSON.parse(r);if(!t||typeof t!=="object"||Array.isArray(t))throw Error("envelope is not an object");let n=Object.keys(t).sort(),i=["ciphertext","epk","nonce","salt","tag","v"];if(n.length!==i.length||n.some((s,a)=>s!==i[a]))throw Error("envelope fields are invalid");if(t.v!==2)throw Error("envelope version is unsupported");for(let s of["epk","salt","nonce","ciphertext","tag"])if(typeof t[s]!=="string")throw Error(`${s} is invalid`);return t}function L(e,r){try{let t=F(e),n=C(r),i=K({key:g(t.epk,"ephemeral public key"),format:"der",type:"spki"});if(i.asymmetricKeyType!=="x25519")throw Error("ephemeral key is not X25519");let s=g(t.salt,"salt",16),a=g(t.nonce,"nonce",12),u=g(t.ciphertext,"ciphertext"),f=g(t.tag,"tag",16),c=B({privateKey:n,publicKey:i}),p=Buffer.from(_("sha256",c,s,R,32));try{let o=b("aes-256-gcm",p,a);return o.setAAD(V(t)),o.setAuthTag(f),Buffer.concat([o.update(u),o.final()]).toString("utf8")}finally{c.fill(0),p.fill(0)}}catch{throw Error(S)}}function Y(e,r){try{if(!/^[0-9a-f]{64}$/.test(r))throw Error("invalid legacy private key");let t=Buffer.from(e.slice(v.length),"base64");if(t.length<32)throw Error("invalid legacy payload");let n=m("sha256").update(Buffer.from(r,"hex")).digest(),i=m("sha256").update(n).digest();return W(t.subarray(32).toString("hex"),i,t.subarray(0,16).toString("hex"),t.subarray(16,32).toString("hex"))}catch{throw Error(S)}}function ct(e){return e.startsWith(v)&&!e.startsWith(h)}function x(e,r){if(!e.startsWith(v))return e;return e.startsWith(h)?L(e,r):Y(e,r)}function ft(e,r,t){return I(x(e,r),t)}function ut(e){let r=e.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return r&&r[1]?r[1].toLowerCase():""}function pt(e=""){return e?process.env[`DOTENV_PRIVATE_KEY_${e.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}function j(e){return typeof e==="string"&&(e.startsWith("encrypted:")||e.startsWith("enc:"))}function N(e,r=Object.keys(e)){let t=r.map((n)=>e[n]).filter((n)=>j(n));return Object.fromEntries(Object.entries(e).filter(([,n])=>n===void 0||!t.some((i)=>n.includes(i))))}function Q(e,r={}){let t={},n=[],i=[],s=e.split(`
3
+ `),a=N(r.processEnv||process.env);for(let u of s){let f=u.trim();if(!f||f.startsWith("#"))continue;if(f.startsWith("DOTENV_PUBLIC_KEY=")){let l=f.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(l&&l[1]!==void 0)t.DOTENV_PUBLIC_KEY=l[1];continue}let c=f.match(/^([^=]+)=(.*)$/);if(!c||c[1]===void 0||c[2]===void 0)continue;let p=c[1].trim(),o=c[2].trim();if(o.startsWith('"')&&o.endsWith('"')||o.startsWith("'")&&o.endsWith("'")){if(o=o.slice(1,-1),o.includes("\\n"))o=o.replace(/\\n/g,`
4
+ `)}if(o.startsWith("encrypted:")||o.startsWith("enc:")){if(!r.privateKey){i.push(p);continue}try{let l=o.startsWith("enc:")?`encrypted:${o.slice(4)}`:o;o=x(l,r.privateKey)}catch(l){n.push(`Failed to decrypt ${p}: ${l instanceof Error?l.message:"Unknown error"}`),i.push(p);continue}}o=tt(o,{...a,...t}),o=it(o),t[p]=o}return{parsed:t,errors:n,skippedEncrypted:i}}function tt(e,r){return e.replace(/\$\{([^}]+)\}/g,(t,n)=>{let i=n.match(/^([^:\-+]+)(:-|-)(.+)$/);if(i){let[,a,u,f]=i,c=r[a];if(u===":-")return c||f;else return c!==void 0?c:f}let s=n.match(/^([^:\-+]+)(:?\+)(.+)$/);if(s){let[,a,u,f]=s,c=r[a];if(u===":+")return c?f:"";else return c!==void 0?f:""}return r[n]||""})}var et=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function rt(e){let r=[],t="",n=null,i=!1;for(let s of e.trim()){if(i){t+=s,i=!1;continue}if(s==="\\"&&n!=="'"){i=!0;continue}if(n){if(s===n)n=null;else t+=s;continue}if(s==='"'||s==="'"){n=s;continue}if(/\s/.test(s)){if(t)r.push(t),t="";continue}t+=s}if(i||n)throw Error("Command substitution contains an unterminated escape or quote");if(t)r.push(t);return r}function nt(e){let[r,...t]=e,n=t.some((i)=>i.startsWith("-"));if(r==="echo"&&!n)return t.join(" ");if(r==="printf"&&t.length===1&&!t[0]?.includes("%"))return t[0];if(r==="hostname"&&t.length===0)return z();if(r==="whoami"&&t.length===0)return q().username;if(r==="pwd"&&t.length===0)return process.cwd();if(r==="basename"&&t.length===1)return G(t[0]??"");if(r==="dirname"&&t.length===1)return J(t[0]??"");if(r==="cat"&&t.length>0&&!n)return t.map((i)=>U(Z(process.cwd(),i),"utf8")).join("");if(r==="uname"){let i=t[0]??"-s";if(t.length<=1&&i==="-m")return H();if(t.length<=1&&i==="-r")return M();if(t.length<=1&&i==="-s")return{aix:"AIX",android:"Android",cygwin:"CYGWIN",darwin:"Darwin",freebsd:"FreeBSD",haiku:"Haiku",linux:"Linux",netbsd:"NetBSD",openbsd:"OpenBSD",sunos:"SunOS",win32:"Windows_NT"}[T()]??T()}return}function it(e){return e.replace(/\$\(([^)]+)\)/g,(r,t)=>{try{let n=rt(t),i=n[0];if(!i||!et.has(i))return console.warn(`[env] Blocked command substitution for disallowed command: ${i}`),"";let s=nt(n);if(s!==void 0)return s.trim();let a=X(i,n.slice(1),{encoding:"utf8",env:process.env});if(a.status===0)return a.stdout.trim();let u=a.stderr.trim();console.warn(`[env] Command substitution failed (exit code ${a.status??"unknown"}): ${i}${u?`: ${u}`:""}`)}catch(n){console.warn(`[env] Command substitution error: ${n instanceof Error?n.message:String(n)}`)}return""})}async function vt(e,r={}){let t={},n=[],i=[];for(let s of e)try{let a=Bun.file(s);if(!a.size)continue;let u=await a.text(),{parsed:f,errors:c,skippedEncrypted:p}=Q(u,r);n.push(...c),i.push(...p);for(let[o,l]of Object.entries(f)){if(!r.overload&&t[o]!==void 0)continue;t[o]=l}}catch(a){if(a?.code==="ENOENT")continue;n.push(`Failed to read ${s}: ${a instanceof Error?a.message:String(a)}`)}return{parsed:t,errors:n,skippedEncrypted:i}}export{vt as loadEnvFiles,Q as parse};
@@ -0,0 +1,7 @@
1
+ /** Ciphertext preloaded by Bun is not a usable environment value. */
2
+ export declare function isEncryptedValue(value: string | undefined): boolean;
3
+ /**
4
+ * Keep genuine plaintext, excluding copies of known ciphertext that Bun may
5
+ * have interpolated into another variable before the framework loader runs.
6
+ */
7
+ export declare function plaintextEnv(values: Record<string, string | undefined>, encryptedKeys?: readonly string[]): Record<string, string | undefined>;
package/dist/plugin.d.ts CHANGED
@@ -9,8 +9,8 @@ export declare function activeEnvName(options?: { env?: string }): string;
9
9
  * Resolve the dotenvx private key for the active environment, checking
10
10
  * process.env (`DOTENV_PRIVATE_KEY_<ENV>` then `DOTENV_PRIVATE_KEY`) and
11
11
  * finally a local `.env.keys` file. The result — key or `undefined` — is
12
- * cached per environment so the env proxy can call this on every encrypted
13
- * read without repeatedly touching disk.
12
+ * cached per environment and resolved project root so the env proxy can read
13
+ * encrypted values without repeatedly touching disk.
14
14
  */
15
15
  export declare function resolvePrivateKey(options?: { env?: string, cwd?: string }): string | undefined;
16
16
  /**
package/dist/plugin.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // @bun
2
- import{existsSync as T}from"fs";import{readFileSync as Y}from"fs";import{resolve as D}from"path";import{createCipheriv as U,createDecipheriv as j,createHash as S,createPrivateKey as ne,createPublicKey as X,diffieHellman as z,generateKeyPairSync as H,hkdfSync as q,randomBytes as I}from"crypto";var C="x25519-public:",F="x25519-private:",N="encrypted:v2:",W="encrypted:",M=Buffer.from("stacks-env:v2","utf8"),G="Environment decryption failed: authentication or format error";function J(e){if(e.length===32)return e;return S("sha256").update(e).digest()}function Ne(e,t){let n=I(16),r=U("aes-256-gcm",J(t),n);return{ciphertext:Buffer.concat([r.update(e,"utf8"),r.final()]).toString("hex"),iv:n.toString("hex"),authTag:r.getAuthTag().toString("hex")}}function re(e,t,n,r){try{let s=j("aes-256-gcm",J(t),Buffer.from(n,"hex"));return s.setAuthTag(Buffer.from(r,"hex")),Buffer.concat([s.update(Buffer.from(e,"hex")),s.final()]).toString("utf8")}catch{throw Error("Decryption failed: authentication or format error")}}function h(e){return e.toString("base64url")}function m(e,t,n){if(typeof e!=="string"||e.length>0&&!/^[A-Za-z0-9_-]+$/.test(e))throw Error(`${t} is not canonical base64url`);let r=Buffer.from(e,"base64url");if(h(r)!==e)throw Error(`${t} is not canonical base64url`);if(n!==void 0&&r.length!==n)throw Error(`${t} has an invalid length`);return r}function se(e){if(!e.startsWith(C))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let t=X({key:m(e.slice(C.length),"public key"),format:"der",type:"spki"});if(t.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return t}function ie(e){if(!e.startsWith(F))throw Error("Private key is not a version 2 X25519 key");let t=ne({key:m(e.slice(F.length),"private key"),format:"der",type:"pkcs8"});if(t.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return t}function Z(e){return Buffer.from(`stacks-env:v2;${e.epk};${e.salt};${e.nonce}`,"utf8")}function Oe(){let{publicKey:e,privateKey:t}=H("x25519"),n=e.export({format:"der",type:"spki"}),r=t.export({format:"der",type:"pkcs8"});return{publicKey:`${C}${h(n)}`,privateKey:`${F}${h(r)}`}}function oe(e,t){let n=se(t),r=H("x25519"),s=h(r.publicKey.export({format:"der",type:"spki"})),i=I(16),c=I(12),a=z({privateKey:r.privateKey,publicKey:n}),u=Buffer.from(q("sha256",a,i,M,32)),f={epk:s,salt:h(i),nonce:h(c)};try{let o=U("aes-256-gcm",u,c);o.setAAD(Z(f));let p=Buffer.concat([o.update(e,"utf8"),o.final()]),l={v:2,...f,ciphertext:h(p),tag:h(o.getAuthTag())};return`${N}${h(Buffer.from(JSON.stringify(l),"utf8"))}`}finally{a.fill(0),u.fill(0)}}function ce(e){let t=m(e.slice(N.length),"envelope").toString("utf8"),n=JSON.parse(t);if(!n||typeof n!=="object"||Array.isArray(n))throw Error("envelope is not an object");let r=Object.keys(n).sort(),s=["ciphertext","epk","nonce","salt","tag","v"];if(r.length!==s.length||r.some((i,c)=>i!==s[c]))throw Error("envelope fields are invalid");if(n.v!==2)throw Error("envelope version is unsupported");for(let i of["epk","salt","nonce","ciphertext","tag"])if(typeof n[i]!=="string")throw Error(`${i} is invalid`);return n}function ae(e,t){try{let n=ce(e),r=ie(t),s=X({key:m(n.epk,"ephemeral public key"),format:"der",type:"spki"});if(s.asymmetricKeyType!=="x25519")throw Error("ephemeral key is not X25519");let i=m(n.salt,"salt",16),c=m(n.nonce,"nonce",12),a=m(n.ciphertext,"ciphertext"),u=m(n.tag,"tag",16),f=z({privateKey:r,publicKey:s}),o=Buffer.from(q("sha256",f,i,M,32));try{let p=j("aes-256-gcm",o,c);return p.setAAD(Z(n)),p.setAuthTag(u),Buffer.concat([p.update(a),p.final()]).toString("utf8")}finally{f.fill(0),o.fill(0)}}catch{throw Error(G)}}function fe(e,t){try{if(!/^[0-9a-f]{64}$/.test(t))throw Error("invalid legacy private key");let n=Buffer.from(e.slice(W.length),"base64");if(n.length<32)throw Error("invalid legacy payload");let r=S("sha256").update(Buffer.from(t,"hex")).digest(),s=S("sha256").update(r).digest();return re(n.subarray(32).toString("hex"),s,n.subarray(0,16).toString("hex"),n.subarray(16,32).toString("hex"))}catch{throw Error(G)}}function Te(e){return e.startsWith(W)&&!e.startsWith(N)}function k(e,t){if(!e.startsWith(W))return e;return e.startsWith(N)?ae(e,t):fe(e,t)}function De(e,t,n){return oe(k(e,t),n)}function Be(e){let t=e.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return t&&t[1]?t[1].toLowerCase():""}function O(e=""){return e?process.env[`DOTENV_PRIVATE_KEY_${e.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}import{spawnSync as ue}from"child_process";import{readFileSync as pe}from"fs";import{arch as de,hostname as le,platform as Q,release as ge,userInfo as he}from"os";import{basename as ye,dirname as me,resolve as Ee}from"path";function _(e,t={}){let n={},r=[],s=[],i=e.split(`
3
- `);for(let c of i){let a=c.trim();if(!a||a.startsWith("#"))continue;if(a.startsWith("DOTENV_PUBLIC_KEY=")){let p=a.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(p&&p[1]!==void 0)n.DOTENV_PUBLIC_KEY=p[1];continue}let u=a.match(/^([^=]+)=(.*)$/);if(!u||u[1]===void 0||u[2]===void 0)continue;let f=u[1].trim(),o=u[2].trim();if(o.startsWith('"')&&o.endsWith('"')||o.startsWith("'")&&o.endsWith("'")){if(o=o.slice(1,-1),o.includes("\\n"))o=o.replace(/\\n/g,`
4
- `)}if(o.startsWith("encrypted:")||o.startsWith("enc:")){if(!t.privateKey){s.push(f);continue}try{let p=o.startsWith("enc:")?`encrypted:${o.slice(4)}`:o;o=k(p,t.privateKey)}catch(p){r.push(`Failed to decrypt ${f}: ${p instanceof Error?p.message:"Unknown error"}`)}}o=ve(o,{...t.processEnv||process.env,...n}),o=xe(o),n[f]=o}return{parsed:n,errors:r,skippedEncrypted:s}}function ve(e,t){return e.replace(/\$\{([^}]+)\}/g,(n,r)=>{let s=r.match(/^([^:\-+]+)(:-|-)(.+)$/);if(s){let[,c,a,u]=s,f=t[c];if(a===":-")return f||u;else return f!==void 0?f:u}let i=r.match(/^([^:\-+]+)(:?\+)(.+)$/);if(i){let[,c,a,u]=i,f=t[c];if(a===":+")return f?u:"";else return f!==void 0?u:""}return t[r]||""})}var we=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function ke(e){let t=[],n="",r=null,s=!1;for(let i of e.trim()){if(s){n+=i,s=!1;continue}if(i==="\\"&&r!=="'"){s=!0;continue}if(r){if(i===r)r=null;else n+=i;continue}if(i==='"'||i==="'"){r=i;continue}if(/\s/.test(i)){if(n)t.push(n),n="";continue}n+=i}if(s||r)throw Error("Command substitution contains an unterminated escape or quote");if(n)t.push(n);return t}function _e(e){let[t,...n]=e,r=n.some((s)=>s.startsWith("-"));if(t==="echo"&&!r)return n.join(" ");if(t==="printf"&&n.length===1&&!n[0]?.includes("%"))return n[0];if(t==="hostname"&&n.length===0)return le();if(t==="whoami"&&n.length===0)return he().username;if(t==="pwd"&&n.length===0)return process.cwd();if(t==="basename"&&n.length===1)return ye(n[0]??"");if(t==="dirname"&&n.length===1)return me(n[0]??"");if(t==="cat"&&n.length>0&&!r)return n.map((s)=>pe(Ee(process.cwd(),s),"utf8")).join("");if(t==="uname"){let s=n[0]??"-s";if(n.length<=1&&s==="-m")return de();if(n.length<=1&&s==="-r")return ge();if(n.length<=1&&s==="-s")return{aix:"AIX",android:"Android",cygwin:"CYGWIN",darwin:"Darwin",freebsd:"FreeBSD",haiku:"Haiku",linux:"Linux",netbsd:"NetBSD",openbsd:"OpenBSD",sunos:"SunOS",win32:"Windows_NT"}[Q()]??Q()}return}function xe(e){return e.replace(/\$\(([^)]+)\)/g,(t,n)=>{try{let r=ke(n),s=r[0];if(!s||!we.has(s))return console.warn(`[env] Blocked command substitution for disallowed command: ${s}`),"";let i=_e(r);if(i!==void 0)return i.trim();let c=ue(s,r.slice(1),{encoding:"utf8",env:process.env});if(c.status===0)return c.stdout.trim();let a=c.stderr.trim();console.warn(`[env] Command substitution failed (exit code ${c.status??"unknown"}): ${s}${a?`: ${a}`:""}`)}catch(r){console.warn(`[env] Command substitution error: ${r instanceof Error?r.message:String(r)}`)}return""})}async function We(e,t={}){let n={},r=[],s=[];for(let i of e)try{let c=Bun.file(i);if(!c.size)continue;let a=await c.text(),{parsed:u,errors:f,skippedEncrypted:o}=_(a,t);r.push(...f),s.push(...o);for(let[p,l]of Object.entries(u)){if(!t.overload&&n[p]!==void 0)continue;n[p]=l}}catch(c){if(c?.code==="ENOENT")continue;r.push(`Failed to read ${i}: ${c instanceof Error?c.message:String(c)}`)}return{parsed:n,errors:r,skippedEncrypted:s}}function x(e){return typeof e==="string"&&(e.startsWith("encrypted:")||e.startsWith("enc:"))}function E(e){if(!e||x(e))return;let t=e.trim().toLowerCase();if(!/^[a-z0-9_-]+$/.test(t))return;if(t==="prod")return"production";if(t==="stage")return"staging";if(t==="dev")return"development";return t}var B=null,A,R;function Pe(e,t,n=".env.keys"){let r=D(t,n);if(!T(r))return;try{let{parsed:s}=_(Y(r,"utf-8"));if(e){let i=s[`DOTENV_PRIVATE_KEY_${e.toUpperCase()}`];if(i)return i}return s.DOTENV_PRIVATE_KEY}catch{return}}function be(e={}){return E(e.env)||R||E("development")||E(process.env.DOTENV_ENV)||E(process.env.APP_ENV)||"development"}function Ke(e={}){let t=be(e),n=e.cwd||process.cwd();if(B===t)return A;let r=O(t);if(!r)r=process.env.DOTENV_PRIVATE_KEY;if(!r)r=Pe(t,n);return B=t,A=r,r}function He(){B=null,A=void 0,R=void 0}function qe(e,t={}){if(!x(e))return e;let n=Ke(t);if(!n)return;try{let r=e.startsWith("enc:")?`encrypted:${e.slice(4)}`:e;return k(r,n)}catch{return}}function ee(e={}){let{path:t=[".env"],overload:n=!1,env:r,privateKey:s,keysFile:i=".env.keys",quiet:c=!1,cwd:a=process.cwd()}=e,u=Array.isArray(t)?t:[t],f=[],o=0,p=new Set(Object.entries(process.env).filter(([,d])=>d!==void 0&&!x(d)).map(([d])=>d)),l=s;if(!l&&i){let d=D(a,i);if(T(d))try{let g=Y(d,"utf-8"),{parsed:y}=_(g);if(r){let P=`DOTENV_PRIVATE_KEY_${r.toUpperCase()}`;l=y[P]||y.DOTENV_PRIVATE_KEY}else l=y.DOTENV_PRIVATE_KEY}catch(g){f.push(`Failed to load keys file: ${g instanceof Error?g.message:"Unknown error"}`)}}if(!l)l=O(r||"")||O();for(let d of u){let g=D(a,d);if(!T(g)){if(!c)f.push(`File not found: ${g}`);continue}try{let y=Y(g,"utf-8"),{parsed:P,errors:te,skippedEncrypted:v}=_(y,{privateKey:l,processEnv:process.env});if(f.push(...te),v.length>0){for(let V of v)if(x(process.env[V]))delete process.env[V];let w=r?`DOTENV_PRIVATE_KEY_${r.toUpperCase()}`:"DOTENV_PRIVATE_KEY",b=v.slice(0,5).join(", "),K=v.length>5?`, \u2026 +${v.length-5} more`:"";console.warn(`[env] warning: skipped ${v.length} encrypted value(s) in ${d} (${w} not set; defaults apply): ${b}${K}`)}let L=0;for(let[w,b]of Object.entries(P)){if(w==="DOTENV_PUBLIC_KEY")continue;let K=process.env[w],V=x(K)&&b!==K;if(n||!p.has(w)||V)process.env[w]=b,o++,L++}if(!c&&!process.env.__ENV_LOADED__)console.error(`[env] loaded ${L}/${Object.keys(P).length} variables from ${d}`),process.env.__ENV_LOADED__="1"}catch(y){f.push(`Failed to load ${g}: ${y instanceof Error?y.message:"Unknown error"}`)}}return{loaded:o,errors:f}}function Ve(e={}){return{name:"env-plugin",setup(t){ee(e)}}}function Me(e={}){let t=E(e.env)||E("development")||E(process.env.DOTENV_ENV)||E(process.env.APP_ENV)||"development",n=e.cwd||process.cwd();if(R!==t)R=t,B=null,A=void 0;let r=[],s=(a)=>{if(!r.includes(a)&&T(D(n,a)))r.push(a)};s(".env"),s(".env.local");let i=`.env.${t}`,c=`.env.${t}.local`;return s(i),s(c),ee({...e,path:r,env:t})}var Je=Ve;export{be as activeEnvName,Me as autoLoadEnv,qe as decryptEnvValue,Je as default,Ve as envPlugin,ee as loadEnv,He as resetPrivateKeyCache,Ke as resolvePrivateKey};
2
+ import{existsSync as D}from"fs";import{readFileSync as j}from"fs";import{resolve as b}from"path";import{createCipheriv as z,createDecipheriv as H,createHash as C,createPrivateKey as oe,createPublicKey as q,diffieHellman as M,generateKeyPairSync as G,hkdfSync as J,randomBytes as F}from"crypto";var W="x25519-public:",Y="x25519-private:",N="encrypted:v2:",L="encrypted:",Z=Buffer.from("stacks-env:v2","utf8"),Q="Environment decryption failed: authentication or format error";function ee(e){if(e.length===32)return e;return C("sha256").update(e).digest()}function Be(e,n){let t=F(16),r=z("aes-256-gcm",ee(n),t);return{ciphertext:Buffer.concat([r.update(e,"utf8"),r.final()]).toString("hex"),iv:t.toString("hex"),authTag:r.getAuthTag().toString("hex")}}function ce(e,n,t,r){try{let s=H("aes-256-gcm",ee(n),Buffer.from(t,"hex"));return s.setAuthTag(Buffer.from(r,"hex")),Buffer.concat([s.update(Buffer.from(e,"hex")),s.final()]).toString("utf8")}catch{throw Error("Decryption failed: authentication or format error")}}function h(e){return e.toString("base64url")}function m(e,n,t){if(typeof e!=="string"||e.length>0&&!/^[A-Za-z0-9_-]+$/.test(e))throw Error(`${n} is not canonical base64url`);let r=Buffer.from(e,"base64url");if(h(r)!==e)throw Error(`${n} is not canonical base64url`);if(t!==void 0&&r.length!==t)throw Error(`${n} has an invalid length`);return r}function ae(e){if(!e.startsWith(W))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let n=q({key:m(e.slice(W.length),"public key"),format:"der",type:"spki"});if(n.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return n}function fe(e){if(!e.startsWith(Y))throw Error("Private key is not a version 2 X25519 key");let n=oe({key:m(e.slice(Y.length),"private key"),format:"der",type:"pkcs8"});if(n.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return n}function te(e){return Buffer.from(`stacks-env:v2;${e.epk};${e.salt};${e.nonce}`,"utf8")}function Re(){let{publicKey:e,privateKey:n}=G("x25519"),t=e.export({format:"der",type:"spki"}),r=n.export({format:"der",type:"pkcs8"});return{publicKey:`${W}${h(t)}`,privateKey:`${Y}${h(r)}`}}function ue(e,n){let t=ae(n),r=G("x25519"),s=h(r.publicKey.export({format:"der",type:"spki"})),i=F(16),c=F(12),f=M({privateKey:r.privateKey,publicKey:t}),u=Buffer.from(J("sha256",f,i,Z,32)),a={epk:s,salt:h(i),nonce:h(c)};try{let d=z("aes-256-gcm",u,c);d.setAAD(te(a));let o=Buffer.concat([d.update(e,"utf8"),d.final()]),p={v:2,...a,ciphertext:h(o),tag:h(d.getAuthTag())};return`${N}${h(Buffer.from(JSON.stringify(p),"utf8"))}`}finally{f.fill(0),u.fill(0)}}function pe(e){let n=m(e.slice(N.length),"envelope").toString("utf8"),t=JSON.parse(n);if(!t||typeof t!=="object"||Array.isArray(t))throw Error("envelope is not an object");let r=Object.keys(t).sort(),s=["ciphertext","epk","nonce","salt","tag","v"];if(r.length!==s.length||r.some((i,c)=>i!==s[c]))throw Error("envelope fields are invalid");if(t.v!==2)throw Error("envelope version is unsupported");for(let i of["epk","salt","nonce","ciphertext","tag"])if(typeof t[i]!=="string")throw Error(`${i} is invalid`);return t}function de(e,n){try{let t=pe(e),r=fe(n),s=q({key:m(t.epk,"ephemeral public key"),format:"der",type:"spki"});if(s.asymmetricKeyType!=="x25519")throw Error("ephemeral key is not X25519");let i=m(t.salt,"salt",16),c=m(t.nonce,"nonce",12),f=m(t.ciphertext,"ciphertext"),u=m(t.tag,"tag",16),a=M({privateKey:r,publicKey:s}),d=Buffer.from(J("sha256",a,i,Z,32));try{let o=H("aes-256-gcm",d,c);return o.setAAD(te(t)),o.setAuthTag(u),Buffer.concat([o.update(f),o.final()]).toString("utf8")}finally{a.fill(0),d.fill(0)}}catch{throw Error(Q)}}function le(e,n){try{if(!/^[0-9a-f]{64}$/.test(n))throw Error("invalid legacy private key");let t=Buffer.from(e.slice(L.length),"base64");if(t.length<32)throw Error("invalid legacy payload");let r=C("sha256").update(Buffer.from(n,"hex")).digest(),s=C("sha256").update(r).digest();return ce(t.subarray(32).toString("hex"),s,t.subarray(0,16).toString("hex"),t.subarray(16,32).toString("hex"))}catch{throw Error(Q)}}function Ae(e){return e.startsWith(L)&&!e.startsWith(N)}function k(e,n){if(!e.startsWith(L))return e;return e.startsWith(N)?de(e,n):le(e,n)}function Se(e,n,t){return ue(k(e,n),t)}function Ie(e){let n=e.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return n&&n[1]?n[1].toLowerCase():""}function T(e=""){return e?process.env[`DOTENV_PRIVATE_KEY_${e.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}import{spawnSync as ge}from"child_process";import{readFileSync as he}from"fs";import{arch as ye,hostname as me,platform as ne,release as Ee,userInfo as ve}from"os";import{basename as we,dirname as ke,resolve as xe}from"path";function x(e){return typeof e==="string"&&(e.startsWith("encrypted:")||e.startsWith("enc:"))}function _(e,n=Object.keys(e)){let t=n.map((r)=>e[r]).filter((r)=>x(r));return Object.fromEntries(Object.entries(e).filter(([,r])=>r===void 0||!t.some((s)=>r.includes(s))))}function P(e,n={}){let t={},r=[],s=[],i=e.split(`
3
+ `),c=_(n.processEnv||process.env);for(let f of i){let u=f.trim();if(!u||u.startsWith("#"))continue;if(u.startsWith("DOTENV_PUBLIC_KEY=")){let p=u.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(p&&p[1]!==void 0)t.DOTENV_PUBLIC_KEY=p[1];continue}let a=u.match(/^([^=]+)=(.*)$/);if(!a||a[1]===void 0||a[2]===void 0)continue;let d=a[1].trim(),o=a[2].trim();if(o.startsWith('"')&&o.endsWith('"')||o.startsWith("'")&&o.endsWith("'")){if(o=o.slice(1,-1),o.includes("\\n"))o=o.replace(/\\n/g,`
4
+ `)}if(o.startsWith("encrypted:")||o.startsWith("enc:")){if(!n.privateKey){s.push(d);continue}try{let p=o.startsWith("enc:")?`encrypted:${o.slice(4)}`:o;o=k(p,n.privateKey)}catch(p){r.push(`Failed to decrypt ${d}: ${p instanceof Error?p.message:"Unknown error"}`),s.push(d);continue}}o=_e(o,{...c,...t}),o=Ve(o),t[d]=o}return{parsed:t,errors:r,skippedEncrypted:s}}function _e(e,n){return e.replace(/\$\{([^}]+)\}/g,(t,r)=>{let s=r.match(/^([^:\-+]+)(:-|-)(.+)$/);if(s){let[,c,f,u]=s,a=n[c];if(f===":-")return a||u;else return a!==void 0?a:u}let i=r.match(/^([^:\-+]+)(:?\+)(.+)$/);if(i){let[,c,f,u]=i,a=n[c];if(f===":+")return a?u:"";else return a!==void 0?u:""}return n[r]||""})}var Pe=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function be(e){let n=[],t="",r=null,s=!1;for(let i of e.trim()){if(s){t+=i,s=!1;continue}if(i==="\\"&&r!=="'"){s=!0;continue}if(r){if(i===r)r=null;else t+=i;continue}if(i==='"'||i==="'"){r=i;continue}if(/\s/.test(i)){if(t)n.push(t),t="";continue}t+=i}if(s||r)throw Error("Command substitution contains an unterminated escape or quote");if(t)n.push(t);return n}function Ke(e){let[n,...t]=e,r=t.some((s)=>s.startsWith("-"));if(n==="echo"&&!r)return t.join(" ");if(n==="printf"&&t.length===1&&!t[0]?.includes("%"))return t[0];if(n==="hostname"&&t.length===0)return me();if(n==="whoami"&&t.length===0)return ve().username;if(n==="pwd"&&t.length===0)return process.cwd();if(n==="basename"&&t.length===1)return we(t[0]??"");if(n==="dirname"&&t.length===1)return ke(t[0]??"");if(n==="cat"&&t.length>0&&!r)return t.map((s)=>he(xe(process.cwd(),s),"utf8")).join("");if(n==="uname"){let s=t[0]??"-s";if(t.length<=1&&s==="-m")return ye();if(t.length<=1&&s==="-r")return Ee();if(t.length<=1&&s==="-s")return{aix:"AIX",android:"Android",cygwin:"CYGWIN",darwin:"Darwin",freebsd:"FreeBSD",haiku:"Haiku",linux:"Linux",netbsd:"NetBSD",openbsd:"OpenBSD",sunos:"SunOS",win32:"Windows_NT"}[ne()]??ne()}return}function Ve(e){return e.replace(/\$\(([^)]+)\)/g,(n,t)=>{try{let r=be(t),s=r[0];if(!s||!Pe.has(s))return console.warn(`[env] Blocked command substitution for disallowed command: ${s}`),"";let i=Ke(r);if(i!==void 0)return i.trim();let c=ge(s,r.slice(1),{encoding:"utf8",env:process.env});if(c.status===0)return c.stdout.trim();let f=c.stderr.trim();console.warn(`[env] Command substitution failed (exit code ${c.status??"unknown"}): ${s}${f?`: ${f}`:""}`)}catch(r){console.warn(`[env] Command substitution error: ${r instanceof Error?r.message:String(r)}`)}return""})}async function ze(e,n={}){let t={},r=[],s=[];for(let i of e)try{let c=Bun.file(i);if(!c.size)continue;let f=await c.text(),{parsed:u,errors:a,skippedEncrypted:d}=P(f,n);r.push(...a),s.push(...d);for(let[o,p]of Object.entries(u)){if(!n.overload&&t[o]!==void 0)continue;t[o]=p}}catch(c){if(c?.code==="ENOENT")continue;r.push(`Failed to read ${i}: ${c instanceof Error?c.message:String(c)}`)}return{parsed:t,errors:r,skippedEncrypted:s}}function E(e){if(!e||x(e))return;let n=e.trim().toLowerCase();if(!/^[a-z0-9_-]+$/.test(n))return;if(n==="prod")return"production";if(n==="stage")return"staging";if(n==="dev")return"development";return n}var B=null,R=null,A,S;function $e(e,n,t=".env.keys"){let r=b(n,t);if(!D(r))return;try{let{parsed:s}=P(j(r,"utf-8"));if(e){let i=s[`DOTENV_PRIVATE_KEY_${e.toUpperCase()}`];if(i)return i}return s.DOTENV_PRIVATE_KEY}catch{return}}function Oe(e={}){return E(e.env)||S||E("development")||E(process.env.DOTENV_ENV)||E(process.env.APP_ENV)||"development"}function Ne(e={}){let n=Oe(e),t=b(e.cwd||process.cwd());if(B===n&&R===t)return A;let r=T(n);if(!r)r=process.env.DOTENV_PRIVATE_KEY;if(!r)r=$e(n,t);return B=n,R=t,A=r,r}function et(){B=null,R=null,A=void 0,S=void 0}function tt(e,n={}){if(!x(e))return e;let t=Ne(n);if(!t)return;try{let r=e.startsWith("enc:")?`encrypted:${e.slice(4)}`:e;return k(r,t)}catch{return}}function re(e={}){let{path:n=[".env"],overload:t=!1,env:r,privateKey:s,keysFile:i=".env.keys",quiet:c=!1,cwd:f=process.cwd()}=e,u=Array.isArray(n)?n:[n],a=[],d=0,o=new Set(Object.entries(_(process.env)).filter(([,l])=>l!==void 0).map(([l])=>l)),p=s;if(!p&&i){let l=b(f,i);if(D(l))try{let g=j(l,"utf-8"),{parsed:y}=P(g);if(r){let K=`DOTENV_PRIVATE_KEY_${r.toUpperCase()}`;p=y[K]||y.DOTENV_PRIVATE_KEY}else p=y.DOTENV_PRIVATE_KEY}catch(g){a.push(`Failed to load keys file: ${g instanceof Error?g.message:"Unknown error"}`)}}if(!p)p=T(r||"")||T();for(let l of u){let g=b(f,l);if(!D(g)){if(!c)a.push(`File not found: ${g}`);continue}try{let y=j(g,"utf-8"),{parsed:K,errors:se,skippedEncrypted:v}=P(y,{privateKey:p,processEnv:process.env});if(a.push(...se),v.length>0){let w=_(process.env,v);for(let X of Object.keys(process.env))if(!Object.hasOwn(w,X))delete process.env[X];let V=r?`DOTENV_PRIVATE_KEY_${r.toUpperCase()}`:"DOTENV_PRIVATE_KEY",O=p?"decryption failed":`${V} not set`,I=v.slice(0,5).join(", "),ie=v.length>5?`, \u2026 +${v.length-5} more`:"";console.warn(`[env] warning: skipped ${v.length} encrypted value(s) in ${l} (${O}; existing plaintext or defaults apply): ${I}${ie}`)}let U=0;for(let[w,V]of Object.entries(K)){if(w==="DOTENV_PUBLIC_KEY")continue;let O=process.env[w],I=x(O)&&V!==O;if(t||!o.has(w)||I)process.env[w]=V,d++,U++}if(!c&&!process.env.__ENV_LOADED__)console.error(`[env] loaded ${U}/${Object.keys(K).length} variables from ${l}`),process.env.__ENV_LOADED__="1"}catch(y){a.push(`Failed to load ${g}: ${y instanceof Error?y.message:"Unknown error"}`)}}return{loaded:d,errors:a}}function Te(e={}){return{name:"env-plugin",setup(n){re(e)}}}function nt(e={}){let n=E(e.env)||E("development")||E(process.env.DOTENV_ENV)||E(process.env.APP_ENV)||"development",t=e.cwd||process.cwd();if(S!==n)S=n,B=null,R=null,A=void 0;let r=[],s=(f)=>{if(!r.includes(f)&&D(b(t,f)))r.push(f)};s(".env"),s(".env.local");let i=`.env.${n}`,c=`.env.${n}.local`;return s(i),s(c),re({...e,path:r,env:n})}var st=Te;export{Oe as activeEnvName,nt as autoLoadEnv,tt as decryptEnvValue,st as default,Te as envPlugin,re as loadEnv,et as resetPrivateKeyCache,Ne as resolvePrivateKey};
package/dist/types.d.ts CHANGED
@@ -88,6 +88,7 @@ export declare interface FrameworkEnv {
88
88
  DB_READ_AUTO_ROUTE: boolean | undefined
89
89
  DB_QUERY_LOGGING_ENABLED: boolean | undefined
90
90
  DB_QUERY_LOGGING_CAPTURE_ALL_TRACES: boolean | undefined
91
+ DB_QUERY_LOGGING_CAPTURE_BINDINGS: boolean | undefined
91
92
  DB_QUERY_LOGGING_SLOW_THRESHOLD: number | undefined
92
93
  DB_QUERY_LOGGING_RETENTION_DAYS: number | undefined
93
94
  DB_QUERY_LOGGING_PRUNE_FREQUENCY: number | undefined
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/env",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.74.45",
5
+ "version": "0.74.47",
6
6
  "description": "Stacks env helper methods.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -56,10 +56,10 @@
56
56
  "prepublishOnly": "bun run build"
57
57
  },
58
58
  "dependencies": {
59
- "@stacksjs/path": "0.74.45"
59
+ "@stacksjs/path": "0.74.47"
60
60
  },
61
61
  "devDependencies": {
62
62
  "better-dx": "^0.2.24",
63
- "@stacksjs/validation": "0.74.45"
63
+ "@stacksjs/validation": "0.74.47"
64
64
  }
65
65
  }