import { controllerMcpCallerCredentialEnvironmentVariable } from '../ts_interfaces/index.js'; const controllerEnvironmentPrefix = 'HARNESS_CONTROLLER_'; const forwardedEnvironmentVariable = 'HARNESS_CONTROLLER_FORWARD_ENV'; const runtimeEnvironmentAllowlist = new Set([ 'AGL_HOME', 'CLAUDE_CONFIG_DIR', 'APPDATA', 'COLORTERM', 'COMSPEC', 'FORCE_COLOR', 'HOME', 'HOMEDRIVE', 'HOMEPATH', 'HTTP_PROXY', 'HTTPS_PROXY', 'LANG', 'LOCALAPPDATA', 'LOGNAME', 'NODE_EXTRA_CA_CERTS', 'NO_PROXY', 'PATH', 'PATHEXT', 'SHELL', 'SSL_CERT_DIR', 'SSL_CERT_FILE', 'SYSTEMROOT', 'TEMP', 'TERM', 'TMP', 'TMPDIR', 'TZ', 'USER', 'USERPROFILE', 'WINDIR', 'XDG_CACHE_HOME', 'XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'XDG_RUNTIME_DIR', 'XDG_STATE_HOME', ]); const environmentVariablePattern = /^[A-Za-z_][A-Za-z0-9_]*$/u; const proxyEnvironmentVariables = new Set(['HTTP_PROXY', 'HTTPS_PROXY']); const maxForwardedEnvironmentVariables = 64; const maxForwardingDirectiveBytes = 4096; const readExplicitlyForwardedEnvironmentNames = (environmentArg: NodeJS.ProcessEnv): string[] => { const forwardingDirective = environmentArg[forwardedEnvironmentVariable]; if (!forwardingDirective) return []; if (Buffer.byteLength(forwardingDirective, 'utf8') > maxForwardingDirectiveBytes) { throw new Error(`${forwardedEnvironmentVariable} exceeds its size limit.`); } const requestedNames = forwardingDirective .split(',') .map((name) => name.trim()) .filter((name) => name.length > 0); if (requestedNames.length > maxForwardedEnvironmentVariables) { throw new Error(`${forwardedEnvironmentVariable} names too many variables.`); } const uniqueNames = [...new Set(requestedNames)]; for (const name of uniqueNames) { if (!environmentVariablePattern.test(name)) { throw new Error(`${forwardedEnvironmentVariable} contains an invalid variable name.`); } const normalizedName = name.toUpperCase(); if ( normalizedName === 'HARNESS_CONTROLLER' || normalizedName.startsWith(controllerEnvironmentPrefix) || normalizedName === 'OPENCODE_SERVER_USERNAME' || normalizedName === 'OPENCODE_SERVER_PASSWORD' || normalizedName === 'OPENCODE_AUTH_CONTENT' || normalizedName === controllerMcpCallerCredentialEnvironmentVariable ) { throw new Error(`${forwardedEnvironmentVariable} cannot forward ${name}.`); } } return uniqueNames; }; const proxyValueRequiresExplicitForwarding = (valueArg: string): boolean => { try { const parsed = new URL(valueArg); return parsed.username.length > 0 || parsed.password.length > 0; } catch { return true; } }; /** Environment safe to expose to controller-owned child runtimes and terminals. */ export const createSanitizedRuntimeEnvironment = ( environmentArg: NodeJS.ProcessEnv = process.env, ): NodeJS.ProcessEnv => { if (Object.entries(environmentArg).some(([key, value]) => ( value !== undefined && key.toUpperCase() === 'OPENCODE_AUTH_CONTENT' ))) { throw new Error('OPENCODE_AUTH_CONTENT cannot be supplied to controller-owned runtimes.'); } const environment: NodeJS.ProcessEnv = {}; const explicitlyForwardedNames = readExplicitlyForwardedEnvironmentNames(environmentArg); const explicitlyForwardedNormalizedNames = new Set( explicitlyForwardedNames.map((nameArg) => nameArg.toUpperCase()), ); for (const [key, value] of Object.entries(environmentArg)) { const normalizedKey = key.toUpperCase(); if ( value !== undefined && (runtimeEnvironmentAllowlist.has(normalizedKey) || normalizedKey.startsWith('LC_')) && ( !proxyEnvironmentVariables.has(normalizedKey) || explicitlyForwardedNormalizedNames.has(normalizedKey) || !proxyValueRequiresExplicitForwarding(value) ) ) { environment[key] = value; } } for (const name of explicitlyForwardedNames) { const value = environmentArg[name]; if (value !== undefined) environment[name] = value; } return environment; };