{"record":{"id":"2025160879bd9420","repo":"zeroclaw-labs/zeroclaw","slug":"empty-session-token","errorCode":null,"errorMessage":"empty session token","messagePattern":"empty session token","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-runtime/src/security/nevis.rs","lineNumber":250,"sourceCode":"\n    #[allow(clippy::unused_async)] // Will use async when JWKS validation is implemented\n    async fn validate_token_local(&self, token: &str) -> Result<NevisIdentity> {\n        // JWT structure check: header.payload.signature\n        let parts: Vec<&str> = token.split('.').collect();\n        if parts.len() != 3 {\n            bail!(\"Invalid JWT structure: expected 3 dot-separated parts\");\n        }\n\n        bail!(\n            \"Local JWKS token validation is not yet implemented. \\\n             Set token_validation = \\\"remote\\\" to use the Nevis introspection endpoint.\"\n        );\n    }\n\n    /// Validate a Nevis session token (cookie-based sessions).\n    pub async fn validate_session(&self, session_token: &str) -> Result<NevisIdentity> {\n        if session_token.is_empty() {\n            bail!(\"empty session token\");\n        }\n\n        let session_url = format!(\n            \"{}/auth/realms/{}/protocol/openid-connect/userinfo\",\n            self.instance_url.trim_end_matches('/'),\n            self.realm,\n        );\n\n        let resp = self\n            .http_client\n            .get(&session_url)\n            .bearer_auth(session_token)\n            .send()\n            .await\n            .context(\"Failed to reach Nevis userinfo endpoint\")?;\n\n        if !resp.status().is_success() {\n            bail!(","sourceCodeStart":232,"sourceCodeEnd":268,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/security/nevis.rs#L232-L268","documentation":"validate_session rejects an empty string up front (nevis.rs:249-251) before calling the userinfo endpoint. It is the same fail-fast guard as validate_token, but for cookie-based session tokens.","triggerScenarios":"Calling validate_session with an empty string — usually because the session cookie was absent, named differently than expected, or failed to parse from the Cookie header.","commonSituations":"Cookie flags (Secure/SameSite) prevent the cookie from arriving cross-origin; cookie name mismatch between client and gateway; the request is the anonymous first hit before any session exists.","solutions":["Treat an absent or empty session cookie as 'not authenticated' (401 or redirect to login) before calling validate_session","Check the exact cookie name your Nevis instance sets against your extraction logic","Confirm Secure/SameSite settings allow the cookie on your deployment origin"],"exampleFix":"// before\nlet session = cookies.get(\"session\").map(|c| c.value().to_string()).unwrap_or_default();\nlet id = provider.validate_session(&session).await?;\n\n// after\nlet Some(session) = cookies.get(\"session\").map(|c| c.value()).filter(|s| !s.is_empty()) else {\n    return unauthorized_redirect_to_login();\n};\nlet id = provider.validate_session(session).await?;","handlingStrategy":"validation","validationCode":"let session = cookies\n    .get(SESSION_COOKIE)\n    .map(|c| c.value())\n    .filter(|s| !s.trim().is_empty());\nlet Some(session) = session else {\n    return redirect_to_login();\n};","typeGuard":null,"tryCatchPattern":"Match err.to_string().contains(\"empty session token\") and treat the caller as anonymous: 401 or a login redirect, never a 500.","preventionTips":["Return 401 or redirect at the cookie-extraction layer when the cookie is missing","Log the cookie name looked up, never its value","Test the anonymous first-request path explicitly"],"tags":["auth","nevis","session","cookies","input-validation","rust"],"backgroundTag":"empty-auth-token","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}