Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/shared/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ export function replaceVariables(

// Replace all variables in the string
for (const [key, replacement] of Object.entries(variables)) {
const pattern = new RegExp(`\\$\\{${key}\\}`, "g");
// Escape regex metacharacters in the key before building the pattern.
// Keys contain "." (e.g. "user_config.cs_password") and the manifest
// schema allows arbitrary user_config key names, so an unescaped key
// would over-match (any char for ".") or throw/ReDoS on a metacharacter.
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const pattern = new RegExp(`\\$\\{${escapedKey}\\}`, "g");

// Check if this pattern actually exists in the string
if (result.match(pattern)) {
Expand Down
20 changes: 20 additions & 0 deletions test/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,26 @@ describe("replaceVariables", () => {
const result = replaceVariables("Hello ${unknown}!", {});
expect(result).toBe("Hello ${unknown}!");
});

it("should treat '.' in the key as a literal, not a regex wildcard", () => {
// The "." must match only a literal dot. A template with a different
// character in that position must NOT be substituted.
const result = replaceVariables("${user_config_cs_password}", {
"user_config.cs_password": "SECRET",
});
expect(result).toBe("${user_config_cs_password}");
});

it("should not throw on keys containing regex metacharacters", () => {
// A user_config key with a metacharacter (e.g. "[") must not crash regex
// construction; it should match its own literal placeholder.
expect(() =>
replaceVariables("x ${a[b} y", { "a[b": "VALUE" }),
).not.toThrow();
expect(replaceVariables("x ${a[b} y", { "a[b": "VALUE" })).toBe(
"x VALUE y",
);
});
});

describe("getMcpConfigForManifest", () => {
Expand Down