{"record":{"id":"6ef66a1c0e31913d","repo":"zeroclaw-labs/zeroclaw","slug":"userinfo-response-has-missing-or-empty-sub-claim","errorCode":null,"errorMessage":"Userinfo response has missing or empty `sub` claim","messagePattern":"Userinfo response has missing or empty `sub` claim","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-runtime/src/security/nevis.rs","lineNumber":280,"sourceCode":"            .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!(\n                \"Nevis session validation returned HTTP {}\",\n                resp.status().as_u16()\n            );\n        }\n\n        let body: UserInfoResponse = resp\n            .json()\n            .await\n            .context(\"Failed to parse Nevis userinfo response\")?;\n\n        if body.sub.trim().is_empty() {\n            bail!(\"Userinfo response has missing or empty `sub` claim\");\n        }\n\n        let now = std::time::SystemTime::now()\n            .duration_since(std::time::UNIX_EPOCH)\n            .unwrap_or_default()\n            .as_secs();\n\n        let mut roles = body.realm_access.map(|ra| ra.roles).unwrap_or_default();\n        roles.sort();\n        roles.dedup();\n\n        let identity = NevisIdentity {\n            user_id: body.sub,\n            roles,\n            scopes: body\n                .scope\n                .unwrap_or_default()\n                .split_whitespace()","sourceCodeStart":262,"sourceCodeEnd":298,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/security/nevis.rs#L262-L298","documentation":"The userinfo endpoint answered 2xx but the sub field is empty or whitespace (nevis.rs:279-281). ZeroClaw requires sub as the user identifier and fails closed. This indicates an IdP-side claim problem, not a bad token.","triggerScenarios":"validate_session against an IdP deployment whose userinfo does not map the subject claim; a service or anonymized token with no subject; a custom claim mapper that nulls sub.","commonSituations":"Nevis realm userinfo mapper misconfiguration; token issued for a service account without sub; a federation bridge that drops the sub claim.","solutions":["Capture a raw userinfo response for a real user token and confirm sub is populated","Fix the IdP client/protocol mappers so sub is emitted on userinfo","If tokens without sub are legitimate in your setup, filter them before they reach validate_session"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"Match err.to_string().contains(\"`sub` claim\") and treat it as an IdP misconfiguration: return 503, page an admin, and do not log the affected user out of other flows.","preventionTips":["Add an IdP contract test asserting userinfo contains a non-empty sub for a real user","Monitor this error separately from 401s — it signals IdP config drift","Review realm template changes before applying them to production"],"tags":["auth","nevis","oidc","claims","rust"],"backgroundTag":"oidc-missing-sub-claim","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}