{"record":{"id":"04945fd7f281581c","repo":"openai/codex","slug":"failed-to-decrypt-encrypted-task-id","errorCode":null,"errorMessage":"failed to decrypt encrypted task id","messagePattern":"failed to decrypt encrypted task id","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"codex-rs/agent-identity/src/lib.rs","lineNumber":421,"sourceCode":"    }\n    let encrypted_task_id = response\n        .encrypted_task_id\n        .or(response.encrypted_task_id_camel)\n        .context(\"agent task registration response omitted task id\")?;\n    decrypt_task_id_response(key, &encrypted_task_id)\n}\n\npub fn decrypt_task_id_response(\n    key: AgentIdentityKey<'_>,\n    encrypted_task_id: &str,\n) -> Result<String> {\n    let signing_key = signing_key_from_private_key_pkcs8_base64(key.private_key_pkcs8_base64)?;\n    let ciphertext = BASE64_STANDARD\n        .decode(encrypted_task_id)\n        .context(\"encrypted task id is not valid base64\")?;\n    let plaintext = curve25519_secret_key_from_signing_key(&signing_key)\n        .unseal(&ciphertext)\n        .map_err(|_| anyhow::anyhow!(\"failed to decrypt encrypted task id\"))?;\n    String::from_utf8(plaintext).context(\"decrypted task id is not valid UTF-8\")\n}\n\npub fn generate_agent_key_material() -> Result<GeneratedAgentKeyMaterial> {\n    let mut seed_material = [0u8; AGENT_IDENTITY_KEY_SEED_BYTES];\n    OsRng\n        .try_fill_bytes(&mut seed_material)\n        .context(\"failed to generate agent identity private key seed material\")?;\n    // Ed25519 stores a 32-byte seed, so derive it from all sampled seed material.\n    let mut digest = Sha512::new();\n    digest.update(AGENT_IDENTITY_KEY_DERIVATION_CONTEXT);\n    digest.update(seed_material);\n    let digest = digest.finalize();\n    let mut secret_key_bytes = [0u8; 32];\n    secret_key_bytes.copy_from_slice(&digest[..32]);\n    let signing_key = SigningKey::from_bytes(&secret_key_bytes);\n    let private_key_pkcs8 = signing_key\n        .to_pkcs8_der()","sourceCodeStart":403,"sourceCodeEnd":439,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-rs/agent-identity/src/lib.rs#L403-L439","documentation":"Host::parse runs normalize_host (trim whitespace, strip brackets and a single :port, lowercase, drop trailing dots, canonicalize IP literals) and rejects a result that comes out empty. Host is the unit used for network policy evaluation, so callers are whatever feeds hosts into policy: config domain entries or host strings extracted from requests. Inputs that normalize to empty include an empty string, whitespace-only input, \":8080\" (empty host before the port), and a lone \".\" (nothing left after trimming trailing dots).","triggerScenarios":"Calling Host::parse with an empty, whitespace-only, port-only (\":443\"), or dot-only (\".\") string — typically not hand-written but passed through from an empty host in a config list, or from URL handling where the authority was missing (e.g. a URL like http:///path with no host).","commonSituations":"Config lists containing empty strings from templating; upstream code splitting host:port and passing the empty half; request paths where a malformed URL yielded no host.","solutions":["Reject or skip empty host strings before calling Host::parse","When splitting host:port yourself, verify the host part is non-empty after the split","If hosts come from URLs, parse with url::Url and require .host_str() to return Some and be non-empty"],"exampleFix":"// before\nlet host = Host::parse(raw)?; // raw may be \"\" or \":443\" from a bad config entry\n\n// after\nif raw.trim().is_empty() || raw.trim().trim_end_matches('.').is_empty() {\n    continue; // skip malformed entry instead of failing policy build\n}\nlet host = Host::parse(raw)?;","handlingStrategy":"validation","validationCode":"// Mirror of normalize_host for pre-checking callers\nfn parseable_host(input: &str) -> bool {\n    let h = input.trim();\n    let h = h.strip_prefix('[').and_then(|r| r.split(']').next()).unwrap_or(h);\n    let h = if h.matches(':').count() == 1 { h.split(':').next().unwrap_or(\"\") } else { h };\n    !h.to_ascii_lowercase().trim_end_matches('.').is_empty()\n}\nif !parseable_host(raw) {\n    return Err(anyhow!(\"unusable host string {raw:?}\"));\n}\nlet host = Host::parse(raw)?;","typeGuard":"fn is_nonempty_host_str(input: &str) -> bool {\n    !input.trim().is_empty() && input.trim() != \".\" && !input.trim().starts_with(':')\n}","tryCatchPattern":"let host = match Host::parse(raw) {\n    Ok(host) => host,\n    Err(err) if err.to_string().contains(\"host is empty\") => continue, // skip bad entry\n    Err(err) => return Err(err),\n};","preventionTips":["Filter empty strings out of host lists at config load","When splitting host:port, validate the host half before use","Derive hosts from url::Url::host_str() rather than string surgery"],"tags":["rust","network-proxy","hostname","policy","validation"],"backgroundTag":"invalid-hostname","analyzedSha":"339751715c64496cb86246bfb3935f40e309dd3d","analyzedAt":"2026-08-25T05:35:09.876Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}