mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-26 17:12:27 +03:00
fix(db): fallback load STORAGE_ENCRYPTION_KEY from env files (#11614)
Merged via /merge-batch (lote 2026-08-26 batch 2, v3.8.51). Encontrei um bug real na validação: o guard `NODE_ENV==="test"||VITEST` não cobre o test runner nativo do projeto (`node --import tsx/esm --test`, que não seta NODE_ENV), então o fallback estava lendo `~/.omniroute/.env` — o .env de PRODUÇÃO real do operador — durante qualquer execução de teste sem esse env var. Corrigi usando o `isTestContext()` já existente e mais robusto em `dataPaths.ts` (cobre `NODE_TEST_CONTEXT`/`--test` argv), e troquei o path hardcoded `os.homedir()/.omniroute` por `resolveDataDir()` para respeitar `DATA_DIR` quando configurado. Enviei a correção para o seu branch antes do merge. Validado: 7/7 testes de `db-encryption.test.ts` passando. Obrigado pela contribuição — a ideia central (fallback do STORAGE_ENCRYPTION_KEY) é boa e necessária.
This commit is contained in:
@@ -91,7 +91,7 @@ export function resolveDataDir({ isCloud = false }: { isCloud?: boolean } = {}):
|
||||
* plus the AGENTS.md single-file command, which does NOT load
|
||||
* `tests/_setup/isolateDataDir.ts`.
|
||||
*/
|
||||
function isTestContext(): boolean {
|
||||
export function isTestContext(): boolean {
|
||||
return (
|
||||
process.env.NODE_ENV === "test" ||
|
||||
!!process.env.VITEST ||
|
||||
|
||||
@@ -76,6 +76,43 @@ function decryptFailureSignature(
|
||||
const RECOVERY_HINT =
|
||||
"Re-authenticate this account, or verify STORAGE_ENCRYPTION_KEY matches the key used to store it.";
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import { isTestContext, resolveDataDir } from "../dataPaths.ts";
|
||||
|
||||
function ensureSecretLoaded(): string | undefined {
|
||||
if (isTestContext()) {
|
||||
return process.env.STORAGE_ENCRYPTION_KEY;
|
||||
}
|
||||
if (process.env.STORAGE_ENCRYPTION_KEY) {
|
||||
return process.env.STORAGE_ENCRYPTION_KEY;
|
||||
}
|
||||
const candidates = [
|
||||
path.join(resolveDataDir(), ".env"),
|
||||
path.join(process.cwd(), ".env"),
|
||||
path.join(os.homedir(), ".hermes", ".env"),
|
||||
];
|
||||
for (const envPath of candidates) {
|
||||
try {
|
||||
if (fs.existsSync(envPath)) {
|
||||
const content = fs.readFileSync(envPath, "utf8");
|
||||
for (const line of content.split("\n")) {
|
||||
const trimmed = line.trim();
|
||||
if (trimmed.startsWith("STORAGE_ENCRYPTION_KEY=")) {
|
||||
const val = trimmed.split("=", 2)[1]?.trim().replace(/^["'](.*)["']$/, "$1");
|
||||
if (val) {
|
||||
process.env.STORAGE_ENCRYPTION_KEY = val;
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the PRIMARY encryption key using the static salt.
|
||||
* This is the canonical key derivation that all new encryptions use.
|
||||
@@ -84,7 +121,7 @@ const RECOVERY_HINT =
|
||||
function getStaticKey(): Buffer | null {
|
||||
if (_staticKey !== null) return _staticKey;
|
||||
|
||||
const secret = process.env.STORAGE_ENCRYPTION_KEY;
|
||||
const secret = ensureSecretLoaded();
|
||||
if (!secret || typeof secret !== "string" || secret.trim().length === 0) return null;
|
||||
|
||||
try {
|
||||
@@ -110,7 +147,7 @@ function getStaticKey(): Buffer | null {
|
||||
function getLegacyDynamicKey(): Buffer | null {
|
||||
if (_legacyDynamicKey !== null) return _legacyDynamicKey;
|
||||
|
||||
const secret = process.env.STORAGE_ENCRYPTION_KEY;
|
||||
const secret = ensureSecretLoaded();
|
||||
if (!secret || typeof secret !== "string" || secret.trim().length === 0) return null;
|
||||
|
||||
const dynamicSalt = createHash("sha256").update(secret).digest().slice(0, 16);
|
||||
@@ -124,7 +161,7 @@ function getLegacyDynamicKey(): Buffer | null {
|
||||
|
||||
/** Check if encryption is enabled. */
|
||||
export function isEncryptionEnabled(): boolean {
|
||||
return !!process.env.STORAGE_ENCRYPTION_KEY;
|
||||
return !!ensureSecretLoaded();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -123,3 +123,18 @@ test("legacy encryption migration parses ciphertext in canonical payload order",
|
||||
assert.match(migrated.value, /^enc:v1:/);
|
||||
assert.equal(encryption.decrypt(migrated.value), "legacy-provider-token");
|
||||
});
|
||||
|
||||
test("ensureSecretLoaded loads key from .env file when process.env.STORAGE_ENCRYPTION_KEY is unset", async () => {
|
||||
delete process.env.STORAGE_ENCRYPTION_KEY;
|
||||
const originalNodeEnv = process.env.NODE_ENV;
|
||||
try {
|
||||
delete process.env.NODE_ENV;
|
||||
const encryption = await importFresh("src/lib/db/encryption.ts");
|
||||
// Under non-test NODE_ENV, if a local .env exists with a key, it discovers it
|
||||
const enabled = encryption.isEncryptionEnabled();
|
||||
assert.equal(typeof enabled, "boolean");
|
||||
} finally {
|
||||
process.env.NODE_ENV = originalNodeEnv;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user