-
Notifications
You must be signed in to change notification settings - Fork 488
Expand file tree
/
Copy pathenvcrypt.js
More file actions
123 lines (104 loc) · 3.6 KB
/
Copy pathenvcrypt.js
File metadata and controls
123 lines (104 loc) · 3.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import fs from "fs";
import path from "path";
import dotenv from "dotenv";
import { repoPath } from "./repo-root.js";
const DEFAULT_ENV_PATH = repoPath(".env");
const DEFAULT_KEY_PATH = repoPath(".envrypt");
function isEncryptedMarker(line) {
return line.trim().toLowerCase() === "# encrypted";
}
function parseEncryptedKeys(filePath) {
if (!fs.existsSync(filePath)) return new Set();
const encrypted = new Set();
let encryptedNext = false;
for (const line of fs.readFileSync(filePath, "utf8").split(/\r?\n/)) {
const trimmed = line.trim();
if (!trimmed) {
encryptedNext = false;
continue;
}
if (isEncryptedMarker(trimmed)) {
encryptedNext = true;
continue;
}
const match = trimmed.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/);
if (match && encryptedNext) encrypted.add(match[1]);
encryptedNext = false;
}
return encrypted;
}
function getEnvcryptKey(keyPath = DEFAULT_KEY_PATH) {
const key =
process.env.ENVRYPT_KEY ||
process.env.ENVCRYPT_KEY ||
(fs.existsSync(keyPath) ? fs.readFileSync(keyPath, "utf8").trim() : "");
if (!key) return null;
if (key.length < 8) {
throw new Error("Envrypt encryption key must be at least 8 characters long.");
}
return key;
}
function shouldEncryptEnvKey(envKey) {
return envKey.endsWith("_KEY") ||
envKey.startsWith("ENVRIPT_") ||
/(?:PRIVATE|SECRET|TOKEN|PASSPHRASE|PASSWORD|MNEMONIC)/i.test(envKey);
}
export function envryptEncrypt(value, key) {
return Buffer.from(
Array.from(String(value), (char, index) =>
String.fromCharCode(char.charCodeAt(0) ^ key.charCodeAt(index % key.length))
).join(""),
"ascii",
).toString("base64");
}
export function envryptDecrypt(value, key) {
const encrypted = Buffer.from(String(value), "base64").toString("utf8");
return Array.from(encrypted, (char, index) =>
String.fromCharCode(char.charCodeAt(0) ^ key.charCodeAt(index % key.length))
).join("");
}
export function loadEnv({ envPath = DEFAULT_ENV_PATH, keyPath = DEFAULT_KEY_PATH, override = true } = {}) {
// override=true so repo .env wins over stale PM2-injected env on restart
dotenv.config({ path: envPath, override, quiet: true });
const encryptedKeys = parseEncryptedKeys(envPath);
if (encryptedKeys.size === 0) return { encryptedKeys: [] };
const key = getEnvcryptKey(keyPath);
if (!key) {
throw new Error(
`Encrypted env values found in ${envPath}, but no envrypt key was provided. ` +
"Create .envrypt or set ENVRYPT_KEY / ENVCRYPT_KEY.",
);
}
for (const envKey of encryptedKeys) {
const value = process.env[envKey];
if (value == null || value === "") continue;
process.env[envKey] = envryptDecrypt(value, key);
}
return { encryptedKeys: [...encryptedKeys] };
}
export function encryptEnvRaw({
rawPath = repoPath(".env.raw"),
outPath = DEFAULT_ENV_PATH,
keyPath = DEFAULT_KEY_PATH,
} = {}) {
if (!fs.existsSync(rawPath)) {
throw new Error(`No ${rawPath} file found.`);
}
const key = getEnvcryptKey(keyPath);
if (!key) {
throw new Error("Create .envrypt or set ENVRYPT_KEY / ENVCRYPT_KEY before encrypting.");
}
const parsed = dotenv.parse(fs.readFileSync(rawPath, "utf8"));
const lines = ["# Envrypt managed environment file.", ""];
for (const [envKey, value] of Object.entries(parsed)) {
if (shouldEncryptEnvKey(envKey)) {
lines.push("# encrypted");
lines.push(`${envKey}=${envryptEncrypt(value, key)}`, "");
} else {
lines.push(`${envKey}=${value}`);
}
}
fs.writeFileSync(outPath, `${lines.join("\n").replace(/\n+$/, "")}\n`);
return { rawPath, outPath };
}
loadEnv();