diff --git a/src/shared/config.ts b/src/shared/config.ts index 7073f58..7a38134 100644 --- a/src/shared/config.ts +++ b/src/shared/config.ts @@ -34,7 +34,11 @@ export function replaceVariables( { key, replacement }, ); } else { - result = result.replace(pattern, replacement); + // Use a replacement function so that `$` sequences in the value + // (e.g. `$&`, `$$`, `$1`) are inserted literally rather than being + // interpreted as String.prototype.replace special patterns. This + // matters for secrets such as passwords that may contain `$`. + result = result.replace(pattern, () => replacement); } } } diff --git a/test/config.test.ts b/test/config.test.ts index 7e29f69..e2fa3c6 100644 --- a/test/config.test.ts +++ b/test/config.test.ts @@ -29,6 +29,15 @@ describe("replaceVariables", () => { consoleWarnSpy.mockRestore(); }); + it("should insert values containing $ patterns literally", () => { + // `$&`, `$$`, `$1` etc. are special in String.prototype.replace's + // replacement string — secrets containing them must be inserted verbatim. + const result = replaceVariables("${user_config.password}", { + "user_config.password": "pa$&ss$$wo$1rd", + }); + expect(result).toBe("pa$&ss$$wo$1rd"); + }); + it("should replace variables in objects", () => { const result = replaceVariables( { message: "Hello ${name}!" },