{"record":{"id":"11bb0f5aae1aaa80","repo":"openai/codex","slug":"network-proxy-attribution-environment-mismatch","errorCode":null,"errorMessage":"network proxy attribution environment mismatch","messagePattern":"network proxy attribution environment mismatch","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"codex-rs/network-proxy/src/attribution.rs","lineNumber":63,"sourceCode":"    type Output = S::Output;\n    type Error = BoxError;\n\n    async fn serve(&self, mut stream: TcpStream) -> Result<Self::Output, Self::Error> {\n        let state = match read_attribution_token(&mut stream).await? {\n            Some(token) => self.state.for_execution_token(&token).ok_or_else(|| {\n                io::Error::new(\n                    io::ErrorKind::PermissionDenied,\n                    \"unknown network proxy attribution token\",\n                )\n            })?,\n            None => self.state.as_ref().clone(),\n        };\n        if let Some(expected_environment_id) = self.environment_id.as_deref()\n            && state\n                .environment_id()\n                .is_some_and(|actual| actual != expected_environment_id)\n        {\n            return Err(io::Error::new(\n                io::ErrorKind::PermissionDenied,\n                \"network proxy attribution environment mismatch\",\n            )\n            .into());\n        }\n        stream.extensions_mut().insert(Arc::new(state));\n        self.inner.serve(stream).await.map_err(Into::into)\n    }\n}\n\nasync fn read_attribution_token(stream: &mut TcpStream) -> Result<Option<String>, BoxError> {\n    let mut marker = [0_u8; 1];\n    let read = stream.stream.peek(&mut marker).await?;\n    if read == 0 {\n        return Err(io::Error::new(io::ErrorKind::UnexpectedEof, \"empty proxy connection\").into());\n    }\n    if marker[0] != ATTRIBUTION_FRAME_MAGIC[0] {\n        return Ok(None);","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-rs/network-proxy/src/attribution.rs#L45-L81","documentation":"The attribution token resolved to a registered execution, but the ingress was constructed with an expected environment id and the token's state carries a different one, so the connection is rejected with PermissionDenied. This is a deliberate isolation check in BindConnectionAttribution::serve: traffic attributed to an execution from another environment must not be routed through this ingress. It only fires when the bind supplied an environment_id AND the resolved state's environment_id is set and differs.","triggerScenarios":"The ingress is built via BindConnectionAttribution::new(..., Some(expected_env)) while the client presents a token minted inside a different environment id -- e.g. a token exported in devcontainer A used against an ingress bound to devcontainer B.","commonSituations":"Copying CODEX_NETWORK_PROXY_ATTRIBUTION between shells or containers of different environments; environment/workspace renamed so bind config and token registry disagree; multi-environment test setups mixing tokens.","solutions":["Use a token minted in the same environment the ingress was bound to -- re-export CODEX_NETWORK_PROXY_ATTRIBUTION inside that environment and reconnect.","Verify the environment id passed at bind time matches the environment recorded for the token's execution in the registry.","Restart bridge and proxy within one environment/session so both sides derive from the same configuration."],"exampleFix":"// before: token exported in environment A, ingress bound to environment B\n// $ export CODEX_NETWORK_PROXY_ATTRIBUTION=<token-from-env-A>\n// after: obtain the token inside the environment the ingress serves\nlet token = env::var(\"CODEX_NETWORK_PROXY_ATTRIBUTION\")?; // read within env B's execution","handlingStrategy":"validation","validationCode":"// If both ids are visible to the client, compare before dialing\nif let (Some(expected), Some(actual)) = (bind_environment_id, token_environment_id) {\n    if expected != actual {\n        return Err(io::Error::new(\n            io::ErrorKind::PermissionDenied,\n            \"attribution token belongs to a different environment\",\n        ));\n    }\n}","typeGuard":null,"tryCatchPattern":"Match io::ErrorKind::PermissionDenied whose message contains 'environment mismatch': surface it as a configuration error (wrong environment for this token); do not retry -- retrying cannot change the environment.","preventionTips":["Source the token and the proxy bind config from the same environment descriptor.","Do not copy CODEX_NETWORK_PROXY_ATTRIBUTION between containers/workspaces.","Log both environment ids when the check fails so the mismatch is obvious."],"tags":["rust","codex","network-proxy","attribution","environment","permission-denied"],"backgroundTag":"auth-environment-mismatch","analyzedSha":"339751715c64496cb86246bfb3935f40e309dd3d","analyzedAt":"2026-08-25T05:35:09.876Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}