@stacksjs/env 0.70.161 → 0.70.162

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
@@ -6,7 +6,7 @@ A secure .env file management package with built-in encryption support for Bun a
6
6
 
7
7
  - 🔐 **Automatic Encryption/Decryption** - Secure your environment variables with public-key cryptography
8
8
  - 🚀 **Bun Plugin** - Seamless integration with Bun's runtime
9
- - 🔑 **secp256k1 ECIES** - Industry-standard elliptic curve encryption
9
+ - 🔑 **Versioned envelope** - X25519 + HKDF-SHA-256 + AES-256-GCM
10
10
  - 📝 **Variable Expansion** - Support for `${VAR}`, defaults, and alternates
11
11
  - 🔧 **Command Substitution** - Execute commands with `$(command)`
12
12
  - 🎯 **Multi-Environment** - Manage multiple .env files for different environments
@@ -98,12 +98,25 @@ buddy env:decrypt --file .env.production
98
98
 
99
99
  ### How Encryption Works
100
100
 
101
- The encryption uses **secp256k1 ECIES** (Elliptic Curve Integrated Encryption Scheme):
101
+ New writes use the experimental version 2 envelope proposed in
102
+ [`stacksjs/rfcs#6`](https://github.com/stacksjs/rfcs/issues/6):
102
103
 
103
- 1. A keypair is generated using secp256k1 (same as Bitcoin)
104
- 2. Each value is encrypted with AES-256-GCM using an ephemeral key
105
- 3. The ephemeral key is encrypted with the public key
106
- 4. Only the private key can decrypt the values
104
+ 1. A recipient keypair is generated using X25519.
105
+ 2. Every value gets a fresh ephemeral X25519 pair, 16-byte HKDF salt, and
106
+ 12-byte AES-GCM nonce.
107
+ 3. X25519 and HKDF-SHA-256 derive a one-use AES-256-GCM key.
108
+ 4. Ciphertext and envelope metadata are authenticated; malformed, modified, and
109
+ wrong-key inputs fail with the same non-secret error.
110
+
111
+ The pre-RFC `encrypted:<base64>` format can only be read for migration. New
112
+ writes are `encrypted:v2:<base64url>`, and legacy public keys are rejected. Run
113
+ `buddy env:rotate` to decrypt legacy values in memory, generate a version 2 key,
114
+ and replace the encrypted file without writing plaintext to disk.
115
+
116
+ This feature is not a complete secret-management system. It has not yet passed
117
+ the independent review required by [Stacks issue #2058](https://github.com/stacksjs/stacks/issues/2058),
118
+ so do not treat it as a production security boundary without your own review.
119
+ See [SECURITY.md](./SECURITY.md) for its threat model and non-guarantees.
107
120
 
108
121
  **Example encrypted .env:**
109
122
 
@@ -112,11 +125,11 @@ The encryption uses **secp256k1 ECIES** (Elliptic Curve Integrated Encryption Sc
112
125
  # / public-key encryption for .env files /
113
126
  # / [how it works](https://stacksjs.com/encryption) /
114
127
  # /----------------------------------------------------------/
115
- DOTENV_PUBLIC_KEY="034af93e93708b994c10f236c96ef88e47291066946cce2e8d98c9e02c741ced45"
128
+ DOTENV_PUBLIC_KEY="x25519-public:<base64url-spki>"
116
129
 
117
130
  # .env
118
- API_KEY="encrypted:BDqDBibm4wsYqMpCjTQ6BsDHmMadg9K3dAt+Z9HPMfLEIRVz50hmLXPXRuDBXaJi..."
119
- DB_PASSWORD="encrypted:AKx8Bh3m5xtZrNqDkUP7CuEInOcfg9L4eBy/2qt59vbSU0aN9WSmN..."
131
+ API_KEY="encrypted:v2:<base64url-envelope>"
132
+ DB_PASSWORD="encrypted:v2:<base64url-envelope>"
120
133
  ```
121
134
 
122
135
  ## CLI Commands
package/dist/cli.js CHANGED
@@ -1,23 +1,24 @@
1
1
  // @bun
2
- import{createCipheriv as S,createDecipheriv as b,createHash as f,randomBytes as x}from"crypto";function P(j,I){let X=x(16),J=I.length===32?I:Buffer.from(I.toString("hex").slice(0,64),"hex"),q=S("aes-256-gcm",J,X),Q=Buffer.concat([q.update(j,"utf8"),q.final()]),U=q.getAuthTag();return{ciphertext:Q.toString("hex"),iv:X.toString("hex"),authTag:U.toString("hex")}}function N(j,I,X,J){let q=I.length===32?I:Buffer.from(I.toString("hex").slice(0,64),"hex");try{let Q=b("aes-256-gcm",q,Buffer.from(X,"hex"));return Q.setAuthTag(Buffer.from(J,"hex")),Buffer.concat([Q.update(Buffer.from(j,"hex")),Q.final()]).toString("utf8")}catch(Q){throw Error(`Decryption failed (data may be corrupted or key is incorrect): ${Q instanceof Error?Q.message:String(Q)}`)}}function F(){let j=x(32);return{publicKey:f("sha256").update(j).digest().toString("hex"),privateKey:j.toString("hex")}}function V(j,I){let X=f("sha256").update(Buffer.from(I,"hex")).digest(),{ciphertext:J,iv:q,authTag:Q}=P(j,X);return`encrypted:${Buffer.concat([Buffer.from(q,"hex"),Buffer.from(Q,"hex"),Buffer.from(J,"hex")]).toString("base64")}`}function w(j,I){if(!j.startsWith("encrypted:"))return j;let X=j.slice(10),J=Buffer.from(X,"base64");if(J.length<32)throw Error("Invalid encrypted data: payload too short (need at least iv + authTag = 32 bytes)");let q=J.subarray(0,16).toString("hex"),Q=J.subarray(16,32).toString("hex"),U=J.subarray(32).toString("hex"),z=f("sha256").update(Buffer.from(I,"hex")).digest(),Y=f("sha256").update(z).digest();return N(U,Y,q,Q)}function v(j){let I=j.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return I&&I[1]?I[1].toLowerCase():""}function c(j=""){if(!j)return process.env.DOTENV_PRIVATE_KEY;let I=`DOTENV_PRIVATE_KEY_${j.toUpperCase()}`;return process.env[I]}function C(j,I={}){let X={},J=[],q=[],Q=j.split(`
3
- `);for(let U of Q){let z=U.trim();if(!z||z.startsWith("#"))continue;if(z.startsWith("DOTENV_PUBLIC_KEY=")){let A=z.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(A&&A[1]!==void 0)X.DOTENV_PUBLIC_KEY=A[1];continue}let Y=z.match(/^([^=]+)=(.*)$/);if(!Y||Y[1]===void 0||Y[2]===void 0)continue;let Z=Y[1].trim(),$=Y[2].trim();if($.startsWith('"')&&$.endsWith('"')||$.startsWith("'")&&$.endsWith("'")){if($=$.slice(1,-1),$.includes("\\n"))$=$.replace(/\\n/g,`
4
- `)}if($.startsWith("encrypted:")||$.startsWith("enc:")){if(!I.privateKey){q.push(Z);continue}try{let A=$.startsWith("enc:")?`encrypted:${$.slice(4)}`:$;$=w(A,I.privateKey)}catch(A){J.push(`Failed to decrypt ${Z}: ${A instanceof Error?A.message:"Unknown error"}`)}}$=K($,{...I.processEnv||process.env,...X}),$=h($),X[Z]=$}return{parsed:X,errors:J,skippedEncrypted:q}}function K(j,I){return j.replace(/\$\{([^}]+)\}/g,(X,J)=>{let q=J.match(/^([^:\-+]+)(:-|-)(.+)$/);if(q){let[,U,z,Y]=q,Z=I[U];if(z===":-")return Z||Y;else return Z!==void 0?Z:Y}let Q=J.match(/^([^:\-+]+)(:?\+)(.+)$/);if(Q){let[,U,z,Y]=Q,Z=I[U];if(z===":+")return Z?Y:"";else return Z!==void 0?Y:""}return I[J]||""})}var m=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function h(j){return j.replace(/\$\(([^)]+)\)/g,(I,X)=>{try{let J=X.trim().split(/\s+/),q=J[0];if(!q||!m.has(q))return console.warn(`[env] Blocked command substitution for disallowed command: ${q}`),"";let Q=Bun.spawnSync(J,{stdout:"pipe",stderr:"pipe"});if(Q.exitCode===0)return new TextDecoder().decode(Q.stdout).trim();console.warn(`[env] Command substitution failed (exit code ${Q.exitCode}): ${q}`)}catch(J){console.warn(`[env] Command substitution error: ${J instanceof Error?J.message:String(J)}`)}return""})}async function s(j,I={}){let X={},J=[],q=[];for(let Q of j)try{let U=Bun.file(Q);if(!U.size)continue;let z=await U.text(),{parsed:Y,errors:Z,skippedEncrypted:$}=C(z,I);J.push(...Z),q.push(...$);for(let[A,O]of Object.entries(Y)){if(!I.overload&&X[A]!==void 0)continue;X[A]=O}}catch(U){if(U?.code==="ENOENT")continue;J.push(`Failed to read ${Q}: ${U instanceof Error?U.message:String(U)}`)}return{parsed:X,errors:J,skippedEncrypted:q}}import{existsSync as g,readFileSync as D,writeFileSync as L}from"fs";import{resolve as M}from"path";function k(j={}){let I=j.cwd||process.cwd(),X=M(I,j.file||".env"),J=M(I,j.keysFile||".env.keys");if(!g(X))return{success:!1,error:`File not found: ${X}`};try{let q,Q;if(g(J)){let O=D(J,"utf-8"),{parsed:W}=C(O),_=((j.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),G=_?`DOTENV_PUBLIC_KEY_${_}`:"DOTENV_PUBLIC_KEY",T=_?`DOTENV_PRIVATE_KEY_${_}`:"DOTENV_PRIVATE_KEY";if(q=W[G]||"",Q=W[T]||"",!q||!Q){let B=F();q=B.publicKey,Q=B.privateKey;let E=`
5
- ${G}="${q}"
6
- ${T}="${Q}"
7
- `;L(J,O+E,"utf-8")}}else{let O=F();q=O.publicKey,Q=O.privateKey;let H=((j.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),_=H?`DOTENV_PUBLIC_KEY_${H}`:"DOTENV_PUBLIC_KEY",G=H?`DOTENV_PRIVATE_KEY_${H}`:"DOTENV_PRIVATE_KEY",T=`# .env.keys - Keep this file secure and never commit to source control
8
- ${_}="${q}"
9
- ${G}="${Q}"
10
- `;L(J,T,"utf-8")}let z=D(X,"utf-8").split(`
11
- `),Y=[],Z=j.file?j.file.replace(/^\.env\./,"").toUpperCase():"",$=Z?`DOTENV_PUBLIC_KEY_${Z}`:"DOTENV_PUBLIC_KEY";Y.push("#/-------------------[DOTENV_PUBLIC_KEY]--------------------/"),Y.push("#/ public-key encryption for .env files /"),Y.push("#/ [how it works](https://stacksjs.com/encryption) /"),Y.push("#/----------------------------------------------------------/"),Y.push(`${$}="${q}"`),Y.push("");for(let O of z){let W=O.trim();if(!W||W.startsWith("#")){Y.push(O);continue}if(W.startsWith("DOTENV_PUBLIC_KEY"))continue;let R=W.match(/^([^=]+)=(.*)$/);if(!R||R[1]===void 0||R[2]===void 0){Y.push(O);continue}let H=R[1].trim(),_=R[2].trim();if(_.startsWith('"')&&_.endsWith('"')||_.startsWith("'")&&_.endsWith("'"))_=_.slice(1,-1);let G=!0;if(j.key&&!H.includes(j.key))G=!1;if(j.excludeKey&&H.includes(j.excludeKey))G=!1;if(_.startsWith("encrypted:"))G=!1;if(G)_=V(_,q);Y.push(`${H}="${_}"`)}let A=Y.join(`
12
- `);if(j.stdout)return{success:!0,output:A};return L(X,A,"utf-8"),{success:!0,output:`\u2714 encrypted (${j.file||".env"})
13
- \u2714 key added to ${j.keysFile||".env.keys"}`}}catch(q){return{success:!1,error:`Failed to encrypt: ${q instanceof Error?q.message:"Unknown error"}`}}}function d(j={}){let I=j.cwd||process.cwd(),X=M(I,j.file||".env"),J=M(I,j.keysFile||".env.keys");if(!g(X))return{success:!1,error:`File not found: ${X}`};if(!g(J))return{success:!1,error:`Keys file not found: ${J}`};try{let q=D(J,"utf-8"),{parsed:Q}=C(q),Y=((j.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),Z=Y?`DOTENV_PRIVATE_KEY_${Y}`:"DOTENV_PRIVATE_KEY",$=Q[Z];if(!$)return{success:!1,error:`Private key not found: ${Z}`};let O=D(X,"utf-8").split(`
14
- `),W=[];for(let H of O){let _=H.trim();if(!_||_.startsWith("#")){W.push(H);continue}if(_.startsWith("DOTENV_PUBLIC_KEY"))continue;let G=_.match(/^([^=]+)=(.*)$/);if(!G||G[1]===void 0||G[2]===void 0){W.push(H);continue}let T=G[1].trim(),B=G[2].trim();if(B.startsWith('"')&&B.endsWith('"')||B.startsWith("'")&&B.endsWith("'"))B=B.slice(1,-1);let E=B.startsWith("encrypted:");if(j.key&&!T.includes(j.key))E=!1;if(E)B=w(B,$);W.push(`${T}="${B}"`)}let R=W.join(`
15
- `);if(j.stdout)return{success:!0,output:R};return L(X,R,"utf-8"),{success:!0,output:`\u2714 decrypted (${j.file||".env"})`}}catch(q){return{success:!1,error:`Failed to decrypt: ${q instanceof Error?q.message:"Unknown error"}`}}}function i(j,I,X={}){let J=X.cwd||process.cwd(),q=M(J,X.file||".env"),Q=M(J,X.keysFile||".env.keys");try{let U="";if(g(q))U=D(q,"utf-8");let z=U.split(`
16
- `),Y=!1,Z,O=((X.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),W=O?`DOTENV_PUBLIC_KEY_${O}`:"DOTENV_PUBLIC_KEY",R=O?`DOTENV_PRIVATE_KEY_${O}`:"DOTENV_PRIVATE_KEY";for(let G of z){let T=G.trim();if(T.startsWith(`${W}=`)){let B=T.match(/^[^=]+=["']?([^"'\n]+)["']?/);if(B)Z=B[1];break}}if(Z){let G=!1;if(g(Q)){let T=D(Q,"utf-8"),{parsed:B}=C(T);G=Boolean(B[R])}if(!G)Z=void 0}if(!X.plain&&!Z){let G=F();Z=G.publicKey;let T="";if(g(Q))T=D(Q,"utf-8");T+=`
17
- ${W}="${G.publicKey}"
18
- ${R}="${G.privateKey}"
19
- `,L(Q,T,"utf-8");for(let B=z.length-1;B>=0;B--){let E=z[B];if(E!==void 0&&E.trim().startsWith(`${W}=`))z.splice(B,1)}z.unshift(`${W}="${Z}"`)}let H=I;if(!X.plain&&Z)H=V(I,Z);for(let G=0;G<z.length;G++){let T=z[G];if(T===void 0)continue;if(T.trim().startsWith(`${j}=`)){z[G]=`${j}="${H}"`,Y=!0;break}}if(!Y)z.push(`${j}="${H}"`);let _=z.join(`
20
- `);return L(q,_,"utf-8"),{success:!0,output:`set ${j}${X.plain?"":" with encryption"} (${X.file||".env"})`}}catch(U){return{success:!1,error:`Failed to set: ${U instanceof Error?U.message:"Unknown error"}`}}}function t(j,I={}){let X=I.cwd||process.cwd(),J=M(X,I.file||".env");if(!g(J))return{success:!1,error:`File not found: ${J}`};try{let q,Q=M(X,I.keysFile||".env.keys"),Y=((I.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),Z=Y?`DOTENV_PRIVATE_KEY_${Y}`:"DOTENV_PRIVATE_KEY";if(g(Q)){let R=D(Q,"utf-8"),{parsed:H}=C(R);q=H[Z]}if(!q)q=process.env[Z];let $=D(J,"utf-8"),{parsed:A}=C($,{privateKey:q});if(j&&!I.all){let R=A[j];if(R===void 0)return{success:!1,error:`Key not found: ${j}`};return{success:!0,output:R}}let O=I.all?{...process.env,...A}:A,W;switch(I.format){case"shell":W=Object.entries(O).map(([R,H])=>`${R}=${H}`).join(" ");break;case"eval":W=Object.entries(O).map(([R,H])=>`${R}="${H}"`).join(`
21
- `);break;case"json":default:W=I.prettyPrint?JSON.stringify(O,null,2):JSON.stringify(O);break}return{success:!0,output:W}}catch(q){return{success:!1,error:`Failed to get: ${q instanceof Error?q.message:"Unknown error"}`}}}function e(j,I={}){let X=I.cwd||process.cwd(),J=M(X,I.keysFile||".env.keys");if(!g(J))return{success:!1,error:`Keys file not found: ${J}`};try{let q=D(J,"utf-8"),{parsed:Q}=C(q),Y=((I.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),Z=Y?`DOTENV_PUBLIC_KEY_${Y}`:"DOTENV_PUBLIC_KEY",$=Y?`DOTENV_PRIVATE_KEY_${Y}`:"DOTENV_PRIVATE_KEY";if(j){let W=Q[j];if(!W)return{success:!1,error:`Key not found: ${j}`};return{success:!0,output:W}}let A={[Z]:Q[Z],[$]:Q[$]};return{success:!0,output:I.format==="shell"?`${Z}=${A[Z]} ${$}=${A[$]}`:JSON.stringify(A)}}catch(q){return{success:!1,error:`Failed to get keypair: ${q instanceof Error?q.message:"Unknown error"}`}}}function jj(j={}){let I=d({file:j.file,keysFile:j.keysFile,stdout:!0,cwd:j.cwd});if(!I.success)return I;let X=j.cwd||process.cwd(),J=M(X,j.file||".env"),q=M(X,j.keysFile||".env.keys");if(I.output)L(J,I.output,"utf-8");let Q=F(),z=D(q,"utf-8").split(`
22
- `),Y=j.file?j.file.replace(/^\.env\./,"").toUpperCase():"",Z=Y?`DOTENV_PUBLIC_KEY_${Y}`:"DOTENV_PUBLIC_KEY",$=Y?`DOTENV_PRIVATE_KEY_${Y}`:"DOTENV_PRIVATE_KEY";for(let A=0;A<z.length;A++){let O=z[A];if(O===void 0)continue;if(O.startsWith(`${Z}=`))z[A]=`${Z}="${Q.publicKey}"`;else if(O.startsWith(`${$}=`))z[A]=`${$}="${Q.privateKey}"`}return L(q,z.join(`
23
- `),"utf-8"),k({file:j.file,keysFile:j.keysFile,key:j.key,excludeKey:j.excludeKey,stdout:j.stdout,cwd:j.cwd})}export{i as setEnv,jj as rotateKeypair,e as getKeypair,t as getEnv,k as encryptEnv,d as decryptEnv};
2
+ import{createCipheriv as u,createDecipheriv as K,createHash as C,createPrivateKey as e,createPublicKey as m,diffieHellman as k,generateKeyPairSync as n,hkdfSync as s,randomBytes as F}from"crypto";var b="x25519-public:",S="x25519-private:",V="encrypted:v2:",P="encrypted:",o=Buffer.from("stacks-env:v2","utf8"),i="Environment decryption failed: authentication or format error";function v(f){if(f.length===32)return f;return C("sha256").update(f).digest()}function _f(f,g){let r=F(16),c=u("aes-256-gcm",v(g),r);return{ciphertext:Buffer.concat([c.update(f,"utf8"),c.final()]).toString("hex"),iv:r.toString("hex"),authTag:c.getAuthTag().toString("hex")}}function ff(f,g,r,c){try{let E=K("aes-256-gcm",v(g),Buffer.from(r,"hex"));return E.setAuthTag(Buffer.from(c,"hex")),Buffer.concat([E.update(Buffer.from(f,"hex")),E.final()]).toString("utf8")}catch{throw Error("Decryption failed: authentication or format error")}}function U(f){return f.toString("base64url")}function z(f,g,r){if(typeof f!=="string"||f.length>0&&!/^[A-Za-z0-9_-]+$/.test(f))throw Error(`${g} is not canonical base64url`);let c=Buffer.from(f,"base64url");if(U(c)!==f)throw Error(`${g} is not canonical base64url`);if(r!==void 0&&c.length!==r)throw Error(`${g} has an invalid length`);return c}function gf(f){if(!f.startsWith(b))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let g=m({key:z(f.slice(b.length),"public key"),format:"der",type:"spki"});if(g.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return g}function rf(f){if(!f.startsWith(S))throw Error("Private key is not a version 2 X25519 key");let g=e({key:z(f.slice(S.length),"private key"),format:"der",type:"pkcs8"});if(g.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return g}function y(f){return Buffer.from(`stacks-env:v2;${f.epk};${f.salt};${f.nonce}`,"utf8")}function q(){let{publicKey:f,privateKey:g}=n("x25519"),r=f.export({format:"der",type:"spki"}),c=g.export({format:"der",type:"pkcs8"});return{publicKey:`${b}${U(r)}`,privateKey:`${S}${U(c)}`}}function L(f,g){let r=gf(g),c=n("x25519"),E=U(c.publicKey.export({format:"der",type:"spki"})),h=F(16),A=F(12),x=k({privateKey:c.privateKey,publicKey:r}),$=Buffer.from(s("sha256",x,h,o,32)),B={epk:E,salt:U(h),nonce:U(A)};try{let w=u("aes-256-gcm",$,A);w.setAAD(y(B));let _=Buffer.concat([w.update(f,"utf8"),w.final()]),D={v:2,...B,ciphertext:U(_),tag:U(w.getAuthTag())};return`${V}${U(Buffer.from(JSON.stringify(D),"utf8"))}`}finally{x.fill(0),$.fill(0)}}function Ef(f){let g=z(f.slice(V.length),"envelope").toString("utf8"),r=JSON.parse(g);if(!r||typeof r!=="object"||Array.isArray(r))throw Error("envelope is not an object");let c=Object.keys(r).sort(),E=["ciphertext","epk","nonce","salt","tag","v"];if(c.length!==E.length||c.some((h,A)=>h!==E[A]))throw Error("envelope fields are invalid");if(r.v!==2)throw Error("envelope version is unsupported");for(let h of["epk","salt","nonce","ciphertext","tag"])if(typeof r[h]!=="string")throw Error(`${h} is invalid`);return r}function cf(f,g){try{let r=Ef(f),c=rf(g),E=m({key:z(r.epk,"ephemeral public key"),format:"der",type:"spki"});if(E.asymmetricKeyType!=="x25519")throw Error("ephemeral key is not X25519");let h=z(r.salt,"salt",16),A=z(r.nonce,"nonce",12),x=z(r.ciphertext,"ciphertext"),$=z(r.tag,"tag",16),B=k({privateKey:c,publicKey:E}),w=Buffer.from(s("sha256",B,h,o,32));try{let _=K("aes-256-gcm",w,A);return _.setAAD(y(r)),_.setAuthTag($),Buffer.concat([_.update(x),_.final()]).toString("utf8")}finally{B.fill(0),w.fill(0)}}catch{throw Error(i)}}function Bf(f,g){try{if(!/^[0-9a-f]{64}$/.test(g))throw Error("invalid legacy private key");let r=Buffer.from(f.slice(P.length),"base64");if(r.length<32)throw Error("invalid legacy payload");let c=C("sha256").update(Buffer.from(g,"hex")).digest(),E=C("sha256").update(c).digest();return ff(r.subarray(32).toString("hex"),E,r.subarray(0,16).toString("hex"),r.subarray(16,32).toString("hex"))}catch{throw Error(i)}}function If(f){return f.startsWith(P)&&!f.startsWith(V)}function Q(f,g){if(!f.startsWith(P))return f;return f.startsWith(V)?cf(f,g):Bf(f,g)}function Tf(f,g,r){return L(Q(f,g),r)}function tf(f){let g=f.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return g&&g[1]?g[1].toLowerCase():""}function Rf(f=""){return f?process.env[`DOTENV_PRIVATE_KEY_${f.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}function Z(f,g={}){let r={},c=[],E=[],h=f.split(`
3
+ `);for(let A of h){let x=A.trim();if(!x||x.startsWith("#"))continue;if(x.startsWith("DOTENV_PUBLIC_KEY=")){let _=x.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(_&&_[1]!==void 0)r.DOTENV_PUBLIC_KEY=_[1];continue}let $=x.match(/^([^=]+)=(.*)$/);if(!$||$[1]===void 0||$[2]===void 0)continue;let B=$[1].trim(),w=$[2].trim();if(w.startsWith('"')&&w.endsWith('"')||w.startsWith("'")&&w.endsWith("'")){if(w=w.slice(1,-1),w.includes("\\n"))w=w.replace(/\\n/g,`
4
+ `)}if(w.startsWith("encrypted:")||w.startsWith("enc:")){if(!g.privateKey){E.push(B);continue}try{let _=w.startsWith("enc:")?`encrypted:${w.slice(4)}`:w;w=Q(_,g.privateKey)}catch(_){c.push(`Failed to decrypt ${B}: ${_ instanceof Error?_.message:"Unknown error"}`)}}w=hf(w,{...g.processEnv||process.env,...r}),w=$f(w),r[B]=w}return{parsed:r,errors:c,skippedEncrypted:E}}function hf(f,g){return f.replace(/\$\{([^}]+)\}/g,(r,c)=>{let E=c.match(/^([^:\-+]+)(:-|-)(.+)$/);if(E){let[,A,x,$]=E,B=g[A];if(x===":-")return B||$;else return B!==void 0?B:$}let h=c.match(/^([^:\-+]+)(:?\+)(.+)$/);if(h){let[,A,x,$]=h,B=g[A];if(x===":+")return B?$:"";else return B!==void 0?$:""}return g[c]||""})}var wf=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function $f(f){return f.replace(/\$\(([^)]+)\)/g,(g,r)=>{try{let c=r.trim().split(/\s+/),E=c[0];if(!E||!wf.has(E))return console.warn(`[env] Blocked command substitution for disallowed command: ${E}`),"";let h=Bun.spawnSync(c,{stdout:"pipe",stderr:"pipe"});if(h.exitCode===0)return new TextDecoder().decode(h.stdout).trim();console.warn(`[env] Command substitution failed (exit code ${h.exitCode}): ${E}`)}catch(c){console.warn(`[env] Command substitution error: ${c instanceof Error?c.message:String(c)}`)}return""})}async function Of(f,g={}){let r={},c=[],E=[];for(let h of f)try{let A=Bun.file(h);if(!A.size)continue;let x=await A.text(),{parsed:$,errors:B,skippedEncrypted:w}=Z(x,g);c.push(...B),E.push(...w);for(let[_,D]of Object.entries($)){if(!g.overload&&r[_]!==void 0)continue;r[_]=D}}catch(A){if(A?.code==="ENOENT")continue;c.push(`Failed to read ${h}: ${A instanceof Error?A.message:String(A)}`)}return{parsed:r,errors:c,skippedEncrypted:E}}import{existsSync as G,readFileSync as j,renameSync as d,rmSync as l,writeFileSync as J}from"fs";import{basename as N,dirname as p,resolve as W}from"path";function a(f,g,r,c){let E=["#/-------------------[DOTENV_PUBLIC_KEY]--------------------/","#/ versioned X25519 encryption for .env files /","#/ [how it works](https://stacksjs.com/encryption) /","#/----------------------------------------------------------/",`${r}="${g}"`,""];for(let h of f.split(`
5
+ `)){let A=h.trim();if(!A||A.startsWith("#")){E.push(h);continue}if(A.startsWith("DOTENV_PUBLIC_KEY"))continue;let x=A.match(/^([^=]+)=(.*)$/);if(!x||x[1]===void 0||x[2]===void 0){E.push(h);continue}let $=x[1].trim(),B=x[2].trim();if(B.startsWith('"')&&B.endsWith('"')||B.startsWith("'")&&B.endsWith("'"))B=B.slice(1,-1);if((!c.key||$.includes(c.key))&&(!c.excludeKey||!$.includes(c.excludeKey))&&!B.startsWith("encrypted:"))B=L(B,g);E.push(`${$}="${B}"`)}return E.join(`
6
+ `)}function Jf(f={}){let g=f.cwd||process.cwd(),r=W(g,f.file||".env"),c=W(g,f.keysFile||".env.keys");if(!G(r))return{success:!1,error:`File not found: ${r}`};try{let E,h;if(G(c)){let w=j(c,"utf-8"),{parsed:_}=Z(w),R=((f.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),X=R?`DOTENV_PUBLIC_KEY_${R}`:"DOTENV_PUBLIC_KEY",H=R?`DOTENV_PRIVATE_KEY_${R}`:"DOTENV_PRIVATE_KEY";if(E=_[X]||"",h=_[H]||"",!E||!h){let t=q();E=t.publicKey,h=t.privateKey;let O=`
7
+ ${X}="${E}"
8
+ ${H}="${h}"
9
+ `;J(c,w+O,"utf-8")}}else{let w=q();E=w.publicKey,h=w.privateKey;let I=((f.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),R=I?`DOTENV_PUBLIC_KEY_${I}`:"DOTENV_PUBLIC_KEY",X=I?`DOTENV_PRIVATE_KEY_${I}`:"DOTENV_PRIVATE_KEY",H=`# .env.keys - Keep this file secure and never commit to source control
10
+ ${R}="${E}"
11
+ ${X}="${h}"
12
+ `;J(c,H,"utf-8")}let A=j(r,"utf-8"),x=f.file?f.file.replace(/^\.env\./,"").toUpperCase():"",$=x?`DOTENV_PUBLIC_KEY_${x}`:"DOTENV_PUBLIC_KEY",B=a(A,E,$,f);if(f.stdout)return{success:!0,output:B};return J(r,B,"utf-8"),{success:!0,output:`\u2714 encrypted (${f.file||".env"})
13
+ \u2714 key added to ${f.keysFile||".env.keys"}`}}catch(E){return{success:!1,error:`Failed to encrypt: ${E instanceof Error?E.message:"Unknown error"}`}}}function xf(f={}){let g=f.cwd||process.cwd(),r=W(g,f.file||".env"),c=W(g,f.keysFile||".env.keys");if(!G(r))return{success:!1,error:`File not found: ${r}`};if(!G(c))return{success:!1,error:`Keys file not found: ${c}`};try{let E=j(c,"utf-8"),{parsed:h}=Z(E),$=((f.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),B=$?`DOTENV_PRIVATE_KEY_${$}`:"DOTENV_PRIVATE_KEY",w=h[B];if(!w)return{success:!1,error:`Private key not found: ${B}`};let D=j(r,"utf-8").split(`
14
+ `),I=[];for(let X of D){let H=X.trim();if(!H||H.startsWith("#")){I.push(X);continue}if(H.startsWith("DOTENV_PUBLIC_KEY"))continue;let t=H.match(/^([^=]+)=(.*)$/);if(!t||t[1]===void 0||t[2]===void 0){I.push(X);continue}let O=t[1].trim(),T=t[2].trim();if(T.startsWith('"')&&T.endsWith('"')||T.startsWith("'")&&T.endsWith("'"))T=T.slice(1,-1);let Y=T.startsWith("encrypted:");if(f.key&&!O.includes(f.key))Y=!1;if(Y)T=Q(T,w);I.push(`${O}="${T}"`)}let R=I.join(`
15
+ `);if(f.stdout)return{success:!0,output:R};return J(r,R,"utf-8"),{success:!0,output:`\u2714 decrypted (${f.file||".env"})`}}catch(E){return{success:!1,error:`Failed to decrypt: ${E instanceof Error?E.message:"Unknown error"}`}}}function Uf(f,g,r={}){let c=r.cwd||process.cwd(),E=W(c,r.file||".env"),h=W(c,r.keysFile||".env.keys");try{let A="";if(G(E))A=j(E,"utf-8");let x=A.split(`
16
+ `),$=!1,B,D=((r.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),I=D?`DOTENV_PUBLIC_KEY_${D}`:"DOTENV_PUBLIC_KEY",R=D?`DOTENV_PRIVATE_KEY_${D}`:"DOTENV_PRIVATE_KEY";for(let t of x){let O=t.trim();if(O.startsWith(`${I}=`)){let T=O.match(/^[^=]+=["']?([^"'\n]+)["']?/);if(T)B=T[1];break}}if(B){let t=!1;if(G(h)){let O=j(h,"utf-8"),{parsed:T}=Z(O);t=Boolean(T[R])}if(!t)B=void 0}if(!r.plain&&!B){let t=q();B=t.publicKey;let O="";if(G(h))O=j(h,"utf-8");O+=`
17
+ ${I}="${t.publicKey}"
18
+ ${R}="${t.privateKey}"
19
+ `,J(h,O,"utf-8");for(let T=x.length-1;T>=0;T--){let Y=x[T];if(Y!==void 0&&Y.trim().startsWith(`${I}=`))x.splice(T,1)}x.unshift(`${I}="${B}"`)}let X=g;if(!r.plain&&B)X=L(g,B);for(let t=0;t<x.length;t++){let O=x[t];if(O===void 0)continue;if(O.trim().startsWith(`${f}=`)){x[t]=`${f}="${X}"`,$=!0;break}}if(!$)x.push(`${f}="${X}"`);let H=x.join(`
20
+ `);return J(E,H,"utf-8"),{success:!0,output:`set ${f}${r.plain?"":" with encryption"} (${r.file||".env"})`}}catch(A){return{success:!1,error:`Failed to set: ${A instanceof Error?A.message:"Unknown error"}`}}}function zf(f,g={}){let r=g.cwd||process.cwd(),c=W(r,g.file||".env");if(!G(c))return{success:!1,error:`File not found: ${c}`};try{let E,h=W(r,g.keysFile||".env.keys"),$=((g.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),B=$?`DOTENV_PRIVATE_KEY_${$}`:"DOTENV_PRIVATE_KEY";if(G(h)){let R=j(h,"utf-8"),{parsed:X}=Z(R);E=X[B]}if(!E)E=process.env[B];let w=j(c,"utf-8"),{parsed:_}=Z(w,{privateKey:E});if(f&&!g.all){let R=_[f];if(R===void 0)return{success:!1,error:`Key not found: ${f}`};return{success:!0,output:R}}let D=g.all?{...process.env,..._}:_,I;switch(g.format){case"shell":I=Object.entries(D).map(([R,X])=>`${R}=${X}`).join(" ");break;case"eval":I=Object.entries(D).map(([R,X])=>`${R}="${X}"`).join(`
21
+ `);break;case"json":default:I=g.prettyPrint?JSON.stringify(D,null,2):JSON.stringify(D);break}return{success:!0,output:I}}catch(E){return{success:!1,error:`Failed to get: ${E instanceof Error?E.message:"Unknown error"}`}}}function Zf(f,g={}){let r=g.cwd||process.cwd(),c=W(r,g.keysFile||".env.keys");if(!G(c))return{success:!1,error:`Keys file not found: ${c}`};try{let E=j(c,"utf-8"),{parsed:h}=Z(E),$=((g.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),B=$?`DOTENV_PUBLIC_KEY_${$}`:"DOTENV_PUBLIC_KEY",w=$?`DOTENV_PRIVATE_KEY_${$}`:"DOTENV_PRIVATE_KEY";if(f){let I=h[f];if(!I)return{success:!1,error:`Key not found: ${f}`};return{success:!0,output:I}}let _={[B]:h[B],[w]:h[w]};return{success:!0,output:g.format==="shell"?`${B}=${_[B]} ${w}=${_[w]}`:JSON.stringify(_)}}catch(E){return{success:!1,error:`Failed to get keypair: ${E instanceof Error?E.message:"Unknown error"}`}}}function qf(f={}){let g=xf({file:f.file,keysFile:f.keysFile,stdout:!0,cwd:f.cwd});if(!g.success)return g;let r=f.cwd||process.cwd(),c=W(r,f.file||".env"),E=W(r,f.keysFile||".env.keys");if(g.output===void 0)return{success:!1,error:"Rotation produced no decrypted content"};let h=q(),A=j(c),x=j(E),$=x.toString("utf8").split(`
22
+ `),B=f.file||".env",w=N(B).replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),_=w?`DOTENV_PUBLIC_KEY_${w}`:"DOTENV_PUBLIC_KEY",D=w?`DOTENV_PRIVATE_KEY_${w}`:"DOTENV_PRIVATE_KEY",I=!1,R=!1;for(let Y=0;Y<$.length;Y++){let M=$[Y];if(M===void 0)continue;if(M.startsWith(`${_}=`))$[Y]=`${_}="${h.publicKey}"`,I=!0;else if(M.startsWith(`${D}=`))$[Y]=`${D}="${h.privateKey}"`,R=!0}if(!I)$.push(`${_}="${h.publicKey}"`);if(!R)$.push(`${D}="${h.privateKey}"`);let X=`${$.join(`
23
+ `).replace(/\n+$/,"")}
24
+ `,H=a(g.output,h.publicKey,_,f),t=`${process.pid}-${Date.now()}`,O=W(p(c),`.${N(c)}.${t}.rotate`),T=W(p(E),`.${N(E)}.${t}.rotate`);try{if(J(O,H,{encoding:"utf8",mode:384}),J(T,X,{encoding:"utf8",mode:384}),f.stdout)return d(T,E),{success:!0,output:H};d(O,c);try{d(T,E)}catch(Y){throw J(c,A),Y}return{success:!0,output:`\u2714 rotated (${f.file||".env"}) to encrypted:v2`}}catch(Y){if(!G(E))J(E,x,{mode:384});return{success:!1,error:`Failed to rotate without exposing plaintext: ${Y instanceof Error?Y.message:"Unknown error"}`}}finally{l(O,{force:!0}),l(T,{force:!0})}}export{Uf as setEnv,qf as rotateKeypair,Zf as getKeypair,zf as getEnv,Jf as encryptEnv,xf as decryptEnv};
package/dist/crypto.d.ts CHANGED
@@ -1,13 +1,9 @@
1
- // AES-256-GCM encryption using Node.js crypto
2
1
  export declare function aesEncrypt(plaintext: string, key: Buffer): { ciphertext: string, iv: string, authTag: string };
3
2
  export declare function aesDecrypt(ciphertext: string, key: Buffer, iv: string, authTag: string): string;
4
- // secp256k1 key generation
5
3
  export declare function generateKeypair(): { publicKey: string, privateKey: string };
6
- // Encrypt a value using public key
7
- export declare function encryptValue(value: string, publicKey: string): string;
8
- // Decrypt a value using private key
4
+ export declare function encryptValue(value: string, recipientPublicKey: string): string;
5
+ export declare function isLegacyEncryptedValue(value: string): boolean;
9
6
  export declare function decryptValue(encryptedValue: string, privateKey: string): string;
10
- // Parse environment name from private key variable
7
+ export declare function migrateEncryptedValue(encryptedValue: string, oldPrivateKey: string, newPublicKey: string): string;
11
8
  export declare function parseEnvFromKey(keyName: string): string;
12
- // Get appropriate private key for environment
13
9
  export declare function getPrivateKey(env?: string): string | undefined;
package/dist/crypto.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- import{createCipheriv as P,createDecipheriv as Q,createHash as J,randomBytes as L}from"crypto";function R(q,j){let F=L(16),z=j.length===32?j:Buffer.from(j.toString("hex").slice(0,64),"hex"),A=P("aes-256-gcm",z,F),w=Buffer.concat([A.update(q,"utf8"),A.final()]),I=A.getAuthTag();return{ciphertext:w.toString("hex"),iv:F.toString("hex"),authTag:I.toString("hex")}}function S(q,j,F,z){let A=j.length===32?j:Buffer.from(j.toString("hex").slice(0,64),"hex");try{let w=Q("aes-256-gcm",A,Buffer.from(F,"hex"));return w.setAuthTag(Buffer.from(z,"hex")),Buffer.concat([w.update(Buffer.from(q,"hex")),w.final()]).toString("utf8")}catch(w){throw Error(`Decryption failed (data may be corrupted or key is incorrect): ${w instanceof Error?w.message:String(w)}`)}}function W(){let q=L(32);return{publicKey:J("sha256").update(q).digest().toString("hex"),privateKey:q.toString("hex")}}function X(q,j){let F=J("sha256").update(Buffer.from(j,"hex")).digest(),{ciphertext:z,iv:A,authTag:w}=R(q,F);return`encrypted:${Buffer.concat([Buffer.from(A,"hex"),Buffer.from(w,"hex"),Buffer.from(z,"hex")]).toString("base64")}`}function Y(q,j){if(!q.startsWith("encrypted:"))return q;let F=q.slice(10),z=Buffer.from(F,"base64");if(z.length<32)throw Error("Invalid encrypted data: payload too short (need at least iv + authTag = 32 bytes)");let A=z.subarray(0,16).toString("hex"),w=z.subarray(16,32).toString("hex"),I=z.subarray(32).toString("hex"),M=J("sha256").update(Buffer.from(j,"hex")).digest(),O=J("sha256").update(M).digest();return S(I,O,A,w)}function Z(q){let j=q.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return j&&j[1]?j[1].toLowerCase():""}function _(q=""){if(!q)return process.env.DOTENV_PRIVATE_KEY;let j=`DOTENV_PRIVATE_KEY_${q.toUpperCase()}`;return process.env[j]}export{Z as parseEnvFromKey,_ as getPrivateKey,W as generateKeypair,X as encryptValue,Y as decryptValue,R as aesEncrypt,S as aesDecrypt};
2
+ import{createCipheriv as x,createDecipheriv as w,createHash as l,createPrivateKey as S,createPublicKey as k,diffieHellman as b,generateKeyPairSync as K,hkdfSync as B,randomBytes as d}from"crypto";var E="x25519-public:",m="x25519-private:",y="encrypted:v2:",v="encrypted:",P=Buffer.from("stacks-env:v2","utf8"),A="Environment decryption failed: authentication or format error";function T(e){if(e.length===32)return e;return l("sha256").update(e).digest()}function Y(e,t){let r=d(16),n=x("aes-256-gcm",T(t),r);return{ciphertext:Buffer.concat([n.update(e,"utf8"),n.final()]).toString("hex"),iv:r.toString("hex"),authTag:n.getAuthTag().toString("hex")}}function R(e,t,r,n){try{let i=w("aes-256-gcm",T(t),Buffer.from(r,"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 s(e){return e.toString("base64url")}function c(e,t,r){if(typeof e!=="string"||e.length>0&&!/^[A-Za-z0-9_-]+$/.test(e))throw Error(`${t} is not canonical base64url`);let n=Buffer.from(e,"base64url");if(s(n)!==e)throw Error(`${t} is not canonical base64url`);if(r!==void 0&&n.length!==r)throw Error(`${t} has an invalid length`);return n}function V(e){if(!e.startsWith(E))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let t=k({key:c(e.slice(E.length),"public key"),format:"der",type:"spki"});if(t.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return t}function D(e){if(!e.startsWith(m))throw Error("Private key is not a version 2 X25519 key");let t=S({key:c(e.slice(m.length),"private key"),format:"der",type:"pkcs8"});if(t.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return t}function _(e){return Buffer.from(`stacks-env:v2;${e.epk};${e.salt};${e.nonce}`,"utf8")}function H(){let{publicKey:e,privateKey:t}=K("x25519"),r=e.export({format:"der",type:"spki"}),n=t.export({format:"der",type:"pkcs8"});return{publicKey:`${E}${s(r)}`,privateKey:`${m}${s(n)}`}}function I(e,t){let r=V(t),n=K("x25519"),i=s(n.publicKey.export({format:"der",type:"spki"})),o=d(16),f=d(12),g=b({privateKey:n.privateKey,publicKey:r}),h=Buffer.from(B("sha256",g,o,P,32)),u={epk:i,salt:s(o),nonce:s(f)};try{let a=x("aes-256-gcm",h,f);a.setAAD(_(u));let p=Buffer.concat([a.update(e,"utf8"),a.final()]),$={v:2,...u,ciphertext:s(p),tag:s(a.getAuthTag())};return`${y}${s(Buffer.from(JSON.stringify($),"utf8"))}`}finally{g.fill(0),h.fill(0)}}function O(e){let t=c(e.slice(y.length),"envelope").toString("utf8"),r=JSON.parse(t);if(!r||typeof r!=="object"||Array.isArray(r))throw Error("envelope is not an object");let n=Object.keys(r).sort(),i=["ciphertext","epk","nonce","salt","tag","v"];if(n.length!==i.length||n.some((o,f)=>o!==i[f]))throw Error("envelope fields are invalid");if(r.v!==2)throw Error("envelope version is unsupported");for(let o of["epk","salt","nonce","ciphertext","tag"])if(typeof r[o]!=="string")throw Error(`${o} is invalid`);return r}function F(e,t){try{let r=O(e),n=D(t),i=k({key:c(r.epk,"ephemeral public key"),format:"der",type:"spki"});if(i.asymmetricKeyType!=="x25519")throw Error("ephemeral key is not X25519");let o=c(r.salt,"salt",16),f=c(r.nonce,"nonce",12),g=c(r.ciphertext,"ciphertext"),h=c(r.tag,"tag",16),u=b({privateKey:n,publicKey:i}),a=Buffer.from(B("sha256",u,o,P,32));try{let p=w("aes-256-gcm",a,f);return p.setAAD(_(r)),p.setAuthTag(h),Buffer.concat([p.update(g),p.final()]).toString("utf8")}finally{u.fill(0),a.fill(0)}}catch{throw Error(A)}}function N(e,t){try{if(!/^[0-9a-f]{64}$/.test(t))throw Error("invalid legacy private key");let r=Buffer.from(e.slice(v.length),"base64");if(r.length<32)throw Error("invalid legacy payload");let n=l("sha256").update(Buffer.from(t,"hex")).digest(),i=l("sha256").update(n).digest();return R(r.subarray(32).toString("hex"),i,r.subarray(0,16).toString("hex"),r.subarray(16,32).toString("hex"))}catch{throw Error(A)}}function L(e){return e.startsWith(v)&&!e.startsWith(y)}function X(e,t){if(!e.startsWith(v))return e;return e.startsWith(y)?F(e,t):N(e,t)}function W(e,t,r){return I(X(e,t),r)}function j(e){let t=e.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return t&&t[1]?t[1].toLowerCase():""}function z(e=""){return e?process.env[`DOTENV_PRIVATE_KEY_${e.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}export{j as parseEnvFromKey,W as migrateEncryptedValue,L as isLegacyEncryptedValue,z as getPrivateKey,H as generateKeypair,I as encryptValue,X as decryptValue,Y as aesEncrypt,R as aesDecrypt};
package/dist/index.js CHANGED
@@ -1,25 +1,26 @@
1
1
  // @bun
2
- import{createCipheriv as s,createDecipheriv as a,createHash as E,randomBytes as u}from"crypto";function t(J,Q){let X=u(16),$=Q.length===32?Q:Buffer.from(Q.toString("hex").slice(0,64),"hex"),Z=s("aes-256-gcm",$,X),j=Buffer.concat([Z.update(J,"utf8"),Z.final()]),A=Z.getAuthTag();return{ciphertext:j.toString("hex"),iv:X.toString("hex"),authTag:A.toString("hex")}}function e(J,Q,X,$){let Z=Q.length===32?Q:Buffer.from(Q.toString("hex").slice(0,64),"hex");try{let j=a("aes-256-gcm",Z,Buffer.from(X,"hex"));return j.setAuthTag(Buffer.from($,"hex")),Buffer.concat([j.update(Buffer.from(J,"hex")),j.final()]).toString("utf8")}catch(j){throw Error(`Decryption failed (data may be corrupted or key is incorrect): ${j instanceof Error?j.message:String(j)}`)}}function w(){let J=u(32);return{publicKey:E("sha256").update(J).digest().toString("hex"),privateKey:J.toString("hex")}}function K(J,Q){let X=E("sha256").update(Buffer.from(Q,"hex")).digest(),{ciphertext:$,iv:Z,authTag:j}=t(J,X);return`encrypted:${Buffer.concat([Buffer.from(Z,"hex"),Buffer.from(j,"hex"),Buffer.from($,"hex")]).toString("base64")}`}function f(J,Q){if(!J.startsWith("encrypted:"))return J;let X=J.slice(10),$=Buffer.from(X,"base64");if($.length<32)throw Error("Invalid encrypted data: payload too short (need at least iv + authTag = 32 bytes)");let Z=$.subarray(0,16).toString("hex"),j=$.subarray(16,32).toString("hex"),A=$.subarray(32).toString("hex"),Y=E("sha256").update(Buffer.from(Q,"hex")).digest(),G=E("sha256").update(Y).digest();return e(A,G,Z,j)}function DJ(J){let Q=J.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return Q&&Q[1]?Q[1].toLowerCase():""}function c(J=""){if(!J)return process.env.DOTENV_PRIVATE_KEY;let Q=`DOTENV_PRIVATE_KEY_${J.toUpperCase()}`;return process.env[Q]}function L(J,Q={}){let X={},$=[],Z=[],j=J.split(`
3
- `);for(let A of j){let Y=A.trim();if(!Y||Y.startsWith("#"))continue;if(Y.startsWith("DOTENV_PUBLIC_KEY=")){let _=Y.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(_&&_[1]!==void 0)X.DOTENV_PUBLIC_KEY=_[1];continue}let G=Y.match(/^([^=]+)=(.*)$/);if(!G||G[1]===void 0||G[2]===void 0)continue;let W=G[1].trim(),O=G[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(!Q.privateKey){Z.push(W);continue}try{let _=O.startsWith("enc:")?`encrypted:${O.slice(4)}`:O;O=f(_,Q.privateKey)}catch(_){$.push(`Failed to decrypt ${W}: ${_ instanceof Error?_.message:"Unknown error"}`)}}O=JJ(O,{...Q.processEnv||process.env,...X}),O=ZJ(O),X[W]=O}return{parsed:X,errors:$,skippedEncrypted:Z}}function JJ(J,Q){return J.replace(/\$\{([^}]+)\}/g,(X,$)=>{let Z=$.match(/^([^:\-+]+)(:-|-)(.+)$/);if(Z){let[,A,Y,G]=Z,W=Q[A];if(Y===":-")return W||G;else return W!==void 0?W:G}let j=$.match(/^([^:\-+]+)(:?\+)(.+)$/);if(j){let[,A,Y,G]=j,W=Q[A];if(Y===":+")return W?G:"";else return W!==void 0?G:""}return Q[$]||""})}var QJ=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function ZJ(J){return J.replace(/\$\(([^)]+)\)/g,(Q,X)=>{try{let $=X.trim().split(/\s+/),Z=$[0];if(!Z||!QJ.has(Z))return console.warn(`[env] Blocked command substitution for disallowed command: ${Z}`),"";let j=Bun.spawnSync($,{stdout:"pipe",stderr:"pipe"});if(j.exitCode===0)return new TextDecoder().decode(j.stdout).trim();console.warn(`[env] Command substitution failed (exit code ${j.exitCode}): ${Z}`)}catch($){console.warn(`[env] Command substitution error: ${$ instanceof Error?$.message:String($)}`)}return""})}async function IJ(J,Q={}){let X={},$=[],Z=[];for(let j of J)try{let A=Bun.file(j);if(!A.size)continue;let Y=await A.text(),{parsed:G,errors:W,skippedEncrypted:O}=L(Y,Q);$.push(...W),Z.push(...O);for(let[_,U]of Object.entries(G)){if(!Q.overload&&X[_]!==void 0)continue;X[_]=U}}catch(A){if(A?.code==="ENOENT")continue;$.push(`Failed to read ${j}: ${A instanceof Error?A.message:String(A)}`)}return{parsed:X,errors:$,skippedEncrypted:Z}}import{existsSync as h}from"fs";import{readFileSync as v}from"fs";import{resolve as m}from"path";function P(J){return typeof J==="string"&&(J.startsWith("encrypted:")||J.startsWith("enc:"))}function N(J){if(!J||P(J))return;let Q=J.trim().toLowerCase();if(!/^[a-z0-9_-]+$/.test(Q))return;if(Q==="prod")return"production";if(Q==="stage")return"staging";if(Q==="dev")return"development";return Q}function y(J={}){let{path:Q=[".env"],overload:X=!1,env:$,privateKey:Z,keysFile:j=".env.keys",quiet:A=!1,cwd:Y=process.cwd()}=J,G=Array.isArray(Q)?Q:[Q],W=[],O=0,_=new Set(Object.entries(process.env).filter(([,H])=>H!==void 0&&!P(H)).map(([H])=>H)),U=Z;if(!U&&j){let H=m(Y,j);if(h(H))try{let q=v(H,"utf-8"),{parsed:B}=L(q);if($){let z=`DOTENV_PRIVATE_KEY_${$.toUpperCase()}`;U=B[z]}else U=B.DOTENV_PRIVATE_KEY}catch(q){W.push(`Failed to load keys file: ${q instanceof Error?q.message:"Unknown error"}`)}}if(!U)U=c($||"");for(let H of G){let q=m(Y,H);if(!h(q)){if(!A)W.push(`File not found: ${q}`);continue}try{let B=v(q,"utf-8"),{parsed:z,errors:D,skippedEncrypted:M}=L(B,{privateKey:U,processEnv:process.env});if(W.push(...D),M.length>0){for(let S of M)if(P(process.env[S]))delete process.env[S];let T=$?`DOTENV_PRIVATE_KEY_${$.toUpperCase()}`:"DOTENV_PRIVATE_KEY",b=M.slice(0,5).join(", "),x=M.length>5?`, \u2026 +${M.length-5} more`:"";console.warn(`[env] warning: skipped ${M.length} encrypted value(s) in ${H} (${T} not set; defaults apply): ${b}${x}`)}let I=0;for(let[T,b]of Object.entries(z)){if(T==="DOTENV_PUBLIC_KEY")continue;let x=process.env[T],S=P(x)&&b!==x;if(X||!_.has(T)||S)process.env[T]=b,O++,I++}if(!A&&!process.env.__ENV_LOADED__)console.error(`[env] loaded ${I}/${Object.keys(z).length} variables from ${H}`),process.env.__ENV_LOADED__="1"}catch(B){W.push(`Failed to load ${q}: ${B instanceof Error?B.message:"Unknown error"}`)}}return{loaded:O,errors:W}}function $J(J={}){return{name:"env-plugin",setup(Q){y(J)}}}function wJ(J={}){let Q=N(J.env)||N("development")||N(process.env.DOTENV_ENV)||N(process.env.APP_ENV)||"development",X=J.cwd||process.cwd(),$=[],Z=(Y)=>{if(!$.includes(Y)&&h(m(X,Y)))$.push(Y)};Z(".env"),Z(".env.local");let j=`.env.${Q}`,A=`.env.${Q}.local`;return Z(j),Z(A),y({...J,path:$,env:Q})}var xJ=$J;import{existsSync as F,readFileSync as C,writeFileSync as V}from"fs";import{resolve as g}from"path";function XJ(J={}){let Q=J.cwd||process.cwd(),X=g(Q,J.file||".env"),$=g(Q,J.keysFile||".env.keys");if(!F(X))return{success:!1,error:`File not found: ${X}`};try{let Z,j;if(F($)){let U=C($,"utf-8"),{parsed:H}=L(U),z=((J.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),D=z?`DOTENV_PUBLIC_KEY_${z}`:"DOTENV_PUBLIC_KEY",M=z?`DOTENV_PRIVATE_KEY_${z}`:"DOTENV_PRIVATE_KEY";if(Z=H[D]||"",j=H[M]||"",!Z||!j){let I=w();Z=I.publicKey,j=I.privateKey;let T=`
5
- ${D}="${Z}"
6
- ${M}="${j}"
7
- `;V($,U+T,"utf-8")}}else{let U=w();Z=U.publicKey,j=U.privateKey;let B=((J.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),z=B?`DOTENV_PUBLIC_KEY_${B}`:"DOTENV_PUBLIC_KEY",D=B?`DOTENV_PRIVATE_KEY_${B}`:"DOTENV_PRIVATE_KEY",M=`# .env.keys - Keep this file secure and never commit to source control
8
- ${z}="${Z}"
9
- ${D}="${j}"
10
- `;V($,M,"utf-8")}let Y=C(X,"utf-8").split(`
11
- `),G=[],W=J.file?J.file.replace(/^\.env\./,"").toUpperCase():"",O=W?`DOTENV_PUBLIC_KEY_${W}`:"DOTENV_PUBLIC_KEY";G.push("#/-------------------[DOTENV_PUBLIC_KEY]--------------------/"),G.push("#/ public-key encryption for .env files /"),G.push("#/ [how it works](https://stacksjs.com/encryption) /"),G.push("#/----------------------------------------------------------/"),G.push(`${O}="${Z}"`),G.push("");for(let U of Y){let H=U.trim();if(!H||H.startsWith("#")){G.push(U);continue}if(H.startsWith("DOTENV_PUBLIC_KEY"))continue;let q=H.match(/^([^=]+)=(.*)$/);if(!q||q[1]===void 0||q[2]===void 0){G.push(U);continue}let B=q[1].trim(),z=q[2].trim();if(z.startsWith('"')&&z.endsWith('"')||z.startsWith("'")&&z.endsWith("'"))z=z.slice(1,-1);let D=!0;if(J.key&&!B.includes(J.key))D=!1;if(J.excludeKey&&B.includes(J.excludeKey))D=!1;if(z.startsWith("encrypted:"))D=!1;if(D)z=K(z,Z);G.push(`${B}="${z}"`)}let _=G.join(`
12
- `);if(J.stdout)return{success:!0,output:_};return V(X,_,"utf-8"),{success:!0,output:`\u2714 encrypted (${J.file||".env"})
13
- \u2714 key added to ${J.keysFile||".env.keys"}`}}catch(Z){return{success:!1,error:`Failed to encrypt: ${Z instanceof Error?Z.message:"Unknown error"}`}}}function jJ(J={}){let Q=J.cwd||process.cwd(),X=g(Q,J.file||".env"),$=g(Q,J.keysFile||".env.keys");if(!F(X))return{success:!1,error:`File not found: ${X}`};if(!F($))return{success:!1,error:`Keys file not found: ${$}`};try{let Z=C($,"utf-8"),{parsed:j}=L(Z),G=((J.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),W=G?`DOTENV_PRIVATE_KEY_${G}`:"DOTENV_PRIVATE_KEY",O=j[W];if(!O)return{success:!1,error:`Private key not found: ${W}`};let U=C(X,"utf-8").split(`
14
- `),H=[];for(let B of U){let z=B.trim();if(!z||z.startsWith("#")){H.push(B);continue}if(z.startsWith("DOTENV_PUBLIC_KEY"))continue;let D=z.match(/^([^=]+)=(.*)$/);if(!D||D[1]===void 0||D[2]===void 0){H.push(B);continue}let M=D[1].trim(),I=D[2].trim();if(I.startsWith('"')&&I.endsWith('"')||I.startsWith("'")&&I.endsWith("'"))I=I.slice(1,-1);let T=I.startsWith("encrypted:");if(J.key&&!M.includes(J.key))T=!1;if(T)I=f(I,O);H.push(`${M}="${I}"`)}let q=H.join(`
15
- `);if(J.stdout)return{success:!0,output:q};return V(X,q,"utf-8"),{success:!0,output:`\u2714 decrypted (${J.file||".env"})`}}catch(Z){return{success:!1,error:`Failed to decrypt: ${Z instanceof Error?Z.message:"Unknown error"}`}}}function PJ(J,Q,X={}){let $=X.cwd||process.cwd(),Z=g($,X.file||".env"),j=g($,X.keysFile||".env.keys");try{let A="";if(F(Z))A=C(Z,"utf-8");let Y=A.split(`
16
- `),G=!1,W,U=((X.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),H=U?`DOTENV_PUBLIC_KEY_${U}`:"DOTENV_PUBLIC_KEY",q=U?`DOTENV_PRIVATE_KEY_${U}`:"DOTENV_PRIVATE_KEY";for(let D of Y){let M=D.trim();if(M.startsWith(`${H}=`)){let I=M.match(/^[^=]+=["']?([^"'\n]+)["']?/);if(I)W=I[1];break}}if(W){let D=!1;if(F(j)){let M=C(j,"utf-8"),{parsed:I}=L(M);D=Boolean(I[q])}if(!D)W=void 0}if(!X.plain&&!W){let D=w();W=D.publicKey;let M="";if(F(j))M=C(j,"utf-8");M+=`
17
- ${H}="${D.publicKey}"
18
- ${q}="${D.privateKey}"
19
- `,V(j,M,"utf-8");for(let I=Y.length-1;I>=0;I--){let T=Y[I];if(T!==void 0&&T.trim().startsWith(`${H}=`))Y.splice(I,1)}Y.unshift(`${H}="${W}"`)}let B=Q;if(!X.plain&&W)B=K(Q,W);for(let D=0;D<Y.length;D++){let M=Y[D];if(M===void 0)continue;if(M.trim().startsWith(`${J}=`)){Y[D]=`${J}="${B}"`,G=!0;break}}if(!G)Y.push(`${J}="${B}"`);let z=Y.join(`
20
- `);return V(Z,z,"utf-8"),{success:!0,output:`set ${J}${X.plain?"":" with encryption"} (${X.file||".env"})`}}catch(A){return{success:!1,error:`Failed to set: ${A instanceof Error?A.message:"Unknown error"}`}}}function KJ(J,Q={}){let X=Q.cwd||process.cwd(),$=g(X,Q.file||".env");if(!F($))return{success:!1,error:`File not found: ${$}`};try{let Z,j=g(X,Q.keysFile||".env.keys"),G=((Q.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),W=G?`DOTENV_PRIVATE_KEY_${G}`:"DOTENV_PRIVATE_KEY";if(F(j)){let q=C(j,"utf-8"),{parsed:B}=L(q);Z=B[W]}if(!Z)Z=process.env[W];let O=C($,"utf-8"),{parsed:_}=L(O,{privateKey:Z});if(J&&!Q.all){let q=_[J];if(q===void 0)return{success:!1,error:`Key not found: ${J}`};return{success:!0,output:q}}let U=Q.all?{...process.env,..._}:_,H;switch(Q.format){case"shell":H=Object.entries(U).map(([q,B])=>`${q}=${B}`).join(" ");break;case"eval":H=Object.entries(U).map(([q,B])=>`${q}="${B}"`).join(`
21
- `);break;case"json":default:H=Q.prettyPrint?JSON.stringify(U,null,2):JSON.stringify(U);break}return{success:!0,output:H}}catch(Z){return{success:!1,error:`Failed to get: ${Z instanceof Error?Z.message:"Unknown error"}`}}}function hJ(J,Q={}){let X=Q.cwd||process.cwd(),$=g(X,Q.keysFile||".env.keys");if(!F($))return{success:!1,error:`Keys file not found: ${$}`};try{let Z=C($,"utf-8"),{parsed:j}=L(Z),G=((Q.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),W=G?`DOTENV_PUBLIC_KEY_${G}`:"DOTENV_PUBLIC_KEY",O=G?`DOTENV_PRIVATE_KEY_${G}`:"DOTENV_PRIVATE_KEY";if(J){let H=j[J];if(!H)return{success:!1,error:`Key not found: ${J}`};return{success:!0,output:H}}let _={[W]:j[W],[O]:j[O]};return{success:!0,output:Q.format==="shell"?`${W}=${_[W]} ${O}=${_[O]}`:JSON.stringify(_)}}catch(Z){return{success:!1,error:`Failed to get keypair: ${Z instanceof Error?Z.message:"Unknown error"}`}}}function mJ(J={}){let Q=jJ({file:J.file,keysFile:J.keysFile,stdout:!0,cwd:J.cwd});if(!Q.success)return Q;let X=J.cwd||process.cwd(),$=g(X,J.file||".env"),Z=g(X,J.keysFile||".env.keys");if(Q.output)V($,Q.output,"utf-8");let j=w(),Y=C(Z,"utf-8").split(`
22
- `),G=J.file?J.file.replace(/^\.env\./,"").toUpperCase():"",W=G?`DOTENV_PUBLIC_KEY_${G}`:"DOTENV_PUBLIC_KEY",O=G?`DOTENV_PRIVATE_KEY_${G}`:"DOTENV_PRIVATE_KEY";for(let _=0;_<Y.length;_++){let U=Y[_];if(U===void 0)continue;if(U.startsWith(`${W}=`))Y[_]=`${W}="${j.publicKey}"`;else if(U.startsWith(`${O}=`))Y[_]=`${O}="${j.privateKey}"`}return V(Z,Y.join(`
23
- `),"utf-8"),XJ({file:J.file,keysFile:J.keysFile,key:J.key,excludeKey:J.excludeKey,stdout:J.stdout,cwd:J.cwd})}import R from"process";import{platform as GJ}from"os";var l=typeof Bun<"u",p=typeof R<"u"&&R.versions?.node!==void 0,WJ=l?"bun":p?"node":"unknown",cJ={name:WJ,version:l?Bun.version:p?R.version:void 0},d=GJ(),vJ=d==="win32",yJ=d==="darwin",lJ=d==="linux",o=Boolean(R.stdout?.isTTY),pJ=typeof globalThis.window<"u",YJ=Boolean(R.env.CI||R.env.CONTINUOUS_INTEGRATION||R.env.BUILD_NUMBER||R.env.RUN_ID),oJ=Boolean(R.env.DEBUG||R.env.VERBOSE||R.argv.includes("--debug")||R.argv.includes("--verbose")),OJ=YJ||!o,iJ=Boolean(!OJ&&(o||R.env.COLORTERM||R.env.FORCE_COLOR||R.env.TERM&&R.env.TERM!=="dumb")),k={github:{name:"GitHub Actions",detected:Boolean(R.env.GITHUB_ACTIONS)},gitlab:{name:"GitLab CI",detected:Boolean(R.env.GITLAB_CI)},circle:{name:"CircleCI",detected:Boolean(R.env.CIRCLECI)},travis:{name:"Travis CI",detected:Boolean(R.env.TRAVIS)},jenkins:{name:"Jenkins",detected:Boolean(R.env.JENKINS_URL)},vercel:{name:"Vercel",detected:Boolean(R.env.VERCEL)},netlify:{name:"Netlify",detected:Boolean(R.env.NETLIFY)},heroku:{name:"Heroku",detected:Boolean(R.env.DYNO)},aws:{name:"AWS",detected:Boolean(R.env.AWS_REGION||R.env.AWS_LAMBDA_FUNCTION_NAME)},azure:{name:"Azure",detected:Boolean(R.env.AZURE_HTTP_USER_AGENT)},cloudflare:{name:"Cloudflare",detected:Boolean(R.env.CF_PAGES)},railway:{name:"Railway",detected:Boolean(R.env.RAILWAY_ENVIRONMENT)},render:{name:"Render",detected:Boolean(R.env.RENDER)}},AJ=Object.keys(k).find((J)=>{let Q=k[J];return Q!==void 0&&Q.detected})||"unknown",nJ=k[AJ]||{name:"Unknown",detected:!1};import HJ from"process";import{projectPath as UJ}from"@stacksjs/path";import n from"fs";var i={APP_ENV:["local","dev","development","staging","prod","production"],DB_CONNECTION:["mysql","sqlite","postgres","dynamodb"],DB_MIGRATE_FRESH:["allow","confirm","disabled"],MAIL_MAILER:["smtp","mailgun","ses","log","sendgrid","mailtrap"],SEARCH_ENGINE_DRIVER:["opensearch","meilisearch","algolia","typesense"],FRONTEND_APP_ENV:["development","staging","production"]};var _J={get:(J,Q)=>{let X=J[Q],$=["_PORT","_TIMEOUT","_TTL","_SIZE","_LIMIT","_MAX","_MIN","_INTERVAL","_RETRIES","_CONCURRENCY","_WORKERS","_CONNECTIONS"];if(typeof X==="string"&&/^\d+$/.test(X)&&!X.startsWith("0")&&$.some((Z)=>Q.endsWith(Z)))return Number(X);if(typeof X==="string"){let Z=X.toLowerCase();if(Z==="true")return!0;if(Z==="false")return!1}return X}};function qJ(){return typeof Bun<"u"?Bun.env:HJ.env}var r=new Proxy(qJ(),_J);function QQ(J,Q,X){let $=X?.path||UJ(".env"),j=n.readFileSync($,"utf-8").split(`
24
- `),A=j.findIndex((W)=>W.startsWith(`${J}=`)),G=/[\s"'#$\\]/.test(Q)?`"${Q.replace(/"/g,"\\\"")}"`:Q;if(A!==-1)j[A]=`${J}=${G}`;else j.push(`${J}=${G}`);n.writeFileSync($,j.join(`
25
- `))}function ZQ(J=r){let Q=[];for(let[X,$]of Object.entries(i)){let Z=J[X];if(Z!==void 0&&Z!==""&&!$.includes(String(Z)))Q.push(`${X}="${Z}" is not valid. Allowed values: ${$.join(", ")}`)}return Q}function $Q(J,Q=r){let X=[];for(let $ of J){let Z=Q[$];if(Z===void 0||Z===""||Z===null)X.push($)}if(X.length>0)throw Error(`[env] Missing required environment variable(s): ${X.join(", ")}. Set them in .env or your process environment before booting.`);return Q}export{QQ as writeEnv,ZQ as validateEnv,PJ as setEnv,cJ as runtimeInfo,WJ as runtime,mJ as rotateKeypair,$Q as requireEnv,nJ as providerInfo,AJ as provider,qJ as process,d as platform,DJ as parseEnvFromKey,L as parse,IJ as loadEnvFiles,y as loadEnv,vJ as isWindows,p as isNode,OJ as isMinimal,yJ as isMacOS,lJ as isLinux,oJ as isDebug,iJ as isColorSupported,YJ as isCI,l as isBun,pJ as hasWindow,o as hasTTY,c as getPrivateKey,hJ as getKeypair,KJ as getEnv,w as generateKeypair,$J as envPlugin,i as envEnum,r as env,K as encryptValue,XJ as encryptEnv,f as decryptValue,jJ as decryptEnv,wJ as autoLoadEnv,t as aesEncrypt,e as aesDecrypt};
2
+ import{createCipheriv as e,createDecipheriv as ff,createHash as k,createPrivateKey as xf,createPublicKey as gf,diffieHellman as Bf,generateKeyPairSync as $f,hkdfSync as Af,randomBytes as d}from"crypto";var t="x25519-public:",r="x25519-private:",c="encrypted:v2:",n="encrypted:",_f=Buffer.from("stacks-env:v2","utf8"),Xf="Environment decryption failed: authentication or format error";function Df(f){if(f.length===32)return f;return k("sha256").update(f).digest()}function vf(f,g){let B=d(16),$=e("aes-256-gcm",Df(g),B);return{ciphertext:Buffer.concat([$.update(f,"utf8"),$.final()]).toString("hex"),iv:B.toString("hex"),authTag:$.getAuthTag().toString("hex")}}function Ef(f,g,B,$){try{let A=ff("aes-256-gcm",Df(g),Buffer.from(B,"hex"));return A.setAuthTag(Buffer.from($,"hex")),Buffer.concat([A.update(Buffer.from(f,"hex")),A.final()]).toString("utf8")}catch{throw Error("Decryption failed: authentication or format error")}}function z(f){return f.toString("base64url")}function C(f,g,B){if(typeof f!=="string"||f.length>0&&!/^[A-Za-z0-9_-]+$/.test(f))throw Error(`${g} is not canonical base64url`);let $=Buffer.from(f,"base64url");if(z($)!==f)throw Error(`${g} is not canonical base64url`);if(B!==void 0&&$.length!==B)throw Error(`${g} has an invalid length`);return $}function Uf(f){if(!f.startsWith(t))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let g=gf({key:C(f.slice(t.length),"public key"),format:"der",type:"spki"});if(g.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return g}function qf(f){if(!f.startsWith(r))throw Error("Private key is not a version 2 X25519 key");let g=xf({key:C(f.slice(r.length),"private key"),format:"der",type:"pkcs8"});if(g.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return g}function Hf(f){return Buffer.from(`stacks-env:v2;${f.epk};${f.salt};${f.nonce}`,"utf8")}function F(){let{publicKey:f,privateKey:g}=$f("x25519"),B=f.export({format:"der",type:"spki"}),$=g.export({format:"der",type:"pkcs8"});return{publicKey:`${t}${z(B)}`,privateKey:`${r}${z($)}`}}function P(f,g){let B=Uf(g),$=$f("x25519"),A=z($.publicKey.export({format:"der",type:"spki"})),_=d(16),R=d(12),O=Bf({privateKey:$.privateKey,publicKey:B}),H=Buffer.from(Af("sha256",O,_,_f,32)),X={epk:A,salt:z(_),nonce:z(R)};try{let D=e("aes-256-gcm",H,R);D.setAAD(Hf(X));let T=Buffer.concat([D.update(f,"utf8"),D.final()]),G={v:2,...X,ciphertext:z(T),tag:z(D.getAuthTag())};return`${c}${z(Buffer.from(JSON.stringify(G),"utf8"))}`}finally{O.fill(0),H.fill(0)}}function Lf(f){let g=C(f.slice(c.length),"envelope").toString("utf8"),B=JSON.parse(g);if(!B||typeof B!=="object"||Array.isArray(B))throw Error("envelope is not an object");let $=Object.keys(B).sort(),A=["ciphertext","epk","nonce","salt","tag","v"];if($.length!==A.length||$.some((_,R)=>_!==A[R]))throw Error("envelope fields are invalid");if(B.v!==2)throw Error("envelope version is unsupported");for(let _ of["epk","salt","nonce","ciphertext","tag"])if(typeof B[_]!=="string")throw Error(`${_} is invalid`);return B}function Mf(f,g){try{let B=Lf(f),$=qf(g),A=gf({key:C(B.epk,"ephemeral public key"),format:"der",type:"spki"});if(A.asymmetricKeyType!=="x25519")throw Error("ephemeral key is not X25519");let _=C(B.salt,"salt",16),R=C(B.nonce,"nonce",12),O=C(B.ciphertext,"ciphertext"),H=C(B.tag,"tag",16),X=Bf({privateKey:$,publicKey:A}),D=Buffer.from(Af("sha256",X,_,_f,32));try{let T=ff("aes-256-gcm",D,R);return T.setAAD(Hf(B)),T.setAuthTag(H),Buffer.concat([T.update(O),T.final()]).toString("utf8")}finally{X.fill(0),D.fill(0)}}catch{throw Error(Xf)}}function zf(f,g){try{if(!/^[0-9a-f]{64}$/.test(g))throw Error("invalid legacy private key");let B=Buffer.from(f.slice(n.length),"base64");if(B.length<32)throw Error("invalid legacy payload");let $=k("sha256").update(Buffer.from(g,"hex")).digest(),A=k("sha256").update($).digest();return Ef(B.subarray(32).toString("hex"),A,B.subarray(0,16).toString("hex"),B.subarray(16,32).toString("hex"))}catch{throw Error(Xf)}}function of(f){return f.startsWith(n)&&!f.startsWith(c)}function h(f,g){if(!f.startsWith(n))return f;return f.startsWith(c)?Mf(f,g):zf(f,g)}function pf(f,g,B){return P(h(f,g),B)}function lf(f){let g=f.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return g&&g[1]?g[1].toLowerCase():""}function i(f=""){return f?process.env[`DOTENV_PRIVATE_KEY_${f.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}function U(f,g={}){let B={},$=[],A=[],_=f.split(`
3
+ `);for(let R of _){let O=R.trim();if(!O||O.startsWith("#"))continue;if(O.startsWith("DOTENV_PUBLIC_KEY=")){let T=O.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(T&&T[1]!==void 0)B.DOTENV_PUBLIC_KEY=T[1];continue}let H=O.match(/^([^=]+)=(.*)$/);if(!H||H[1]===void 0||H[2]===void 0)continue;let X=H[1].trim(),D=H[2].trim();if(D.startsWith('"')&&D.endsWith('"')||D.startsWith("'")&&D.endsWith("'")){if(D=D.slice(1,-1),D.includes("\\n"))D=D.replace(/\\n/g,`
4
+ `)}if(D.startsWith("encrypted:")||D.startsWith("enc:")){if(!g.privateKey){A.push(X);continue}try{let T=D.startsWith("enc:")?`encrypted:${D.slice(4)}`:D;D=h(T,g.privateKey)}catch(T){$.push(`Failed to decrypt ${X}: ${T instanceof Error?T.message:"Unknown error"}`)}}D=Vf(D,{...g.processEnv||process.env,...B}),D=bf(D),B[X]=D}return{parsed:B,errors:$,skippedEncrypted:A}}function Vf(f,g){return f.replace(/\$\{([^}]+)\}/g,(B,$)=>{let A=$.match(/^([^:\-+]+)(:-|-)(.+)$/);if(A){let[,R,O,H]=A,X=g[R];if(O===":-")return X||H;else return X!==void 0?X:H}let _=$.match(/^([^:\-+]+)(:?\+)(.+)$/);if(_){let[,R,O,H]=_,X=g[R];if(O===":+")return X?H:"";else return X!==void 0?H:""}return g[$]||""})}var Cf=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function bf(f){return f.replace(/\$\(([^)]+)\)/g,(g,B)=>{try{let $=B.trim().split(/\s+/),A=$[0];if(!A||!Cf.has(A))return console.warn(`[env] Blocked command substitution for disallowed command: ${A}`),"";let _=Bun.spawnSync($,{stdout:"pipe",stderr:"pipe"});if(_.exitCode===0)return new TextDecoder().decode(_.stdout).trim();console.warn(`[env] Command substitution failed (exit code ${_.exitCode}): ${A}`)}catch($){console.warn(`[env] Command substitution error: ${$ instanceof Error?$.message:String($)}`)}return""})}async function ef(f,g={}){let B={},$=[],A=[];for(let _ of f)try{let R=Bun.file(_);if(!R.size)continue;let O=await R.text(),{parsed:H,errors:X,skippedEncrypted:D}=U(O,g);$.push(...X),A.push(...D);for(let[T,G]of Object.entries(H)){if(!g.overload&&B[T]!==void 0)continue;B[T]=G}}catch(R){if(R?.code==="ENOENT")continue;$.push(`Failed to read ${_}: ${R instanceof Error?R.message:String(R)}`)}return{parsed:B,errors:$,skippedEncrypted:A}}import{existsSync as m}from"fs";import{readFileSync as y}from"fs";import{resolve as u}from"path";function S(f){return typeof f==="string"&&(f.startsWith("encrypted:")||f.startsWith("enc:"))}function b(f){if(!f||S(f))return;let g=f.trim().toLowerCase();if(!/^[a-z0-9_-]+$/.test(g))return;if(g==="prod")return"production";if(g==="stage")return"staging";if(g==="dev")return"development";return g}var v=null,o;function hf(f,g,B=".env.keys"){let $=u(g,B);if(!m($))return;try{let{parsed:A}=U(y($,"utf-8"));if(f){let _=A[`DOTENV_PRIVATE_KEY_${f.toUpperCase()}`];if(_)return _}return A.DOTENV_PRIVATE_KEY}catch{return}}function Ff(f={}){let g=b(f.env)||b(process.env.APP_ENV)||b("development")||b(process.env.DOTENV_ENV)||"development",B=f.cwd||process.cwd();if(v===g)return o;let $=i(g);if(!$)$=process.env.DOTENV_PRIVATE_KEY;if(!$)$=hf(g,B);return v=g,o=$,$}function Dg(){v=null,o=void 0}function Of(f,g={}){if(!S(f))return f;let B=Ff(g);if(!B)return;try{let $=f.startsWith("enc:")?`encrypted:${f.slice(4)}`:f;return h($,B)}catch{return}}function Rf(f={}){let{path:g=[".env"],overload:B=!1,env:$,privateKey:A,keysFile:_=".env.keys",quiet:R=!1,cwd:O=process.cwd()}=f,H=Array.isArray(g)?g:[g],X=[],D=0,T=new Set(Object.entries(process.env).filter(([,W])=>W!==void 0&&!S(W)).map(([W])=>W)),G=A;if(!G&&_){let W=u(O,_);if(m(W))try{let Y=y(W,"utf-8"),{parsed:w}=U(Y);if($){let x=`DOTENV_PRIVATE_KEY_${$.toUpperCase()}`;G=w[x]}else G=w.DOTENV_PRIVATE_KEY}catch(Y){X.push(`Failed to load keys file: ${Y instanceof Error?Y.message:"Unknown error"}`)}}if(!G)G=i($||"");for(let W of H){let Y=u(O,W);if(!m(Y)){if(!R)X.push(`File not found: ${Y}`);continue}try{let w=y(Y,"utf-8"),{parsed:x,errors:I,skippedEncrypted:Z}=U(w,{privateKey:G,processEnv:process.env});if(X.push(...I),Z.length>0){for(let K of Z)if(S(process.env[K]))delete process.env[K];let Q=$?`DOTENV_PRIVATE_KEY_${$.toUpperCase()}`:"DOTENV_PRIVATE_KEY",V=Z.slice(0,5).join(", "),N=Z.length>5?`, \u2026 +${Z.length-5} more`:"";console.warn(`[env] warning: skipped ${Z.length} encrypted value(s) in ${W} (${Q} not set; defaults apply): ${V}${N}`)}let J=0;for(let[Q,V]of Object.entries(x)){if(Q==="DOTENV_PUBLIC_KEY")continue;let N=process.env[Q],K=S(N)&&V!==N;if(B||!T.has(Q)||K)process.env[Q]=V,D++,J++}if(!R&&!process.env.__ENV_LOADED__)console.error(`[env] loaded ${J}/${Object.keys(x).length} variables from ${W}`),process.env.__ENV_LOADED__="1"}catch(w){X.push(`Failed to load ${Y}: ${w instanceof Error?w.message:"Unknown error"}`)}}return{loaded:D,errors:X}}function Sf(f={}){return{name:"env-plugin",setup(g){Rf(f)}}}function Hg(f={}){let g=b(f.env)||b("development")||b(process.env.DOTENV_ENV)||b(process.env.APP_ENV)||"development",B=f.cwd||process.cwd(),$=[],A=(O)=>{if(!$.includes(O)&&m(u(B,O)))$.push(O)};A(".env"),A(".env.local");let _=`.env.${g}`,R=`.env.${g}.local`;return A(_),A(R),Rf({...f,path:$,env:g})}var Rg=Sf;import{existsSync as L,readFileSync as q,renameSync as p,rmSync as Tf,writeFileSync as M}from"fs";import{basename as l,dirname as Wf,resolve as E}from"path";function Yf(f,g,B,$){let A=["#/-------------------[DOTENV_PUBLIC_KEY]--------------------/","#/ versioned X25519 encryption for .env files /","#/ [how it works](https://stacksjs.com/encryption) /","#/----------------------------------------------------------/",`${B}="${g}"`,""];for(let _ of f.split(`
5
+ `)){let R=_.trim();if(!R||R.startsWith("#")){A.push(_);continue}if(R.startsWith("DOTENV_PUBLIC_KEY"))continue;let O=R.match(/^([^=]+)=(.*)$/);if(!O||O[1]===void 0||O[2]===void 0){A.push(_);continue}let H=O[1].trim(),X=O[2].trim();if(X.startsWith('"')&&X.endsWith('"')||X.startsWith("'")&&X.endsWith("'"))X=X.slice(1,-1);if((!$.key||H.includes($.key))&&(!$.excludeKey||!H.includes($.excludeKey))&&!X.startsWith("encrypted:"))X=P(X,g);A.push(`${H}="${X}"`)}return A.join(`
6
+ `)}function Gg(f={}){let g=f.cwd||process.cwd(),B=E(g,f.file||".env"),$=E(g,f.keysFile||".env.keys");if(!L(B))return{success:!1,error:`File not found: ${B}`};try{let A,_;if(L($)){let D=q($,"utf-8"),{parsed:T}=U(D),Y=((f.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),w=Y?`DOTENV_PUBLIC_KEY_${Y}`:"DOTENV_PUBLIC_KEY",x=Y?`DOTENV_PRIVATE_KEY_${Y}`:"DOTENV_PRIVATE_KEY";if(A=T[w]||"",_=T[x]||"",!A||!_){let I=F();A=I.publicKey,_=I.privateKey;let Z=`
7
+ ${w}="${A}"
8
+ ${x}="${_}"
9
+ `;M($,D+Z,"utf-8")}}else{let D=F();A=D.publicKey,_=D.privateKey;let W=((f.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),Y=W?`DOTENV_PUBLIC_KEY_${W}`:"DOTENV_PUBLIC_KEY",w=W?`DOTENV_PRIVATE_KEY_${W}`:"DOTENV_PRIVATE_KEY",x=`# .env.keys - Keep this file secure and never commit to source control
10
+ ${Y}="${A}"
11
+ ${w}="${_}"
12
+ `;M($,x,"utf-8")}let R=q(B,"utf-8"),O=f.file?f.file.replace(/^\.env\./,"").toUpperCase():"",H=O?`DOTENV_PUBLIC_KEY_${O}`:"DOTENV_PUBLIC_KEY",X=Yf(R,A,H,f);if(f.stdout)return{success:!0,output:X};return M(B,X,"utf-8"),{success:!0,output:`\u2714 encrypted (${f.file||".env"})
13
+ \u2714 key added to ${f.keysFile||".env.keys"}`}}catch(A){return{success:!1,error:`Failed to encrypt: ${A instanceof Error?A.message:"Unknown error"}`}}}function Nf(f={}){let g=f.cwd||process.cwd(),B=E(g,f.file||".env"),$=E(g,f.keysFile||".env.keys");if(!L(B))return{success:!1,error:`File not found: ${B}`};if(!L($))return{success:!1,error:`Keys file not found: ${$}`};try{let A=q($,"utf-8"),{parsed:_}=U(A),H=((f.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),X=H?`DOTENV_PRIVATE_KEY_${H}`:"DOTENV_PRIVATE_KEY",D=_[X];if(!D)return{success:!1,error:`Private key not found: ${X}`};let G=q(B,"utf-8").split(`
14
+ `),W=[];for(let w of G){let x=w.trim();if(!x||x.startsWith("#")){W.push(w);continue}if(x.startsWith("DOTENV_PUBLIC_KEY"))continue;let I=x.match(/^([^=]+)=(.*)$/);if(!I||I[1]===void 0||I[2]===void 0){W.push(w);continue}let Z=I[1].trim(),J=I[2].trim();if(J.startsWith('"')&&J.endsWith('"')||J.startsWith("'")&&J.endsWith("'"))J=J.slice(1,-1);let Q=J.startsWith("encrypted:");if(f.key&&!Z.includes(f.key))Q=!1;if(Q)J=h(J,D);W.push(`${Z}="${J}"`)}let Y=W.join(`
15
+ `);if(f.stdout)return{success:!0,output:Y};return M(B,Y,"utf-8"),{success:!0,output:`\u2714 decrypted (${f.file||".env"})`}}catch(A){return{success:!1,error:`Failed to decrypt: ${A instanceof Error?A.message:"Unknown error"}`}}}function Jg(f,g,B={}){let $=B.cwd||process.cwd(),A=E($,B.file||".env"),_=E($,B.keysFile||".env.keys");try{let R="";if(L(A))R=q(A,"utf-8");let O=R.split(`
16
+ `),H=!1,X,G=((B.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),W=G?`DOTENV_PUBLIC_KEY_${G}`:"DOTENV_PUBLIC_KEY",Y=G?`DOTENV_PRIVATE_KEY_${G}`:"DOTENV_PRIVATE_KEY";for(let I of O){let Z=I.trim();if(Z.startsWith(`${W}=`)){let J=Z.match(/^[^=]+=["']?([^"'\n]+)["']?/);if(J)X=J[1];break}}if(X){let I=!1;if(L(_)){let Z=q(_,"utf-8"),{parsed:J}=U(Z);I=Boolean(J[Y])}if(!I)X=void 0}if(!B.plain&&!X){let I=F();X=I.publicKey;let Z="";if(L(_))Z=q(_,"utf-8");Z+=`
17
+ ${W}="${I.publicKey}"
18
+ ${Y}="${I.privateKey}"
19
+ `,M(_,Z,"utf-8");for(let J=O.length-1;J>=0;J--){let Q=O[J];if(Q!==void 0&&Q.trim().startsWith(`${W}=`))O.splice(J,1)}O.unshift(`${W}="${X}"`)}let w=g;if(!B.plain&&X)w=P(g,X);for(let I=0;I<O.length;I++){let Z=O[I];if(Z===void 0)continue;if(Z.trim().startsWith(`${f}=`)){O[I]=`${f}="${w}"`,H=!0;break}}if(!H)O.push(`${f}="${w}"`);let x=O.join(`
20
+ `);return M(A,x,"utf-8"),{success:!0,output:`set ${f}${B.plain?"":" with encryption"} (${B.file||".env"})`}}catch(R){return{success:!1,error:`Failed to set: ${R instanceof Error?R.message:"Unknown error"}`}}}function wg(f,g={}){let B=g.cwd||process.cwd(),$=E(B,g.file||".env");if(!L($))return{success:!1,error:`File not found: ${$}`};try{let A,_=E(B,g.keysFile||".env.keys"),H=((g.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),X=H?`DOTENV_PRIVATE_KEY_${H}`:"DOTENV_PRIVATE_KEY";if(L(_)){let Y=q(_,"utf-8"),{parsed:w}=U(Y);A=w[X]}if(!A)A=process.env[X];let D=q($,"utf-8"),{parsed:T}=U(D,{privateKey:A});if(f&&!g.all){let Y=T[f];if(Y===void 0)return{success:!1,error:`Key not found: ${f}`};return{success:!0,output:Y}}let G=g.all?{...process.env,...T}:T,W;switch(g.format){case"shell":W=Object.entries(G).map(([Y,w])=>`${Y}=${w}`).join(" ");break;case"eval":W=Object.entries(G).map(([Y,w])=>`${Y}="${w}"`).join(`
21
+ `);break;case"json":default:W=g.prettyPrint?JSON.stringify(G,null,2):JSON.stringify(G);break}return{success:!0,output:W}}catch(A){return{success:!1,error:`Failed to get: ${A instanceof Error?A.message:"Unknown error"}`}}}function Ig(f,g={}){let B=g.cwd||process.cwd(),$=E(B,g.keysFile||".env.keys");if(!L($))return{success:!1,error:`Keys file not found: ${$}`};try{let A=q($,"utf-8"),{parsed:_}=U(A),H=((g.file||".env").split("/").pop()||"").replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),X=H?`DOTENV_PUBLIC_KEY_${H}`:"DOTENV_PUBLIC_KEY",D=H?`DOTENV_PRIVATE_KEY_${H}`:"DOTENV_PRIVATE_KEY";if(f){let W=_[f];if(!W)return{success:!1,error:`Key not found: ${f}`};return{success:!0,output:W}}let T={[X]:_[X],[D]:_[D]};return{success:!0,output:g.format==="shell"?`${X}=${T[X]} ${D}=${T[D]}`:JSON.stringify(T)}}catch(A){return{success:!1,error:`Failed to get keypair: ${A instanceof Error?A.message:"Unknown error"}`}}}function Zg(f={}){let g=Nf({file:f.file,keysFile:f.keysFile,stdout:!0,cwd:f.cwd});if(!g.success)return g;let B=f.cwd||process.cwd(),$=E(B,f.file||".env"),A=E(B,f.keysFile||".env.keys");if(g.output===void 0)return{success:!1,error:"Rotation produced no decrypted content"};let _=F(),R=q($),O=q(A),H=O.toString("utf8").split(`
22
+ `),X=f.file||".env",D=l(X).replace(/^\.env\./,"").replace(/^\.env$/,"").toUpperCase(),T=D?`DOTENV_PUBLIC_KEY_${D}`:"DOTENV_PUBLIC_KEY",G=D?`DOTENV_PRIVATE_KEY_${D}`:"DOTENV_PRIVATE_KEY",W=!1,Y=!1;for(let Q=0;Q<H.length;Q++){let V=H[Q];if(V===void 0)continue;if(V.startsWith(`${T}=`))H[Q]=`${T}="${_.publicKey}"`,W=!0;else if(V.startsWith(`${G}=`))H[Q]=`${G}="${_.privateKey}"`,Y=!0}if(!W)H.push(`${T}="${_.publicKey}"`);if(!Y)H.push(`${G}="${_.privateKey}"`);let w=`${H.join(`
23
+ `).replace(/\n+$/,"")}
24
+ `,x=Yf(g.output,_.publicKey,T,f),I=`${process.pid}-${Date.now()}`,Z=E(Wf($),`.${l($)}.${I}.rotate`),J=E(Wf(A),`.${l(A)}.${I}.rotate`);try{if(M(Z,x,{encoding:"utf8",mode:384}),M(J,w,{encoding:"utf8",mode:384}),f.stdout)return p(J,A),{success:!0,output:x};p(Z,$);try{p(J,A)}catch(Q){throw M($,R),Q}return{success:!0,output:`\u2714 rotated (${f.file||".env"}) to encrypted:v2`}}catch(Q){if(!L(A))M(A,O,{mode:384});return{success:!1,error:`Failed to rotate without exposing plaintext: ${Q instanceof Error?Q.message:"Unknown error"}`}}finally{Tf(Z,{force:!0}),Tf(J,{force:!0})}}import j from"process";import{platform as Kf}from"os";var jf=typeof Bun<"u",Gf=typeof j<"u"&&j.versions?.node!==void 0,cf=jf?"bun":Gf?"node":"unknown",Ug={name:cf,version:jf?Bun.version:Gf?j.version:void 0},a=Kf(),qg=a==="win32",Lg=a==="darwin",Mg=a==="linux",Jf=Boolean(j.stdout?.isTTY),zg=typeof globalThis.window<"u",Pf=Boolean(j.env.CI||j.env.CONTINUOUS_INTEGRATION||j.env.BUILD_NUMBER||j.env.RUN_ID),Vg=Boolean(j.env.DEBUG||j.env.VERBOSE||j.argv.includes("--debug")||j.argv.includes("--verbose")),mf=Pf||!Jf,Cg=Boolean(!mf&&(Jf||j.env.COLORTERM||j.env.FORCE_COLOR||j.env.TERM&&j.env.TERM!=="dumb")),s={github:{name:"GitHub Actions",detected:Boolean(j.env.GITHUB_ACTIONS)},gitlab:{name:"GitLab CI",detected:Boolean(j.env.GITLAB_CI)},circle:{name:"CircleCI",detected:Boolean(j.env.CIRCLECI)},travis:{name:"Travis CI",detected:Boolean(j.env.TRAVIS)},jenkins:{name:"Jenkins",detected:Boolean(j.env.JENKINS_URL)},vercel:{name:"Vercel",detected:Boolean(j.env.VERCEL)},netlify:{name:"Netlify",detected:Boolean(j.env.NETLIFY)},heroku:{name:"Heroku",detected:Boolean(j.env.DYNO)},aws:{name:"AWS",detected:Boolean(j.env.AWS_REGION||j.env.AWS_LAMBDA_FUNCTION_NAME)},azure:{name:"Azure",detected:Boolean(j.env.AZURE_HTTP_USER_AGENT)},cloudflare:{name:"Cloudflare",detected:Boolean(j.env.CF_PAGES)},railway:{name:"Railway",detected:Boolean(j.env.RAILWAY_ENVIRONMENT)},render:{name:"Render",detected:Boolean(j.env.RENDER)}},uf=Object.keys(s).find((f)=>{let g=s[f];return g!==void 0&&g.detected})||"unknown",bg=s[uf]||{name:"Unknown",detected:!1};import kf from"process";import{projectPath as df}from"@stacksjs/path";import If from"fs";var wf={APP_ENV:["local","dev","development","staging","prod","production"],DB_CONNECTION:["mysql","sqlite","postgres","dynamodb"],DB_MIGRATE_FRESH:["allow","confirm","disabled"],MAIL_MAILER:["smtp","mailgun","ses","log","sendgrid","mailtrap"],SEARCH_ENGINE_DRIVER:["opensearch","meilisearch","algolia","typesense"],FRONTEND_APP_ENV:["development","staging","production"]};var Zf=new Set;function tf(f){if(Zf.has(f))return;Zf.add(f),console.warn(`[env] warning: "${f}" is encrypted but no usable private key was found to decrypt it; falling back to its default. Set DOTENV_PRIVATE_KEY_<ENV> or ship .env.keys.`)}var rf={get:(f,g)=>{let B=f[g];if(typeof B==="string"&&(B.startsWith("encrypted:")||B.startsWith("enc:"))){let A=Of(B);if(A===void 0){tf(g),delete f[g];return}f[g]=A,B=A}let $=["_PORT","_TIMEOUT","_TTL","_SIZE","_LIMIT","_MAX","_MIN","_INTERVAL","_RETRIES","_CONCURRENCY","_WORKERS","_CONNECTIONS"];if(typeof B==="string"&&/^\d+$/.test(B)&&!B.startsWith("0")&&$.some((A)=>g.endsWith(A)))return Number(B);if(typeof B==="string"){let A=B.toLowerCase();if(A==="true")return!0;if(A==="false")return!1}return B}};function nf(){return typeof Bun<"u"?Bun.env:kf.env}var Qf=new Proxy(nf(),rf);function mg(f,g,B){let $=B?.path||df(".env"),_=If.readFileSync($,"utf-8").split(`
25
+ `),R=_.findIndex((X)=>X.startsWith(`${f}=`)),H=/[\s"'#$\\]/.test(g)?`"${g.replace(/"/g,"\\\"")}"`:g;if(R!==-1)_[R]=`${f}=${H}`;else _.push(`${f}=${H}`);If.writeFileSync($,_.join(`
26
+ `))}function ug(f=Qf){let g=[];for(let[B,$]of Object.entries(wf)){let A=f[B];if(A!==void 0&&A!==""&&!$.includes(String(A)))g.push(`${B}="${A}" is not valid. Allowed values: ${$.join(", ")}`)}return g}function kg(f,g=Qf){let B=[];for(let $ of f){let A=g[$];if(A===void 0||A===""||A===null)B.push($)}if(B.length>0)throw Error(`[env] Missing required environment variable(s): ${B.join(", ")}. Set them in .env or your process environment before booting.`);return g}export{mg as writeEnv,ug as validateEnv,Jg as setEnv,Ug as runtimeInfo,cf as runtime,Zg as rotateKeypair,Ff as resolvePrivateKey,Dg as resetPrivateKeyCache,kg as requireEnv,bg as providerInfo,uf as provider,nf as process,a as platform,lf as parseEnvFromKey,U as parse,pf as migrateEncryptedValue,ef as loadEnvFiles,Rf as loadEnv,qg as isWindows,Gf as isNode,mf as isMinimal,Lg as isMacOS,Mg as isLinux,of as isLegacyEncryptedValue,Vg as isDebug,Cg as isColorSupported,Pf as isCI,jf as isBun,zg as hasWindow,Jf as hasTTY,i as getPrivateKey,Ig as getKeypair,wg as getEnv,F as generateKeypair,Sf as envPlugin,wf as envEnum,Qf as env,P as encryptValue,Gg as encryptEnv,h as decryptValue,Of as decryptEnvValue,Nf as decryptEnv,Hg as autoLoadEnv,vf as aesEncrypt,Ef as aesDecrypt};
package/dist/parser.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // @bun
2
- import{createCipheriv as A,createDecipheriv as O,createHash as w,randomBytes as z}from"crypto";function _(F,j){let R=z(16),q=j.length===32?j:Buffer.from(j.toString("hex").slice(0,64),"hex"),J=A("aes-256-gcm",q,R),I=Buffer.concat([J.update(F,"utf8"),J.final()]),U=J.getAuthTag();return{ciphertext:I.toString("hex"),iv:R.toString("hex"),authTag:U.toString("hex")}}function C(F,j,R,q){let J=j.length===32?j:Buffer.from(j.toString("hex").slice(0,64),"hex");try{let I=O("aes-256-gcm",J,Buffer.from(R,"hex"));return I.setAuthTag(Buffer.from(q,"hex")),Buffer.concat([I.update(Buffer.from(F,"hex")),I.final()]).toString("utf8")}catch(I){throw Error(`Decryption failed (data may be corrupted or key is incorrect): ${I instanceof Error?I.message:String(I)}`)}}function D(){let F=z(32);return{publicKey:w("sha256").update(F).digest().toString("hex"),privateKey:F.toString("hex")}}function H(F,j){let R=w("sha256").update(Buffer.from(j,"hex")).digest(),{ciphertext:q,iv:J,authTag:I}=_(F,R);return`encrypted:${Buffer.concat([Buffer.from(J,"hex"),Buffer.from(I,"hex"),Buffer.from(q,"hex")]).toString("base64")}`}function G(F,j){if(!F.startsWith("encrypted:"))return F;let R=F.slice(10),q=Buffer.from(R,"base64");if(q.length<32)throw Error("Invalid encrypted data: payload too short (need at least iv + authTag = 32 bytes)");let J=q.subarray(0,16).toString("hex"),I=q.subarray(16,32).toString("hex"),U=q.subarray(32).toString("hex"),Y=w("sha256").update(Buffer.from(j,"hex")).digest(),X=w("sha256").update(Y).digest();return C(U,X,J,I)}function T(F){let j=F.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return j&&j[1]?j[1].toLowerCase():""}function M(F=""){if(!F)return process.env.DOTENV_PRIVATE_KEY;let j=`DOTENV_PRIVATE_KEY_${F.toUpperCase()}`;return process.env[j]}function L(F,j={}){let R={},q=[],J=[],I=F.split(`
3
- `);for(let U of I){let Y=U.trim();if(!Y||Y.startsWith("#"))continue;if(Y.startsWith("DOTENV_PUBLIC_KEY=")){let $=Y.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if($&&$[1]!==void 0)R.DOTENV_PUBLIC_KEY=$[1];continue}let X=Y.match(/^([^=]+)=(.*)$/);if(!X||X[1]===void 0||X[2]===void 0)continue;let Z=X[1].trim(),Q=X[2].trim();if(Q.startsWith('"')&&Q.endsWith('"')||Q.startsWith("'")&&Q.endsWith("'")){if(Q=Q.slice(1,-1),Q.includes("\\n"))Q=Q.replace(/\\n/g,`
4
- `)}if(Q.startsWith("encrypted:")||Q.startsWith("enc:")){if(!j.privateKey){J.push(Z);continue}try{let $=Q.startsWith("enc:")?`encrypted:${Q.slice(4)}`:Q;Q=G($,j.privateKey)}catch($){q.push(`Failed to decrypt ${Z}: ${$ instanceof Error?$.message:"Unknown error"}`)}}Q=P(Q,{...j.processEnv||process.env,...R}),Q=S(Q),R[Z]=Q}return{parsed:R,errors:q,skippedEncrypted:J}}function P(F,j){return F.replace(/\$\{([^}]+)\}/g,(R,q)=>{let J=q.match(/^([^:\-+]+)(:-|-)(.+)$/);if(J){let[,U,Y,X]=J,Z=j[U];if(Y===":-")return Z||X;else return Z!==void 0?Z:X}let I=q.match(/^([^:\-+]+)(:?\+)(.+)$/);if(I){let[,U,Y,X]=I,Z=j[U];if(Y===":+")return Z?X:"";else return Z!==void 0?X:""}return j[q]||""})}var B=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function S(F){return F.replace(/\$\(([^)]+)\)/g,(j,R)=>{try{let q=R.trim().split(/\s+/),J=q[0];if(!J||!B.has(J))return console.warn(`[env] Blocked command substitution for disallowed command: ${J}`),"";let I=Bun.spawnSync(q,{stdout:"pipe",stderr:"pipe"});if(I.exitCode===0)return new TextDecoder().decode(I.stdout).trim();console.warn(`[env] Command substitution failed (exit code ${I.exitCode}): ${J}`)}catch(q){console.warn(`[env] Command substitution error: ${q instanceof Error?q.message:String(q)}`)}return""})}async function E(F,j={}){let R={},q=[],J=[];for(let I of F)try{let U=Bun.file(I);if(!U.size)continue;let Y=await U.text(),{parsed:X,errors:Z,skippedEncrypted:Q}=L(Y,j);q.push(...Z),J.push(...Q);for(let[$,W]of Object.entries(X)){if(!j.overload&&R[$]!==void 0)continue;R[$]=W}}catch(U){if(U?.code==="ENOENT")continue;q.push(`Failed to read ${I}: ${U instanceof Error?U.message:String(U)}`)}return{parsed:R,errors:q,skippedEncrypted:J}}export{L as parse,E as loadEnvFiles};
2
+ import{createCipheriv as b,createDecipheriv as B,createHash as d,createPrivateKey as I,createPublicKey as P,diffieHellman as T,generateKeyPairSync as $,hkdfSync as A,randomBytes as w}from"crypto";var m="x25519-public:",x="x25519-private:",u="encrypted:v2:",K="encrypted:",R=Buffer.from("stacks-env:v2","utf8"),_="Environment decryption failed: authentication or format error";function S(t){if(t.length===32)return t;return d("sha256").update(t).digest()}function V(t,r){let e=w(16),n=b("aes-256-gcm",S(r),e);return{ciphertext:Buffer.concat([n.update(t,"utf8"),n.final()]).toString("hex"),iv:e.toString("hex"),authTag:n.getAuthTag().toString("hex")}}function F(t,r,e,n){try{let o=B("aes-256-gcm",S(r),Buffer.from(e,"hex"));return o.setAuthTag(Buffer.from(n,"hex")),Buffer.concat([o.update(Buffer.from(t,"hex")),o.final()]).toString("utf8")}catch{throw Error("Decryption failed: authentication or format error")}}function y(t){return t.toString("base64url")}function a(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(y(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 X(t){if(!t.startsWith(m))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let r=P({key:a(t.slice(m.length),"public key"),format:"der",type:"spki"});if(r.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return r}function O(t){if(!t.startsWith(x))throw Error("Private key is not a version 2 X25519 key");let r=I({key:a(t.slice(x.length),"private key"),format:"der",type:"pkcs8"});if(r.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return r}function D(t){return Buffer.from(`stacks-env:v2;${t.epk};${t.salt};${t.nonce}`,"utf8")}function G(){let{publicKey:t,privateKey:r}=$("x25519"),e=t.export({format:"der",type:"spki"}),n=r.export({format:"der",type:"pkcs8"});return{publicKey:`${m}${y(e)}`,privateKey:`${x}${y(n)}`}}function v(t,r){let e=X(r),n=$("x25519"),o=y(n.publicKey.export({format:"der",type:"spki"})),c=w(16),s=w(12),h=T({privateKey:n.privateKey,publicKey:e}),f=Buffer.from(A("sha256",h,c,R,32)),g={epk:o,salt:y(c),nonce:y(s)};try{let i=b("aes-256-gcm",f,s);i.setAAD(D(g));let p=Buffer.concat([i.update(t,"utf8"),i.final()]),E={v:2,...g,ciphertext:y(p),tag:y(i.getAuthTag())};return`${u}${y(Buffer.from(JSON.stringify(E),"utf8"))}`}finally{h.fill(0),f.fill(0)}}function Y(t){let r=a(t.slice(u.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(),o=["ciphertext","epk","nonce","salt","tag","v"];if(n.length!==o.length||n.some((c,s)=>c!==o[s]))throw Error("envelope fields are invalid");if(e.v!==2)throw Error("envelope version is unsupported");for(let c of["epk","salt","nonce","ciphertext","tag"])if(typeof e[c]!=="string")throw Error(`${c} is invalid`);return e}function C(t,r){try{let e=Y(t),n=O(r),o=P({key:a(e.epk,"ephemeral public key"),format:"der",type:"spki"});if(o.asymmetricKeyType!=="x25519")throw Error("ephemeral key is not X25519");let c=a(e.salt,"salt",16),s=a(e.nonce,"nonce",12),h=a(e.ciphertext,"ciphertext"),f=a(e.tag,"tag",16),g=T({privateKey:n,publicKey:o}),i=Buffer.from(A("sha256",g,c,R,32));try{let p=B("aes-256-gcm",i,s);return p.setAAD(D(e)),p.setAuthTag(f),Buffer.concat([p.update(h),p.final()]).toString("utf8")}finally{g.fill(0),i.fill(0)}}catch{throw Error(_)}}function H(t,r){try{if(!/^[0-9a-f]{64}$/.test(r))throw Error("invalid legacy private key");let e=Buffer.from(t.slice(K.length),"base64");if(e.length<32)throw Error("invalid legacy payload");let n=d("sha256").update(Buffer.from(r,"hex")).digest(),o=d("sha256").update(n).digest();return F(e.subarray(32).toString("hex"),o,e.subarray(0,16).toString("hex"),e.subarray(16,32).toString("hex"))}catch{throw Error(_)}}function J(t){return t.startsWith(K)&&!t.startsWith(u)}function k(t,r){if(!t.startsWith(K))return t;return t.startsWith(u)?C(t,r):H(t,r)}function U(t,r,e){return v(k(t,r),e)}function z(t){let r=t.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return r&&r[1]?r[1].toLowerCase():""}function Z(t=""){return t?process.env[`DOTENV_PRIVATE_KEY_${t.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}function N(t,r={}){let e={},n=[],o=[],c=t.split(`
3
+ `);for(let s of c){let h=s.trim();if(!h||h.startsWith("#"))continue;if(h.startsWith("DOTENV_PUBLIC_KEY=")){let p=h.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(p&&p[1]!==void 0)e.DOTENV_PUBLIC_KEY=p[1];continue}let f=h.match(/^([^=]+)=(.*)$/);if(!f||f[1]===void 0||f[2]===void 0)continue;let g=f[1].trim(),i=f[2].trim();if(i.startsWith('"')&&i.endsWith('"')||i.startsWith("'")&&i.endsWith("'")){if(i=i.slice(1,-1),i.includes("\\n"))i=i.replace(/\\n/g,`
4
+ `)}if(i.startsWith("encrypted:")||i.startsWith("enc:")){if(!r.privateKey){o.push(g);continue}try{let p=i.startsWith("enc:")?`encrypted:${i.slice(4)}`:i;i=k(p,r.privateKey)}catch(p){n.push(`Failed to decrypt ${g}: ${p instanceof Error?p.message:"Unknown error"}`)}}i=W(i,{...r.processEnv||process.env,...e}),i=l(i),e[g]=i}return{parsed:e,errors:n,skippedEncrypted:o}}function W(t,r){return t.replace(/\$\{([^}]+)\}/g,(e,n)=>{let o=n.match(/^([^:\-+]+)(:-|-)(.+)$/);if(o){let[,s,h,f]=o,g=r[s];if(h===":-")return g||f;else return g!==void 0?g:f}let c=n.match(/^([^:\-+]+)(:?\+)(.+)$/);if(c){let[,s,h,f]=c,g=r[s];if(h===":+")return g?f:"";else return g!==void 0?f:""}return r[n]||""})}var j=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function l(t){return t.replace(/\$\(([^)]+)\)/g,(r,e)=>{try{let n=e.trim().split(/\s+/),o=n[0];if(!o||!j.has(o))return console.warn(`[env] Blocked command substitution for disallowed command: ${o}`),"";let c=Bun.spawnSync(n,{stdout:"pipe",stderr:"pipe"});if(c.exitCode===0)return new TextDecoder().decode(c.stdout).trim();console.warn(`[env] Command substitution failed (exit code ${c.exitCode}): ${o}`)}catch(n){console.warn(`[env] Command substitution error: ${n instanceof Error?n.message:String(n)}`)}return""})}async function M(t,r={}){let e={},n=[],o=[];for(let c of t)try{let s=Bun.file(c);if(!s.size)continue;let h=await s.text(),{parsed:f,errors:g,skippedEncrypted:i}=N(h,r);n.push(...g),o.push(...i);for(let[p,E]of Object.entries(f)){if(!r.overload&&e[p]!==void 0)continue;e[p]=E}}catch(s){if(s?.code==="ENOENT")continue;n.push(`Failed to read ${c}: ${s instanceof Error?s.message:String(s)}`)}return{parsed:e,errors:n,skippedEncrypted:o}}export{N as parse,M as loadEnvFiles};
package/dist/plugin.d.ts CHANGED
@@ -1,4 +1,24 @@
1
1
  import type { BunPlugin } from 'bun';
2
+ /**
3
+ * Resolve the dotenvx private key for the active environment, checking
4
+ * process.env (`DOTENV_PRIVATE_KEY_<ENV>` then `DOTENV_PRIVATE_KEY`) and
5
+ * finally a local `.env.keys` file. The result — key or `undefined` — is
6
+ * cached per environment so the env proxy can call this on every encrypted
7
+ * read without repeatedly touching disk.
8
+ */
9
+ export declare function resolvePrivateKey(options?: { env?: string, cwd?: string }): string | undefined;
10
+ /**
11
+ * Clear the cached private key. Needed after key rotation and between tests
12
+ * that swap `DOTENV_PRIVATE_KEY*` or `APP_ENV`.
13
+ */
14
+ export declare function resetPrivateKeyCache(): void;
15
+ /**
16
+ * Decrypt a single `encrypted:` / `enc:` value on demand. Returns the
17
+ * plaintext, or `undefined` when the value is unusable (no key available or
18
+ * decryption failed) so callers can treat it exactly like an unset variable.
19
+ * Non-encrypted input is returned unchanged.
20
+ */
21
+ export declare function decryptEnvValue(value: string, options?: { env?: string, cwd?: string }): string | undefined;
2
22
  /**
3
23
  * Load .env files and inject into process.env
4
24
  */
package/dist/plugin.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // @bun
2
- import{createCipheriv as f,createDecipheriv as m,createHash as D,randomBytes as K}from"crypto";function k(J,j){let Y=K(16),Q=j.length===32?j:Buffer.from(j.toString("hex").slice(0,64),"hex"),R=f("aes-256-gcm",Q,Y),U=Buffer.concat([R.update(J,"utf8"),R.final()]),Z=R.getAuthTag();return{ciphertext:U.toString("hex"),iv:Y.toString("hex"),authTag:Z.toString("hex")}}function h(J,j,Y,Q){let R=j.length===32?j:Buffer.from(j.toString("hex").slice(0,64),"hex");try{let U=m("aes-256-gcm",R,Buffer.from(Y,"hex"));return U.setAuthTag(Buffer.from(Q,"hex")),Buffer.concat([U.update(Buffer.from(J,"hex")),U.final()]).toString("utf8")}catch(U){throw Error(`Decryption failed (data may be corrupted or key is incorrect): ${U instanceof Error?U.message:String(U)}`)}}function o(){let J=K(32);return{publicKey:D("sha256").update(J).digest().toString("hex"),privateKey:J.toString("hex")}}function s(J,j){let Y=D("sha256").update(Buffer.from(j,"hex")).digest(),{ciphertext:Q,iv:R,authTag:U}=k(J,Y);return`encrypted:${Buffer.concat([Buffer.from(R,"hex"),Buffer.from(U,"hex"),Buffer.from(Q,"hex")]).toString("base64")}`}function N(J,j){if(!J.startsWith("encrypted:"))return J;let Y=J.slice(10),Q=Buffer.from(Y,"base64");if(Q.length<32)throw Error("Invalid encrypted data: payload too short (need at least iv + authTag = 32 bytes)");let R=Q.subarray(0,16).toString("hex"),U=Q.subarray(16,32).toString("hex"),Z=Q.subarray(32).toString("hex"),$=D("sha256").update(Buffer.from(j,"hex")).digest(),W=D("sha256").update($).digest();return h(Z,W,R,U)}function i(J){let j=J.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return j&&j[1]?j[1].toLowerCase():""}function P(J=""){if(!J)return process.env.DOTENV_PRIVATE_KEY;let j=`DOTENV_PRIVATE_KEY_${J.toUpperCase()}`;return process.env[j]}function F(J,j={}){let Y={},Q=[],R=[],U=J.split(`
3
- `);for(let Z of U){let $=Z.trim();if(!$||$.startsWith("#"))continue;if($.startsWith("DOTENV_PUBLIC_KEY=")){let q=$.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(q&&q[1]!==void 0)Y.DOTENV_PUBLIC_KEY=q[1];continue}let W=$.match(/^([^=]+)=(.*)$/);if(!W||W[1]===void 0||W[2]===void 0)continue;let G=W[1].trim(),X=W[2].trim();if(X.startsWith('"')&&X.endsWith('"')||X.startsWith("'")&&X.endsWith("'")){if(X=X.slice(1,-1),X.includes("\\n"))X=X.replace(/\\n/g,`
4
- `)}if(X.startsWith("encrypted:")||X.startsWith("enc:")){if(!j.privateKey){R.push(G);continue}try{let q=X.startsWith("enc:")?`encrypted:${X.slice(4)}`:X;X=N(q,j.privateKey)}catch(q){Q.push(`Failed to decrypt ${G}: ${q instanceof Error?q.message:"Unknown error"}`)}}X=c(X,{...j.processEnv||process.env,...Y}),X=u(X),Y[G]=X}return{parsed:Y,errors:Q,skippedEncrypted:R}}function c(J,j){return J.replace(/\$\{([^}]+)\}/g,(Y,Q)=>{let R=Q.match(/^([^:\-+]+)(:-|-)(.+)$/);if(R){let[,Z,$,W]=R,G=j[Z];if($===":-")return G||W;else return G!==void 0?G:W}let U=Q.match(/^([^:\-+]+)(:?\+)(.+)$/);if(U){let[,Z,$,W]=U,G=j[Z];if($===":+")return G?W:"";else return G!==void 0?W:""}return j[Q]||""})}var y=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function u(J){return J.replace(/\$\(([^)]+)\)/g,(j,Y)=>{try{let Q=Y.trim().split(/\s+/),R=Q[0];if(!R||!y.has(R))return console.warn(`[env] Blocked command substitution for disallowed command: ${R}`),"";let U=Bun.spawnSync(Q,{stdout:"pipe",stderr:"pipe"});if(U.exitCode===0)return new TextDecoder().decode(U.stdout).trim();console.warn(`[env] Command substitution failed (exit code ${U.exitCode}): ${R}`)}catch(Q){console.warn(`[env] Command substitution error: ${Q instanceof Error?Q.message:String(Q)}`)}return""})}async function l(J,j={}){let Y={},Q=[],R=[];for(let U of J)try{let Z=Bun.file(U);if(!Z.size)continue;let $=await Z.text(),{parsed:W,errors:G,skippedEncrypted:X}=F($,j);Q.push(...G),R.push(...X);for(let[q,O]of Object.entries(W)){if(!j.overload&&Y[q]!==void 0)continue;Y[q]=O}}catch(Z){if(Z?.code==="ENOENT")continue;Q.push(`Failed to read ${U}: ${Z instanceof Error?Z.message:String(Z)}`)}return{parsed:Y,errors:Q,skippedEncrypted:R}}import{existsSync as g}from"fs";import{readFileSync as V}from"fs";import{resolve as z}from"path";function b(J){return typeof J==="string"&&(J.startsWith("encrypted:")||J.startsWith("enc:"))}function M(J){if(!J||b(J))return;let j=J.trim().toLowerCase();if(!/^[a-z0-9_-]+$/.test(j))return;if(j==="prod")return"production";if(j==="stage")return"staging";if(j==="dev")return"development";return j}function x(J={}){let{path:j=[".env"],overload:Y=!1,env:Q,privateKey:R,keysFile:U=".env.keys",quiet:Z=!1,cwd:$=process.cwd()}=J,W=Array.isArray(j)?j:[j],G=[],X=0,q=new Set(Object.entries(process.env).filter(([,A])=>A!==void 0&&!b(A)).map(([A])=>A)),O=R;if(!O&&U){let A=z($,U);if(g(A))try{let _=V(A,"utf-8"),{parsed:B}=F(_);if(Q){let w=`DOTENV_PRIVATE_KEY_${Q.toUpperCase()}`;O=B[w]}else O=B.DOTENV_PRIVATE_KEY}catch(_){G.push(`Failed to load keys file: ${_ instanceof Error?_.message:"Unknown error"}`)}}if(!O)O=P(Q||"");for(let A of W){let _=z($,A);if(!g(_)){if(!Z)G.push(`File not found: ${_}`);continue}try{let B=V(_,"utf-8"),{parsed:w,errors:E,skippedEncrypted:I}=F(B,{privateKey:O,processEnv:process.env});if(G.push(...E),I.length>0){for(let T of I)if(b(process.env[T]))delete process.env[T];let L=Q?`DOTENV_PRIVATE_KEY_${Q.toUpperCase()}`:"DOTENV_PRIVATE_KEY",C=I.slice(0,5).join(", "),H=I.length>5?`, \u2026 +${I.length-5} more`:"";console.warn(`[env] warning: skipped ${I.length} encrypted value(s) in ${A} (${L} not set; defaults apply): ${C}${H}`)}let S=0;for(let[L,C]of Object.entries(w)){if(L==="DOTENV_PUBLIC_KEY")continue;let H=process.env[L],T=b(H)&&C!==H;if(Y||!q.has(L)||T)process.env[L]=C,X++,S++}if(!Z&&!process.env.__ENV_LOADED__)console.error(`[env] loaded ${S}/${Object.keys(w).length} variables from ${A}`),process.env.__ENV_LOADED__="1"}catch(B){G.push(`Failed to load ${_}: ${B instanceof Error?B.message:"Unknown error"}`)}}return{loaded:X,errors:G}}function d(J={}){return{name:"env-plugin",setup(j){x(J)}}}function Rj(J={}){let j=M(J.env)||M("development")||M(process.env.DOTENV_ENV)||M(process.env.APP_ENV)||"development",Y=J.cwd||process.cwd(),Q=[],R=($)=>{if(!Q.includes($)&&g(z(Y,$)))Q.push($)};R(".env"),R(".env.local");let U=`.env.${j}`,Z=`.env.${j}.local`;return R(U),R(Z),x({...J,path:Q,env:j})}var Xj=d;export{x as loadEnv,d as envPlugin,Xj as default,Rj as autoLoadEnv};
2
+ import{createCipheriv as F,createDecipheriv as V,createHash as j,createPrivateKey as l,createPublicKey as M,diffieHellman as k,generateKeyPairSync as m,hkdfSync as P,randomBytes as C}from"crypto";var S="x25519-public:",G="x25519-private:",Y="encrypted:v2:",J="encrypted:",e=Buffer.from("stacks-env:v2","utf8"),z="Environment decryption failed: authentication or format error";function N(t){if(t.length===32)return t;return j("sha256").update(t).digest()}function xt(t,r){let c=C(16),n=F("aes-256-gcm",N(r),c);return{ciphertext:Buffer.concat([n.update(t,"utf8"),n.final()]).toString("hex"),iv:c.toString("hex"),authTag:n.getAuthTag().toString("hex")}}function tt(t,r,c,n){try{let i=V("aes-256-gcm",N(r),Buffer.from(c,"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 E(t){return t.toString("base64url")}function T(t,r,c){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(E(n)!==t)throw Error(`${r} is not canonical base64url`);if(c!==void 0&&n.length!==c)throw Error(`${r} has an invalid length`);return n}function rt(t){if(!t.startsWith(S))throw Error("Legacy or invalid public key. Run buddy env:rotate before creating new ciphertext.");let r=M({key:T(t.slice(S.length),"public key"),format:"der",type:"spki"});if(r.asymmetricKeyType!=="x25519")throw Error("Public key is not X25519");return r}function nt(t){if(!t.startsWith(G))throw Error("Private key is not a version 2 X25519 key");let r=l({key:T(t.slice(G.length),"private key"),format:"der",type:"pkcs8"});if(r.asymmetricKeyType!=="x25519")throw Error("Private key is not X25519");return r}function d(t){return Buffer.from(`stacks-env:v2;${t.epk};${t.salt};${t.nonce}`,"utf8")}function Et(){let{publicKey:t,privateKey:r}=m("x25519"),c=t.export({format:"der",type:"spki"}),n=r.export({format:"der",type:"pkcs8"});return{publicKey:`${S}${E(c)}`,privateKey:`${G}${E(n)}`}}function ct(t,r){let c=rt(r),n=m("x25519"),i=E(n.publicKey.export({format:"der",type:"spki"})),o=C(16),g=C(12),h=k({privateKey:n.privateKey,publicKey:c}),w=Buffer.from(P("sha256",h,o,e,32)),s={epk:i,salt:E(o),nonce:E(g)};try{let f=F("aes-256-gcm",w,g);f.setAAD(d(s));let b=Buffer.concat([f.update(t,"utf8"),f.final()]),u={v:2,...s,ciphertext:E(b),tag:E(f.getAuthTag())};return`${Y}${E(Buffer.from(JSON.stringify(u),"utf8"))}`}finally{h.fill(0),w.fill(0)}}function it(t){let r=T(t.slice(Y.length),"envelope").toString("utf8"),c=JSON.parse(r);if(!c||typeof c!=="object"||Array.isArray(c))throw Error("envelope is not an object");let n=Object.keys(c).sort(),i=["ciphertext","epk","nonce","salt","tag","v"];if(n.length!==i.length||n.some((o,g)=>o!==i[g]))throw Error("envelope fields are invalid");if(c.v!==2)throw Error("envelope version is unsupported");for(let o of["epk","salt","nonce","ciphertext","tag"])if(typeof c[o]!=="string")throw Error(`${o} is invalid`);return c}function ft(t,r){try{let c=it(t),n=nt(r),i=M({key:T(c.epk,"ephemeral public key"),format:"der",type:"spki"});if(i.asymmetricKeyType!=="x25519")throw Error("ephemeral key is not X25519");let o=T(c.salt,"salt",16),g=T(c.nonce,"nonce",12),h=T(c.ciphertext,"ciphertext"),w=T(c.tag,"tag",16),s=k({privateKey:n,publicKey:i}),f=Buffer.from(P("sha256",s,o,e,32));try{let b=V("aes-256-gcm",f,g);return b.setAAD(d(c)),b.setAuthTag(w),Buffer.concat([b.update(h),b.final()]).toString("utf8")}finally{s.fill(0),f.fill(0)}}catch{throw Error(z)}}function ot(t,r){try{if(!/^[0-9a-f]{64}$/.test(r))throw Error("invalid legacy private key");let c=Buffer.from(t.slice(J.length),"base64");if(c.length<32)throw Error("invalid legacy payload");let n=j("sha256").update(Buffer.from(r,"hex")).digest(),i=j("sha256").update(n).digest();return tt(c.subarray(32).toString("hex"),i,c.subarray(0,16).toString("hex"),c.subarray(16,32).toString("hex"))}catch{throw Error(z)}}function Tt(t){return t.startsWith(J)&&!t.startsWith(Y)}function _(t,r){if(!t.startsWith(J))return t;return t.startsWith(Y)?ft(t,r):ot(t,r)}function yt(t,r,c){return ct(_(t,r),c)}function $t(t){let r=t.match(/^DOTENV_PRIVATE_KEY_(.+)$/);return r&&r[1]?r[1].toLowerCase():""}function L(t=""){return t?process.env[`DOTENV_PRIVATE_KEY_${t.toUpperCase()}`]:process.env.DOTENV_PRIVATE_KEY}function p(t,r={}){let c={},n=[],i=[],o=t.split(`
3
+ `);for(let g of o){let h=g.trim();if(!h||h.startsWith("#"))continue;if(h.startsWith("DOTENV_PUBLIC_KEY=")){let b=h.match(/^DOTENV_PUBLIC_KEY=["']?([^"'\n]+)["']?/);if(b&&b[1]!==void 0)c.DOTENV_PUBLIC_KEY=b[1];continue}let w=h.match(/^([^=]+)=(.*)$/);if(!w||w[1]===void 0||w[2]===void 0)continue;let s=w[1].trim(),f=w[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(!r.privateKey){i.push(s);continue}try{let b=f.startsWith("enc:")?`encrypted:${f.slice(4)}`:f;f=_(b,r.privateKey)}catch(b){n.push(`Failed to decrypt ${s}: ${b instanceof Error?b.message:"Unknown error"}`)}}f=gt(f,{...r.processEnv||process.env,...c}),f=ht(f),c[s]=f}return{parsed:c,errors:n,skippedEncrypted:i}}function gt(t,r){return t.replace(/\$\{([^}]+)\}/g,(c,n)=>{let i=n.match(/^([^:\-+]+)(:-|-)(.+)$/);if(i){let[,g,h,w]=i,s=r[g];if(h===":-")return s||w;else return s!==void 0?s:w}let o=n.match(/^([^:\-+]+)(:?\+)(.+)$/);if(o){let[,g,h,w]=o,s=r[g];if(h===":+")return s?w:"";else return s!==void 0?w:""}return r[n]||""})}var st=new Set(["date","hostname","whoami","uname","pwd","echo","printf","cat","basename","dirname"]);function ht(t){return t.replace(/\$\(([^)]+)\)/g,(r,c)=>{try{let n=c.trim().split(/\s+/),i=n[0];if(!i||!st.has(i))return console.warn(`[env] Blocked command substitution for disallowed command: ${i}`),"";let o=Bun.spawnSync(n,{stdout:"pipe",stderr:"pipe"});if(o.exitCode===0)return new TextDecoder().decode(o.stdout).trim();console.warn(`[env] Command substitution failed (exit code ${o.exitCode}): ${i}`)}catch(n){console.warn(`[env] Command substitution error: ${n instanceof Error?n.message:String(n)}`)}return""})}async function _t(t,r={}){let c={},n=[],i=[];for(let o of t)try{let g=Bun.file(o);if(!g.size)continue;let h=await g.text(),{parsed:w,errors:s,skippedEncrypted:f}=p(h,r);n.push(...s),i.push(...f);for(let[b,u]of Object.entries(w)){if(!r.overload&&c[b]!==void 0)continue;c[b]=u}}catch(g){if(g?.code==="ENOENT")continue;n.push(`Failed to read ${o}: ${g instanceof Error?g.message:String(g)}`)}return{parsed:c,errors:n,skippedEncrypted:i}}import{existsSync as H}from"fs";import{readFileSync as U}from"fs";import{resolve as W}from"path";function K(t){return typeof t==="string"&&(t.startsWith("encrypted:")||t.startsWith("enc:"))}function y(t){if(!t||K(t))return;let r=t.trim().toLowerCase();if(!/^[a-z0-9_-]+$/.test(r))return;if(r==="prod")return"production";if(r==="stage")return"staging";if(r==="dev")return"development";return r}var Z=null,Q;function wt(t,r,c=".env.keys"){let n=W(r,c);if(!H(n))return;try{let{parsed:i}=p(U(n,"utf-8"));if(t){let o=i[`DOTENV_PRIVATE_KEY_${t.toUpperCase()}`];if(o)return o}return i.DOTENV_PRIVATE_KEY}catch{return}}function bt(t={}){let r=y(t.env)||y(process.env.APP_ENV)||y("development")||y(process.env.DOTENV_ENV)||"development",c=t.cwd||process.cwd();if(Z===r)return Q;let n=L(r);if(!n)n=process.env.DOTENV_PRIVATE_KEY;if(!n)n=wt(r,c);return Z=r,Q=n,n}function Ht(){Z=null,Q=void 0}function Wt(t,r={}){if(!K(t))return t;let c=bt(r);if(!c)return;try{let n=t.startsWith("enc:")?`encrypted:${t.slice(4)}`:t;return _(n,c)}catch{return}}function a(t={}){let{path:r=[".env"],overload:c=!1,env:n,privateKey:i,keysFile:o=".env.keys",quiet:g=!1,cwd:h=process.cwd()}=t,w=Array.isArray(r)?r:[r],s=[],f=0,b=new Set(Object.entries(process.env).filter(([,B])=>B!==void 0&&!K(B)).map(([B])=>B)),u=i;if(!u&&o){let B=W(h,o);if(H(B))try{let x=U(B,"utf-8"),{parsed:$}=p(x);if(n){let D=`DOTENV_PRIVATE_KEY_${n.toUpperCase()}`;u=$[D]}else u=$.DOTENV_PRIVATE_KEY}catch(x){s.push(`Failed to load keys file: ${x instanceof Error?x.message:"Unknown error"}`)}}if(!u)u=L(n||"");for(let B of w){let x=W(h,B);if(!H(x)){if(!g)s.push(`File not found: ${x}`);continue}try{let $=U(x,"utf-8"),{parsed:D,errors:v,skippedEncrypted:A}=p($,{privateKey:u,processEnv:process.env});if(s.push(...v),A.length>0){for(let O of A)if(K(process.env[O]))delete process.env[O];let R=n?`DOTENV_PRIVATE_KEY_${n.toUpperCase()}`:"DOTENV_PRIVATE_KEY",X=A.slice(0,5).join(", "),I=A.length>5?`, \u2026 +${A.length-5} more`:"";console.warn(`[env] warning: skipped ${A.length} encrypted value(s) in ${B} (${R} not set; defaults apply): ${X}${I}`)}let q=0;for(let[R,X]of Object.entries(D)){if(R==="DOTENV_PUBLIC_KEY")continue;let I=process.env[R],O=K(I)&&X!==I;if(c||!b.has(R)||O)process.env[R]=X,f++,q++}if(!g&&!process.env.__ENV_LOADED__)console.error(`[env] loaded ${q}/${Object.keys(D).length} variables from ${B}`),process.env.__ENV_LOADED__="1"}catch($){s.push(`Failed to load ${x}: ${$ instanceof Error?$.message:"Unknown error"}`)}}return{loaded:f,errors:s}}function Bt(t={}){return{name:"env-plugin",setup(r){a(t)}}}function jt(t={}){let r=y(t.env)||y("development")||y(process.env.DOTENV_ENV)||y(process.env.APP_ENV)||"development",c=t.cwd||process.cwd(),n=[],i=(h)=>{if(!n.includes(h)&&H(W(c,h)))n.push(h)};i(".env"),i(".env.local");let o=`.env.${r}`,g=`.env.${r}.local`;return i(o),i(g),a({...t,path:n,env:r})}var St=Bt;export{bt as resolvePrivateKey,Ht as resetPrivateKeyCache,a as loadEnv,Bt as envPlugin,St as default,Wt as decryptEnvValue,jt as autoLoadEnv};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/env",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.161",
5
+ "version": "0.70.162",
6
6
  "description": "Stacks env helper methods.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -51,7 +51,7 @@
51
51
  },
52
52
  "devDependencies": {
53
53
  "better-dx": "^0.2.17",
54
- "@stacksjs/path": "0.70.161",
55
- "@stacksjs/validation": "0.70.161"
54
+ "@stacksjs/path": "0.70.162",
55
+ "@stacksjs/validation": "0.70.162"
56
56
  }
57
57
  }