@boltpl/envseal 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 mgrom
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # envseal
2
+
3
+ encrypt secrets at rest, inject at runtime. no plaintext on disk.
4
+
5
+ ## install
6
+
7
+ ```bash
8
+ npm install -g envseal
9
+ ```
10
+
11
+ ## usage
12
+
13
+ ### passphrase mode (dev/laptop)
14
+
15
+ ```bash
16
+ envseal init
17
+ envseal set DATABASE_URL "postgres://..."
18
+ envseal set STRIPE_KEY "sk_live_..."
19
+ envseal run -- node server.js # prompts for passphrase, injects env
20
+ ```
21
+
22
+ ### keyfile mode (servers)
23
+
24
+ ```bash
25
+ cd ~/projects/myapp
26
+ envseal keygen # generates ~/.envseal/keys/myapp.key (auto-named after directory)
27
+ envseal init --keyfile
28
+ envseal set DATABASE_URL "postgres://..." # auto-finds key, no config needed
29
+ envseal run -- node server.js # just works
30
+
31
+ # in systemd unit - zero interaction
32
+ # ExecStart=envseal run -- node server.js
33
+ ```
34
+
35
+ key is stored in `~/.envseal/keys/<project-dir>.key`, vault is `.envseal.vault` in project dir. separated by default.
36
+
37
+ ### other commands
38
+
39
+ ```bash
40
+ envseal get KEY # decrypt single value
41
+ envseal list # show key names (not values)
42
+ envseal rm KEY # remove a secret
43
+ envseal export # decrypt all as KEY=VALUE on stdout
44
+ envseal import .env # bulk import from .env file
45
+ envseal keygen --out PATH # custom key location
46
+ ```
47
+
48
+ ## key resolution
49
+
50
+ in keyfile mode, envseal looks for the key in order:
51
+
52
+ 1. `ENVSEAL_KEY` env var (base64 key directly)
53
+ 2. `ENVSEAL_KEY_FILE` env var (path to key file)
54
+ 3. `~/.envseal/keys/<project-dir-name>.key` (auto-resolve)
55
+
56
+ in passphrase mode, same lookup order, then falls back to interactive prompt. `ENVSEAL_PASSPHRASE` env var skips the prompt.
57
+
58
+ ## how it works
59
+
60
+ secrets are encrypted with AES-256-GCM. in passphrase mode, the encryption key is derived via scrypt. in keyfile mode, the key is a random 256-bit value.
61
+
62
+ the vault (`.envseal.vault`) stores key names in plaintext, values as encrypted blobs. `envseal run` decrypts everything in memory, passes secrets as env vars to the child process, then exits. nothing plaintext touches disk.
63
+
64
+ zero dependencies - uses only node's built-in `crypto` module.
65
+
66
+ ## vault format
67
+
68
+ ```json
69
+ {
70
+ "version": 2,
71
+ "keyMode": "keyfile",
72
+ "secrets": {
73
+ "DATABASE_URL": { "iv": "...", "data": "...", "tag": "..." }
74
+ }
75
+ }
76
+ ```
77
+
78
+ ## security model
79
+
80
+ protects secrets at rest and from automated exfiltration. scanners grepping for `.env`, `sk-`, `ghp_`, private keys find nothing.
81
+
82
+ in keyfile mode, key and vault are in different locations with different permissions. attacker needs both.
83
+
84
+ not a defense against an active attacker with same-UID shell access. they can read `/proc/<pid>/environ` or attach a debugger. no userspace tool prevents that.
85
+
86
+ ## license
87
+
88
+ MIT
package/bin/envseal ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("../dist/cli.js");
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ const vault_1 = require("./vault");
38
+ const prompt_1 = require("./prompt");
39
+ const run_1 = require("./run");
40
+ const crypto_1 = require("./crypto");
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ function requireVault() {
44
+ const vp = (0, vault_1.findVault)();
45
+ if (!vp) {
46
+ console.error("no vault found. run `envseal init` first.");
47
+ process.exit(1);
48
+ }
49
+ return vp;
50
+ }
51
+ function keysDir() {
52
+ return path.join(process.env.HOME || "/root", ".envseal", "keys");
53
+ }
54
+ function projectName() {
55
+ return path.basename(process.cwd());
56
+ }
57
+ function defaultKeyPath() {
58
+ return path.join(keysDir(), projectName() + ".key");
59
+ }
60
+ function resolveKeyFile() {
61
+ // 1. ENVSEAL_KEY — raw base64 key in env
62
+ const envKey = process.env.ENVSEAL_KEY;
63
+ if (envKey)
64
+ return Buffer.from(envKey, "base64");
65
+ // 2. ENVSEAL_KEY_FILE — path to key file
66
+ const envKeyFile = process.env.ENVSEAL_KEY_FILE;
67
+ if (envKeyFile) {
68
+ if (!fs.existsSync(envKeyFile)) {
69
+ console.error(`key file not found: ${envKeyFile}`);
70
+ process.exit(1);
71
+ }
72
+ return Buffer.from(fs.readFileSync(envKeyFile, "utf8").trim(), "base64");
73
+ }
74
+ // 3. ~/.envseal/keys/<project-dir-name>.key
75
+ const autoKey = defaultKeyPath();
76
+ if (fs.existsSync(autoKey)) {
77
+ return Buffer.from(fs.readFileSync(autoKey, "utf8").trim(), "base64");
78
+ }
79
+ return null;
80
+ }
81
+ async function getCredentials(vaultPath) {
82
+ const mode = (0, vault_1.getVaultMode)(vaultPath);
83
+ if (mode === "keyfile") {
84
+ const keyBuf = resolveKeyFile();
85
+ if (!keyBuf) {
86
+ console.error("keyfile mode but no key found. set ENVSEAL_KEY, ENVSEAL_KEY_FILE, or place .envseal.key");
87
+ process.exit(1);
88
+ }
89
+ return { keyBuf };
90
+ }
91
+ // passphrase mode — check for keyfile first (override), then prompt
92
+ const keyBuf = resolveKeyFile();
93
+ if (keyBuf)
94
+ return { keyBuf };
95
+ const passphrase = await (0, prompt_1.readPassphrase)();
96
+ return { passphrase };
97
+ }
98
+ async function main() {
99
+ const args = process.argv.slice(2);
100
+ const cmd = args[0];
101
+ switch (cmd) {
102
+ case "init": {
103
+ const useKeyfile = args.includes("--keyfile");
104
+ const p = (0, vault_1.createVault)(undefined, useKeyfile ? "keyfile" : "passphrase");
105
+ console.log(`created ${p} (${useKeyfile ? "keyfile" : "passphrase"} mode)`);
106
+ break;
107
+ }
108
+ case "keygen": {
109
+ const outIdx = args.indexOf("--out");
110
+ const outPath = outIdx !== -1 ? args[outIdx + 1] : defaultKeyPath();
111
+ const key = (0, crypto_1.generateKeyFile)();
112
+ const outDir = path.dirname(outPath);
113
+ if (outDir && !fs.existsSync(outDir))
114
+ fs.mkdirSync(outDir, { recursive: true, mode: 0o700 });
115
+ fs.writeFileSync(outPath, key.toString("base64") + "\n", { mode: 0o400 });
116
+ console.log(`key written to ${outPath}`);
117
+ break;
118
+ }
119
+ case "set": {
120
+ const vp = requireVault();
121
+ const key = args[1];
122
+ const val = args[2];
123
+ if (!key || val === undefined) {
124
+ console.error("usage: envseal set KEY VALUE");
125
+ process.exit(1);
126
+ }
127
+ const creds = await getCredentials(vp);
128
+ (0, vault_1.setSecret)(vp, key, val, creds.passphrase, creds.keyBuf);
129
+ console.log(`set ${key}`);
130
+ break;
131
+ }
132
+ case "get": {
133
+ const vp = requireVault();
134
+ const key = args[1];
135
+ if (!key) {
136
+ console.error("usage: envseal get KEY");
137
+ process.exit(1);
138
+ }
139
+ const creds = await getCredentials(vp);
140
+ console.log((0, vault_1.getSecret)(vp, key, creds.passphrase, creds.keyBuf));
141
+ break;
142
+ }
143
+ case "list": {
144
+ const vp = requireVault();
145
+ for (const k of (0, vault_1.listKeys)(vp))
146
+ console.log(k);
147
+ break;
148
+ }
149
+ case "rm": {
150
+ const vp = requireVault();
151
+ const key = args[1];
152
+ if (!key) {
153
+ console.error("usage: envseal rm KEY");
154
+ process.exit(1);
155
+ }
156
+ (0, vault_1.removeKey)(vp, key);
157
+ console.log(`removed ${key}`);
158
+ break;
159
+ }
160
+ case "export": {
161
+ const vp = requireVault();
162
+ const creds = await getCredentials(vp);
163
+ const secrets = (0, vault_1.getAllSecrets)(vp, creds.passphrase, creds.keyBuf);
164
+ for (const [k, v] of Object.entries(secrets)) {
165
+ console.log(`${k}=${v}`);
166
+ }
167
+ break;
168
+ }
169
+ case "import": {
170
+ const vp = requireVault();
171
+ const file = args[1];
172
+ if (!file) {
173
+ console.error("usage: envseal import FILE");
174
+ process.exit(1);
175
+ }
176
+ const creds = await getCredentials(vp);
177
+ const lines = fs.readFileSync(file, "utf8").split("\n");
178
+ let count = 0;
179
+ for (const line of lines) {
180
+ const trimmed = line.trim();
181
+ if (!trimmed || trimmed.startsWith("#"))
182
+ continue;
183
+ const eq = trimmed.indexOf("=");
184
+ if (eq === -1)
185
+ continue;
186
+ const key = trimmed.slice(0, eq).trim();
187
+ const val = trimmed.slice(eq + 1).trim();
188
+ (0, vault_1.setSecret)(vp, key, val, creds.passphrase, creds.keyBuf);
189
+ count++;
190
+ }
191
+ console.log(`imported ${count} secrets`);
192
+ break;
193
+ }
194
+ case "run": {
195
+ const vp = requireVault();
196
+ const dashIdx = args.indexOf("--");
197
+ if (dashIdx === -1 || dashIdx === args.length - 1) {
198
+ console.error("usage: envseal run -- COMMAND [ARGS...]");
199
+ process.exit(1);
200
+ }
201
+ const creds = await getCredentials(vp);
202
+ const secrets = (0, vault_1.getAllSecrets)(vp, creds.passphrase, creds.keyBuf);
203
+ const childArgs = args.slice(dashIdx + 1);
204
+ const code = await (0, run_1.runCommand)(childArgs, secrets);
205
+ process.exit(code);
206
+ break;
207
+ }
208
+ default:
209
+ console.error("usage: envseal <init|keygen|set|get|list|rm|export|import|run>");
210
+ console.error("");
211
+ console.error(" init [--keyfile] create vault (passphrase or keyfile mode)");
212
+ console.error(" keygen [--out PATH] generate random key file");
213
+ console.error(" set KEY VALUE add or update a secret");
214
+ console.error(" get KEY decrypt and print a secret");
215
+ console.error(" list show secret names");
216
+ console.error(" rm KEY remove a secret");
217
+ console.error(" export decrypt all as KEY=VALUE");
218
+ console.error(" import FILE bulk import from .env file");
219
+ console.error(" run -- CMD [ARGS] run command with secrets in env");
220
+ process.exit(1);
221
+ }
222
+ }
223
+ main().catch((err) => {
224
+ console.error(err.message);
225
+ process.exit(1);
226
+ });
@@ -0,0 +1,10 @@
1
+ export interface EncryptedValue {
2
+ iv: string;
3
+ data: string;
4
+ tag: string;
5
+ }
6
+ export declare function deriveKey(passphrase: string, salt: Buffer): Buffer;
7
+ export declare function generateKeyFile(): Buffer;
8
+ export declare function generateSalt(): Buffer;
9
+ export declare function encrypt(plaintext: string, key: Buffer): EncryptedValue;
10
+ export declare function decrypt(enc: EncryptedValue, key: Buffer): string;
package/dist/crypto.js ADDED
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deriveKey = deriveKey;
4
+ exports.generateKeyFile = generateKeyFile;
5
+ exports.generateSalt = generateSalt;
6
+ exports.encrypt = encrypt;
7
+ exports.decrypt = decrypt;
8
+ const crypto_1 = require("crypto");
9
+ const ALGO = "aes-256-gcm";
10
+ const SCRYPT_N = 2 ** 14;
11
+ const SCRYPT_R = 8;
12
+ const SCRYPT_P = 1;
13
+ const KEY_LEN = 32;
14
+ const IV_LEN = 12;
15
+ const SALT_LEN = 16;
16
+ function deriveKey(passphrase, salt) {
17
+ return (0, crypto_1.scryptSync)(passphrase, salt, KEY_LEN, {
18
+ N: SCRYPT_N,
19
+ r: SCRYPT_R,
20
+ p: SCRYPT_P,
21
+ maxmem: 64 * 1024 * 1024,
22
+ });
23
+ }
24
+ function generateKeyFile() {
25
+ return (0, crypto_1.randomBytes)(KEY_LEN);
26
+ }
27
+ function generateSalt() {
28
+ return (0, crypto_1.randomBytes)(SALT_LEN);
29
+ }
30
+ function encrypt(plaintext, key) {
31
+ const iv = (0, crypto_1.randomBytes)(IV_LEN);
32
+ const cipher = (0, crypto_1.createCipheriv)(ALGO, key, iv);
33
+ const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
34
+ const tag = cipher.getAuthTag();
35
+ return {
36
+ iv: iv.toString("base64"),
37
+ data: encrypted.toString("base64"),
38
+ tag: tag.toString("base64"),
39
+ };
40
+ }
41
+ function decrypt(enc, key) {
42
+ const iv = Buffer.from(enc.iv, "base64");
43
+ const data = Buffer.from(enc.data, "base64");
44
+ const tag = Buffer.from(enc.tag, "base64");
45
+ const decipher = (0, crypto_1.createDecipheriv)(ALGO, key, iv);
46
+ decipher.setAuthTag(tag);
47
+ return Buffer.concat([decipher.update(data), decipher.final()]).toString("utf8");
48
+ }
@@ -0,0 +1 @@
1
+ export declare function readPassphrase(prompt?: string): Promise<string>;
package/dist/prompt.js ADDED
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.readPassphrase = readPassphrase;
37
+ const fs = __importStar(require("fs"));
38
+ const readline = __importStar(require("readline"));
39
+ function readPassphrase(prompt = "passphrase: ") {
40
+ const envPass = process.env.ENVSEAL_PASSPHRASE;
41
+ if (envPass)
42
+ return Promise.resolve(envPass);
43
+ return new Promise((resolve, reject) => {
44
+ let ttyFd;
45
+ try {
46
+ ttyFd = fs.openSync("/dev/tty", "r");
47
+ }
48
+ catch {
49
+ reject(new Error("cannot open /dev/tty — set ENVSEAL_PASSPHRASE env var"));
50
+ return;
51
+ }
52
+ const ttyStream = fs.createReadStream("", { fd: ttyFd });
53
+ const rl = readline.createInterface({ input: ttyStream, output: process.stderr });
54
+ rl.question(prompt, (answer) => {
55
+ rl.close();
56
+ ttyStream.destroy();
57
+ resolve(answer);
58
+ });
59
+ });
60
+ }
package/dist/run.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function runCommand(command: string[], extraEnv: Record<string, string>): Promise<number>;
package/dist/run.js ADDED
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runCommand = runCommand;
4
+ const child_process_1 = require("child_process");
5
+ function runCommand(command, extraEnv) {
6
+ return new Promise((resolve, reject) => {
7
+ const merged = {};
8
+ for (const [k, v] of Object.entries(process.env)) {
9
+ if (v !== undefined)
10
+ merged[k] = v;
11
+ }
12
+ Object.assign(merged, extraEnv);
13
+ const child = (0, child_process_1.spawn)(command[0], command.slice(1), {
14
+ stdio: "inherit",
15
+ env: merged,
16
+ });
17
+ child.on("error", reject);
18
+ child.on("close", (code) => resolve(code ?? 1));
19
+ });
20
+ }
@@ -0,0 +1,16 @@
1
+ import { EncryptedValue } from "./crypto";
2
+ export interface Vault {
3
+ version: number;
4
+ keyMode: "passphrase" | "keyfile";
5
+ salt?: string;
6
+ secrets: Record<string, EncryptedValue>;
7
+ }
8
+ export declare function findVault(from?: string): string | null;
9
+ export declare function createVault(dir?: string, keyMode?: "passphrase" | "keyfile"): string;
10
+ export declare function resolveKey(vault: Vault, passphrase?: string, keyBuf?: Buffer): Buffer;
11
+ export declare function getVaultMode(vaultPath: string): "passphrase" | "keyfile";
12
+ export declare function setSecret(vaultPath: string, key: string, value: string, passphrase?: string, keyBuf?: Buffer): void;
13
+ export declare function getSecret(vaultPath: string, key: string, passphrase?: string, keyBuf?: Buffer): string;
14
+ export declare function listKeys(vaultPath: string): string[];
15
+ export declare function removeKey(vaultPath: string, key: string): void;
16
+ export declare function getAllSecrets(vaultPath: string, passphrase?: string, keyBuf?: Buffer): Record<string, string>;
package/dist/vault.js ADDED
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.findVault = findVault;
37
+ exports.createVault = createVault;
38
+ exports.resolveKey = resolveKey;
39
+ exports.getVaultMode = getVaultMode;
40
+ exports.setSecret = setSecret;
41
+ exports.getSecret = getSecret;
42
+ exports.listKeys = listKeys;
43
+ exports.removeKey = removeKey;
44
+ exports.getAllSecrets = getAllSecrets;
45
+ const fs = __importStar(require("fs"));
46
+ const path = __importStar(require("path"));
47
+ const crypto_1 = require("./crypto");
48
+ const VAULT_FILE = ".envseal.vault";
49
+ function findVault(from) {
50
+ let dir;
51
+ try {
52
+ dir = from || process.cwd();
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ while (true) {
58
+ const candidate = path.join(dir, VAULT_FILE);
59
+ if (fs.existsSync(candidate))
60
+ return candidate;
61
+ const parent = path.dirname(dir);
62
+ if (parent === dir)
63
+ return null;
64
+ dir = parent;
65
+ }
66
+ }
67
+ function createVault(dir, keyMode = "passphrase") {
68
+ const target = path.join(dir || process.cwd(), VAULT_FILE);
69
+ if (fs.existsSync(target)) {
70
+ throw new Error("vault already exists at " + target);
71
+ }
72
+ const vault = {
73
+ version: 2,
74
+ keyMode,
75
+ secrets: {},
76
+ };
77
+ if (keyMode === "passphrase") {
78
+ vault.salt = (0, crypto_1.generateSalt)().toString("base64");
79
+ }
80
+ fs.writeFileSync(target, JSON.stringify(vault, null, 2) + "\n");
81
+ return target;
82
+ }
83
+ function readVault(vaultPath) {
84
+ const raw = fs.readFileSync(vaultPath, "utf8");
85
+ const parsed = JSON.parse(raw);
86
+ // migrate v1 vaults
87
+ if (parsed.version === 1) {
88
+ parsed.version = 2;
89
+ parsed.keyMode = "passphrase";
90
+ }
91
+ if (parsed.version !== 2) {
92
+ throw new Error("unsupported vault version: " + parsed.version);
93
+ }
94
+ return parsed;
95
+ }
96
+ function writeVault(vaultPath, vault) {
97
+ fs.writeFileSync(vaultPath, JSON.stringify(vault, null, 2) + "\n");
98
+ }
99
+ function resolveKey(vault, passphrase, keyBuf) {
100
+ if (vault.keyMode === "keyfile") {
101
+ if (!keyBuf)
102
+ throw new Error("keyfile required but not provided");
103
+ return keyBuf;
104
+ }
105
+ // passphrase mode
106
+ if (!passphrase)
107
+ throw new Error("passphrase required");
108
+ if (!vault.salt)
109
+ throw new Error("vault missing salt");
110
+ return (0, crypto_1.deriveKey)(passphrase, Buffer.from(vault.salt, "base64"));
111
+ }
112
+ function getVaultMode(vaultPath) {
113
+ return readVault(vaultPath).keyMode;
114
+ }
115
+ function setSecret(vaultPath, key, value, passphrase, keyBuf) {
116
+ const vault = readVault(vaultPath);
117
+ const dk = resolveKey(vault, passphrase, keyBuf);
118
+ vault.secrets[key] = (0, crypto_1.encrypt)(value, dk);
119
+ writeVault(vaultPath, vault);
120
+ }
121
+ function getSecret(vaultPath, key, passphrase, keyBuf) {
122
+ const vault = readVault(vaultPath);
123
+ const enc = vault.secrets[key];
124
+ if (!enc)
125
+ throw new Error("secret not found: " + key);
126
+ const dk = resolveKey(vault, passphrase, keyBuf);
127
+ return (0, crypto_1.decrypt)(enc, dk);
128
+ }
129
+ function listKeys(vaultPath) {
130
+ return Object.keys(readVault(vaultPath).secrets);
131
+ }
132
+ function removeKey(vaultPath, key) {
133
+ const vault = readVault(vaultPath);
134
+ if (!vault.secrets[key])
135
+ throw new Error("secret not found: " + key);
136
+ delete vault.secrets[key];
137
+ writeVault(vaultPath, vault);
138
+ }
139
+ function getAllSecrets(vaultPath, passphrase, keyBuf) {
140
+ const vault = readVault(vaultPath);
141
+ const dk = resolveKey(vault, passphrase, keyBuf);
142
+ const result = {};
143
+ for (const [k, enc] of Object.entries(vault.secrets)) {
144
+ result[k] = (0, crypto_1.decrypt)(enc, dk);
145
+ }
146
+ return result;
147
+ }
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@boltpl/envseal",
3
+ "version": "1.0.0",
4
+ "description": "encrypt secrets at rest, inject at runtime",
5
+ "main": "dist/cli.js",
6
+ "bin": {
7
+ "envseal": "bin/envseal"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "prepublishOnly": "tsc"
12
+ },
13
+ "keywords": ["env", "secrets", "encryption", "vault", "dotenv", "security"],
14
+ "author": "mgrom",
15
+ "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/mgrom/envseal.git"
19
+ },
20
+ "homepage": "https://github.com/mgrom/envseal",
21
+ "engines": {
22
+ "node": ">=18"
23
+ },
24
+ "files": ["dist", "bin", "LICENSE", "README.md"],
25
+ "devDependencies": {
26
+ "@types/node": "^22.0.0",
27
+ "typescript": "^5.5.0"
28
+ }
29
+ }