@samveys/env-crypt
From packages/env-crypt/README.md in the framework repository.
svs env:encrypt and svs env:decrypt: keep an encrypted copy of your .env in git. The commands are added by a plugin, so @samveys/console stays unchanged. No runtime dependencies; encryption is AES-256-GCM from @samveys/core.
Set up
import { envCrypt } from '@samveys/env-crypt'
export const plugin = envCrypt()Add envCrypt() to the plugins in samveys.config.ts; svs --help then lists both commands.
Use
svs env:encrypt # .env → .env.encrypted (the whole file is one encrypted blob)
svs env:encrypt --readable # keep NAME= visible, encrypt each value: NAME=enc:v1:<base64>
svs env:decrypt # .env.encrypted → .env| Flag | Default | Meaning |
|---|---|---|
--file | .env / .env.encrypted | input file |
--out | .env.encrypted / .env | output file |
--readable | off | (encrypt) keep names visible |
--force | off | overwrite an existing output file |
The key is read from SAMVEYS_ENV_KEY (base64: followed by 32 random bytes). When it is missing, env:encrypt generates one, prints it once and tells you to store it; keep it in your password manager or CI secrets, never in the repository.
Guarantees
- Byte-for-byte: decrypting gives back the original file exactly, in both modes, including comments, blank lines, line endings (
\n,\r\n), quotes, trailing comments and multi-line quoted values. - Tamper-evident: any change to the encrypted file, a wrong key or a missing key is
SVS_E118, and no plain file is written. In readable mode each value is bound to its name, so moving a value under another name also fails. - Private output: files are written with mode
0600.
From code
import { decryptEnv, encryptEnv, generateEnvKey } from '@samveys/env-crypt'
const key = generateEnvKey()
const original = '# secrets\nDB_PASSWORD="p@ss word" # prod\n'
export const readable = encryptEnv(original, key, { readable: true }) // DB_PASSWORD=enc:v1:…
export const restored = decryptEnv(readable, key) // === original