From 3ad140d2f5469a5458d2e1f054770cb7e1f65b28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Br=C3=A4nstr=C3=B6m?= Date: Mon, 11 Aug 2025 21:43:32 +0200 Subject: [PATCH 1/2] feat: add environment variable interpolation for API keys - Add interpolateEnvVars function to support $VAR_NAME and ${VAR_NAME} syntax - Apply interpolation to config after JSON5 parsing in readConfigFile() - Enables secure API key management without hardcoding in config.json - Supports nested objects and arrays for comprehensive interpolation - Maintains backward compatibility with existing configurations Example usage in config.json: { "OPENAI_API_KEY": "$OPENAI_API_KEY", "Providers": [ { "name": "openai", "api_key": "$OPENAI_API_KEY" } ] } --- src/utils/index.ts | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/utils/index.ts b/src/utils/index.ts index 5d153ab..3558d38 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -11,6 +11,26 @@ import { import { getSystemUUID, generateTempAPIKey, getTempAPIKey } from "./systemUUID"; import { cleanupLogFiles } from "./logCleanup"; +// Function to interpolate environment variables in config values +const interpolateEnvVars = (obj: any): any => { + if (typeof obj === "string") { + // Replace $VAR_NAME or ${VAR_NAME} with environment variable values + return obj.replace(/\$\{([^}]+)\}|\$([A-Z_][A-Z0-9_]*)/g, (match, braced, unbraced) => { + const varName = braced || unbraced; + return process.env[varName] || match; // Keep original if env var doesn't exist + }); + } else if (Array.isArray(obj)) { + return obj.map(interpolateEnvVars); + } else if (obj !== null && typeof obj === "object") { + const result: any = {}; + for (const [key, value] of Object.entries(obj)) { + result[key] = interpolateEnvVars(value); + } + return result; + } + return obj; +}; + const ensureDir = async (dir_path: string) => { try { await fs.access(dir_path); @@ -52,7 +72,9 @@ export const readConfigFile = async () => { const config = await fs.readFile(CONFIG_FILE, "utf-8"); try { // Try to parse with JSON5 first (which also supports standard JSON) - return JSON5.parse(config); + const parsedConfig = JSON5.parse(config); + // Interpolate environment variables in the parsed config + return interpolateEnvVars(parsedConfig); } catch (parseError) { console.error(`Failed to parse config file at ${CONFIG_FILE}`); console.error("Error details:", (parseError as Error).message); From 5c1a193f4dc1895b5f092b85c34470d0f783fd2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Br=C3=A4nstr=C3=B6m?= Date: Mon, 11 Aug 2025 21:44:14 +0200 Subject: [PATCH 2/2] docs: add environment variable interpolation documentation - Document and syntax in README - Add example usage in config.json - Explain security benefits and recursive interpolation support --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index e4198ab..e10265b 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,27 @@ The `config.json` file has several key sections: - **`Router`**: Used to set up routing rules. `default` specifies the default model, which will be used for all requests if no other route is configured. - **`API_TIMEOUT_MS`**: Specifies the timeout for API calls in milliseconds. +#### Environment Variable Interpolation + +Claude Code Router supports environment variable interpolation for secure API key management. You can reference environment variables in your `config.json` using either `$VAR_NAME` or `${VAR_NAME}` syntax: + +```json +{ + "OPENAI_API_KEY": "$OPENAI_API_KEY", + "GEMINI_API_KEY": "${GEMINI_API_KEY}", + "Providers": [ + { + "name": "openai", + "api_base_url": "https://api.openai.com/v1/chat/completions", + "api_key": "$OPENAI_API_KEY", + "models": ["gpt-5", "gpt-5-mini"] + } + ] +} +``` + +This allows you to keep sensitive API keys in environment variables instead of hardcoding them in configuration files. The interpolation works recursively through nested objects and arrays. + Here is a comprehensive example: ```json