From 6fe43c7ef7c88e95701c69e53931149b35c7edd3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 23:14:44 +0000 Subject: [PATCH 1/2] Initial plan From 8fcd559e19e692e64ffc1673131ff28fb1f6bee0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 4 Jun 2026 23:19:59 +0000 Subject: [PATCH 2/2] Deduplicate api-proxy env config helpers --- src/env-utils.test.ts | 90 +++++++++++++++++++++++- src/env-utils.ts | 23 ++++++ src/services/api-proxy-credential-env.ts | 18 +---- src/services/api-proxy-service-config.ts | 17 +---- 4 files changed, 117 insertions(+), 31 deletions(-) diff --git a/src/env-utils.test.ts b/src/env-utils.test.ts index 19b156513..28fd74881 100644 --- a/src/env-utils.test.ts +++ b/src/env-utils.test.ts @@ -1,4 +1,19 @@ -import { pickEnvVars } from './env-utils'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { WrapperConfig } from './types'; +import { getConfigEnvValue, getLowerCaseProcessEnvValue, pickEnvVars } from './env-utils'; + +function makeWrapperConfig(overrides: Partial = {}): WrapperConfig { + return { + agentCommand: 'echo test', + allowedDomains: [], + keepContainers: false, + logLevel: 'info', + workDir: '/tmp/env-utils-test', + ...overrides, + }; +} describe('pickEnvVars', () => { let savedEnv: Record; @@ -61,3 +76,76 @@ describe('pickEnvVars', () => { expect(pickEnvVars('TEST_PICK_SINGLE')).toEqual({ TEST_PICK_SINGLE: 'only-one' }); }); }); + +describe('getConfigEnvValue', () => { + let savedEnv: string | undefined; + + beforeEach(() => { + savedEnv = process.env.TEST_CONFIG_ENV_VALUE; + delete process.env.TEST_CONFIG_ENV_VALUE; + }); + + afterEach(() => { + if (savedEnv !== undefined) { + process.env.TEST_CONFIG_ENV_VALUE = savedEnv; + } else { + delete process.env.TEST_CONFIG_ENV_VALUE; + } + }); + + it('prefers additionalEnv over envFile and process.env, trimming the result', () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'env-utils-')); + const envFilePath = path.join(tempDir, '.env'); + fs.writeFileSync(envFilePath, 'TEST_CONFIG_ENV_VALUE= from-file \n'); + process.env.TEST_CONFIG_ENV_VALUE = ' from-process '; + + try { + const config = makeWrapperConfig({ + additionalEnv: { TEST_CONFIG_ENV_VALUE: ' from-additional ' }, + envAll: true, + envFile: envFilePath, + }); + + expect(getConfigEnvValue(config, 'TEST_CONFIG_ENV_VALUE')).toBe('from-additional'); + } finally { + fs.rmSync(tempDir, { recursive: true, force: true }); + } + }); + + it('falls back to process.env only when envAll is enabled and omits blank values', () => { + process.env.TEST_CONFIG_ENV_VALUE = ' '; + const config = makeWrapperConfig({ envAll: true }); + expect(getConfigEnvValue(config, 'TEST_CONFIG_ENV_VALUE')).toBeUndefined(); + + process.env.TEST_CONFIG_ENV_VALUE = ' from-process '; + expect(getConfigEnvValue(config, 'TEST_CONFIG_ENV_VALUE')).toBe('from-process'); + expect(getConfigEnvValue(makeWrapperConfig({ envAll: false }), 'TEST_CONFIG_ENV_VALUE')).toBeUndefined(); + }); +}); + +describe('getLowerCaseProcessEnvValue', () => { + let savedEnv: string | undefined; + + beforeEach(() => { + savedEnv = process.env.TEST_LOWERCASE_ENV_VALUE; + delete process.env.TEST_LOWERCASE_ENV_VALUE; + }); + + afterEach(() => { + if (savedEnv !== undefined) { + process.env.TEST_LOWERCASE_ENV_VALUE = savedEnv; + } else { + delete process.env.TEST_LOWERCASE_ENV_VALUE; + } + }); + + it('trims and lowercases process env values', () => { + process.env.TEST_LOWERCASE_ENV_VALUE = ' GitHub-OIDC '; + expect(getLowerCaseProcessEnvValue('TEST_LOWERCASE_ENV_VALUE')).toBe('github-oidc'); + }); + + it('returns undefined for blank process env values', () => { + process.env.TEST_LOWERCASE_ENV_VALUE = ' '; + expect(getLowerCaseProcessEnvValue('TEST_LOWERCASE_ENV_VALUE')).toBeUndefined(); + }); +}); diff --git a/src/env-utils.ts b/src/env-utils.ts index d4b6b2213..84255549b 100644 --- a/src/env-utils.ts +++ b/src/env-utils.ts @@ -1,3 +1,26 @@ +import { readEnvFile } from './github-env'; +import { WrapperConfig } from './types'; + +export function normalizeEnvValue(value: string | undefined): string | undefined { + const normalizedValue = value?.trim(); + return normalizedValue || undefined; +} + +export function getConfigEnvValue(config: WrapperConfig, key: string): string | undefined { + const envFileValue = config.envFile + ? readEnvFile(config.envFile)[key] + : undefined; + const value = + config.additionalEnv?.[key] ?? + envFileValue ?? + (config.envAll ? process.env[key] : undefined); + return normalizeEnvValue(value); +} + +export function getLowerCaseProcessEnvValue(key: string): string | undefined { + return normalizeEnvValue(process.env[key])?.toLowerCase(); +} + /** * Returns an object containing only the specified environment variable names * that are currently set (non-empty) in `process.env`. diff --git a/src/services/api-proxy-credential-env.ts b/src/services/api-proxy-credential-env.ts index 03aedad1c..e924c8fb9 100644 --- a/src/services/api-proxy-credential-env.ts +++ b/src/services/api-proxy-credential-env.ts @@ -1,7 +1,7 @@ import { logger } from '../logger'; import { WrapperConfig, API_PROXY_PORTS } from '../types'; -import { readEnvFile } from '../github-env'; import { COPILOT_PLACEHOLDER_TOKEN } from '../constants/placeholders'; +import { getConfigEnvValue, getLowerCaseProcessEnvValue } from '../env-utils'; import { NetworkConfig } from './squid-service'; interface ApiProxyCredentialEnvParams { @@ -14,18 +14,6 @@ interface ApiProxyCredentialEnvParams { // are runtime-configurable and not limited to a fixed allowlist. const RESPONSES_WIRE_API_MODEL_PATTERN = /(^|[/:])(gpt-5|o3)([-_.]|$)/i; -function getConfigEnvValue(config: WrapperConfig, key: string): string | undefined { - const envFileValue = config.envFile - ? readEnvFile(config.envFile)[key] - : undefined; - const value = - config.additionalEnv?.[key] ?? - envFileValue ?? - (config.envAll ? process.env[key] : undefined); - const normalizedValue = value?.trim(); - return normalizedValue || undefined; -} - function requiresResponsesWireApi(copilotModel: string): boolean { return RESPONSES_WIRE_API_MODEL_PATTERN.test(copilotModel); } @@ -35,8 +23,8 @@ export function buildAgentCredentialEnv(params: ApiProxyCredentialEnvParams): Re if (!networkConfig.proxyIp) { throw new Error('buildAgentCredentialEnv: networkConfig.proxyIp is required'); } - const normalizedAuthType = (process.env.AWF_AUTH_TYPE || '').trim().toLowerCase(); - const normalizedAuthProvider = (process.env.AWF_AUTH_PROVIDER || '').trim().toLowerCase(); + const normalizedAuthType = getLowerCaseProcessEnvValue('AWF_AUTH_TYPE') || ''; + const normalizedAuthProvider = getLowerCaseProcessEnvValue('AWF_AUTH_PROVIDER') || ''; const shouldProxyAnthropic = Boolean(config.anthropicApiKey || (normalizedAuthType === 'github-oidc' && normalizedAuthProvider === 'anthropic')); const agentEnvAdditions: Record = { diff --git a/src/services/api-proxy-service-config.ts b/src/services/api-proxy-service-config.ts index 9609d1f87..59147c54a 100644 --- a/src/services/api-proxy-service-config.ts +++ b/src/services/api-proxy-service-config.ts @@ -4,10 +4,9 @@ import { SQUID_PORT, } from '../constants'; import { stripScheme } from '../host-env'; -import { readEnvFile } from '../github-env'; import { buildRuntimeImageRef } from '../image-tag'; import { WrapperConfig, API_PROXY_HEALTH_PORT } from '../types'; -import { pickEnvVars } from '../env-utils'; +import { getConfigEnvValue, getLowerCaseProcessEnvValue, pickEnvVars } from '../env-utils'; import { NetworkConfig, ImageBuildConfig } from './squid-service'; import { applyHostPathPrefixToVolumes } from './host-path-prefix'; import { buildContainerSecurityHardening } from './service-security'; @@ -72,25 +71,13 @@ function resolveProviderSessionId(config: WrapperConfig): string | undefined { return normalizedValue || undefined; } -function getConfigEnvValue(config: WrapperConfig, key: string): string | undefined { - const envFileValue = config.envFile - ? readEnvFile(config.envFile)[key] - : undefined; - const value = - config.additionalEnv?.[key] ?? - envFileValue ?? - (config.envAll ? process.env[key] : undefined); - const normalizedValue = value?.trim(); - return normalizedValue || undefined; -} - export function buildApiProxyServiceConfig(params: ApiProxyServiceConfigParams): any { const { config, networkConfig, apiProxyLogsPath, imageConfig } = params; if (!networkConfig.proxyIp) { throw new Error('buildApiProxyServiceConfig: networkConfig.proxyIp is required'); } const { useGHCR, registry, parsedTag, projectRoot } = imageConfig; - const normalizedAuthType = (process.env.AWF_AUTH_TYPE || '').trim().toLowerCase(); + const normalizedAuthType = getLowerCaseProcessEnvValue('AWF_AUTH_TYPE') || ''; const proxyService: any = { container_name: API_PROXY_CONTAINER_NAME,