{"record":{"id":"1d1239c5d3836103","repo":"openai/codex","slug":"invalid-agent-identity-jwt-format","errorCode":null,"errorMessage":"invalid agent identity JWT format","messagePattern":"invalid agent identity JWT format","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"codex-rs/agent-identity/src/lib.rs","lineNumber":296,"sourceCode":"    let jwk = jwks\n        .find(&kid)\n        .with_context(|| format!(\"agent identity JWT kid {kid} is not trusted\"))?;\n    let decoding_key = DecodingKey::from_jwk(jwk).context(\"failed to build JWT decoding key\")?;\n    let mut validation = Validation::new(Algorithm::RS256);\n    validation.set_audience(&[AGENT_IDENTITY_JWT_AUDIENCE]);\n    validation.set_issuer(&[AGENT_IDENTITY_JWT_ISSUER]);\n    validation.required_spec_claims.insert(\"iss\".to_string());\n    validation.required_spec_claims.insert(\"aud\".to_string());\n    decode::<AgentIdentityJwtClaims>(jwt, &decoding_key, &validation)\n        .map(|data| data.claims)\n        .context(\"failed to verify agent identity JWT\")\n}\n\nfn decode_agent_identity_jwt_payload<T: DeserializeOwned>(jwt: &str) -> Result<T> {\n    let mut parts = jwt.split('.');\n    let (_header_b64, payload_b64, _sig_b64) = match (parts.next(), parts.next(), parts.next()) {\n        (Some(h), Some(p), Some(s)) if !h.is_empty() && !p.is_empty() && !s.is_empty() => (h, p, s),\n        _ => anyhow::bail!(\"invalid agent identity JWT format\"),\n    };\n    anyhow::ensure!(parts.next().is_none(), \"invalid agent identity JWT format\");\n\n    let payload_bytes = URL_SAFE_NO_PAD\n        .decode(payload_b64)\n        .context(\"agent identity JWT payload is not valid base64url\")?;\n    serde_json::from_slice(&payload_bytes).context(\"agent identity JWT payload is not valid JSON\")\n}\n\npub fn sign_task_registration_payload(\n    key: AgentIdentityKey<'_>,\n    timestamp: &str,\n) -> Result<String> {\n    let signing_key = signing_key_from_private_key_pkcs8_base64(key.private_key_pkcs8_base64)?;\n    let payload = format!(\"{}:{timestamp}\", key.agent_runtime_id);\n    Ok(BASE64_STANDARD.encode(signing_key.sign(payload.as_bytes()).to_bytes()))\n}\n","sourceCodeStart":278,"sourceCodeEnd":314,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-rs/agent-identity/src/lib.rs#L278-L314","documentation":"An inject_request_headers entry selected secret_file as its secret source, but the string is empty or whitespace-only. parse_secret_file requires a usable path because the proxy reads the file's contents at request time to build the header value, so a blank path can never resolve. Raised during validate_injected_headers at config load and again when hooks compile.","triggerScenarios":"[[network.mitm_hooks.actions.inject_request_headers]] with secret_env_var removed and secret_file = \"\" (or whitespace-only) — validation aborts config load before the proxy starts.","commonSituations":"A placeholder empty string that was never replaced; migration from secret_env_var where the env key was deleted but the file field was never filled in.","solutions":["Set secret_file to a non-empty absolute path the proxy can read: secret_file = \"/etc/codex/github.token\"","If the secret should come from the environment instead, remove secret_file and set secret_env_var","Check file permissions — the proxy process must be able to read it at request time"],"exampleFix":"// config.toml — before\n[[network.mitm_hooks.actions.inject_request_headers]]\nname = \"authorization\"\nsecret_file = \"\"\n\n// after\n[[network.mitm_hooks.actions.inject_request_headers]]\nname = \"authorization\"\nsecret_file = \"/etc/codex/github.token\"\nprefix = \"Bearer \"","handlingStrategy":"validation","validationCode":"for header in &hook.actions.inject_request_headers {\n    if let Some(file) = header.secret_file.as_deref() {\n        if file.trim().is_empty() {\n            return Err(anyhow!(\"{} has a blank secret_file\", header.name));\n        }\n    }\n}","typeGuard":"fn secret_file_nonempty(header: &InjectedHeaderConfig) -> bool {\n    header.secret_file.as_deref().map_or(true, |f| !f.trim().is_empty())\n}","tryCatchPattern":null,"preventionTips":["Treat a present-but-empty secret_file as a missing field in config linters","Verify the file exists and is readable by the proxy user before launch","Pick one secret mechanism per deployment and template only that field"],"tags":["rust","network-proxy","mitm-hooks","secrets","file-path","config-validation"],"backgroundTag":"invalid-file-path","analyzedSha":"339751715c64496cb86246bfb3935f40e309dd3d","analyzedAt":"2026-08-25T05:35:09.876Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}