From 6bf81a78f32bdd3f08861371ad465155986551ed Mon Sep 17 00:00:00 2001 From: Anirudh Sharma Date: Tue, 13 Oct 2026 19:14:40 +0530 Subject: [PATCH] Fix: preserve dollar sequences in environment and file secrets (#7242) Co-authored-by: Codex --- src/utils/config/config.js | 4 ++-- src/utils/config/config.test.js | 35 ++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/utils/config/config.js b/src/utils/config/config.js index 65b8866f1..92e8fb460 100644 --- a/src/utils/config/config.js +++ b/src/utils/config/config.js @@ -69,11 +69,11 @@ export function substituteEnvironmentVars(str) { const cachedVars = getCachedEnvironmentVars(); cachedVars.forEach(([key, value]) => { if (key.startsWith(homepageVarPrefix)) { - result = result.replaceAll(`{{${key}}}`, value); + result = result.replaceAll(`{{${key}}}`, () => value); } else if (key.startsWith(homepageFilePrefix)) { const filename = value; const fileContents = readFileSync(filename, "utf8"); - result = result.replaceAll(`{{${key}}}`, fileContents); + result = result.replaceAll(`{{${key}}}`, () => fileContents); } }); } diff --git a/src/utils/config/config.test.js b/src/utils/config/config.test.js index e2936b68c..8d4678e05 100644 --- a/src/utils/config/config.test.js +++ b/src/utils/config/config.test.js @@ -1,4 +1,4 @@ -import { mkdtempSync, writeFileSync } from "node:fs"; +import { mkdtempSync, rmdirSync, unlinkSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; @@ -37,6 +37,39 @@ describe("utils/config/config", () => { expect(mod.substituteEnvironmentVars("token={{HOMEPAGE_FILE_SECRET}}")).toBe("token=secret"); }); + it.each(["pa$$word", "pa$&word", "pa$`word", "pa$'word", "pa$1word"])( + "substituteEnvironmentVars preserves literal environment value %j in every occurrence", + async (secret) => { + process.env.HOMEPAGE_VAR_SECRET = secret; + + const mod = await import("./config"); + expect( + mod.substituteEnvironmentVars("before {{HOMEPAGE_VAR_SECRET}} between {{HOMEPAGE_VAR_SECRET}} after"), + ).toBe(`before ${secret} between ${secret} after`); + }, + ); + + it.each(["pa$$word", "pa$&word", "pa$`word", "pa$'word", "pa$1word"])( + "substituteEnvironmentVars preserves literal secret file contents %j in every occurrence", + async (secret) => { + const dir = mkdtempSync(path.join(tmpdir(), "homepage-config-test-")); + const secretPath = path.join(dir, "secret.txt"); + writeFileSync(secretPath, secret, "utf8"); + + try { + process.env.HOMEPAGE_FILE_SECRET = secretPath; + + const mod = await import("./config"); + expect( + mod.substituteEnvironmentVars("before {{HOMEPAGE_FILE_SECRET}} between {{HOMEPAGE_FILE_SECRET}} after"), + ).toBe(`before ${secret} between ${secret} after`); + } finally { + unlinkSync(secretPath); + rmdirSync(dir); + } + }, + ); + it("getSettings reads from HOMEPAGE_CONFIG_DIR and converts layout list to an object", async () => { const dir = mkdtempSync(path.join(tmpdir(), "homepage-settings-test-")); process.env.HOMEPAGE_CONFIG_DIR = dir;