{"record":{"id":"2118e9c6c8bdbf2b","repo":"openai/codex","slug":"expected-exactly-one-of-secret-env-var-or-secret-f","errorCode":null,"errorMessage":"expected exactly one of secret_env_var or secret_file","messagePattern":"expected exactly one of secret_env_var or secret_file","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"codex-rs/network-proxy/src/mitm_hook.rs","lineNumber":367,"sourceCode":"    FileFn: Fn(&AbsolutePathBuf) -> Result<String>,\n{\n    let name = parse_header_name(&header.name)?;\n    let (secret, source) = match (\n        header.secret_env_var.as_deref(),\n        header.secret_file.as_deref(),\n    ) {\n        (Some(env_var), None) => {\n            let value = resolve_env_var(env_var)\n                .ok_or_else(|| anyhow!(\"missing required environment variable {env_var}\"))?;\n            (value, SecretSource::EnvVar(env_var.to_string()))\n        }\n        (None, Some(secret_file)) => {\n            let path = parse_secret_file(secret_file)?;\n            let value = read_secret_file(&path)?;\n            (value, SecretSource::File(path))\n        }\n        _ => {\n            return Err(anyhow!(\n                \"expected exactly one of secret_env_var or secret_file\"\n            ));\n        }\n    };\n\n    let prefix = header.prefix.clone().unwrap_or_default();\n    let value = HeaderValue::from_str(&format!(\"{prefix}{secret}\"))\n        .with_context(|| format!(\"invalid value for injected header {}\", header.name))?;\n\n    Ok(ResolvedInjectedHeader {\n        name,\n        value,\n        source,\n    })\n}\n\nfn hook_matches(hook: &MitmHook, req: &Request) -> bool {\n    let method = req.method().as_str().to_ascii_uppercase();","sourceCodeStart":349,"sourceCodeEnd":385,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-rs/network-proxy/src/mitm_hook.rs#L349-L385","documentation":"Each inject_request_headers entry must declare exactly one secret source. compile_injected_header matches on the (secret_env_var, secret_file) pair: (Some, None) reads the environment variable, (None, Some) reads the file, and every other combination — both set, or neither — returns 'expected exactly one of secret_env_var or secret_file' under the context 'failed to compile injected header {name}'.","triggerScenarios":"A header entry with both keys set ({ secret_env_var = \"MY_TOKEN\", secret_file = \"/etc/codex/secrets/token\" }) or with neither key present; raised by compile_mitm_hooks_with_resolvers at hook compilation.","commonSituations":"Copy-pasting an example and adding the second source 'for safety'; migrating from env var to file and leaving both keys; commenting out one key and deleting the other, leaving the entry secret-less.","solutions":["Keep exactly one of secret_env_var or secret_file per entry — prefer the file for services, the env var for interactive runs","If the header needs no secret at all, remove the entire inject_request_headers entry","Run validate_mitm_hook_config after editing config so the failure surfaces before proxy start"],"exampleFix":"# before — both sources\n[[network.mitm_hooks.actions.inject_request_headers]]\nname = \"authorization\"\nsecret_env_var = \"MY_TOKEN\"\nsecret_file = \"/etc/codex/secrets/my_token\"\n\n# after — exactly one source\n[[network.mitm_hooks.actions.inject_request_headers]]\nname = \"authorization\"\nsecret_env_var = \"MY_TOKEN\"","handlingStrategy":"validation","validationCode":"// Rust — exactly one source per injected header\nfor hook in &config.mitm_hooks {\n    for h in &hook.actions.inject_request_headers {\n        if h.secret_env_var.is_some() == h.secret_file.is_some() {\n            return Err(anyhow!(\"header {} must set exactly one of secret_env_var or secret_file\", h.name));\n        }\n    }\n}","typeGuard":"fn has_single_secret_source(h: &InjectedHeaderConfig) -> bool {\n    h.secret_env_var.is_some() != h.secret_file.is_some()\n}","tryCatchPattern":"match compile_mitm_hooks(&config) {\n    Ok(_) => {}\n    Err(err) => eprintln!(\"{err:#}\"), // names the header and the rule that failed\n}","preventionTips":["Pick one secret source per header entry and delete the other key","Treat secret-source drift as a review blocker in config PRs","Run validate_mitm_hook_config in CI on every config change"],"tags":["network","mitm","codex","secrets","config-validation"],"backgroundTag":"mutually-exclusive-config-fields","analyzedSha":"339751715c64496cb86246bfb3935f40e309dd3d","analyzedAt":"2026-08-25T05:35:09.876Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}