{"record":{"id":"732b57cd550c7ea3","repo":"xai-org/grok-build","slug":"oidcerror-forceloginnoprincipalsallowed","errorCode":null,"errorMessage":"OidcError::ForceLoginNoPrincipalsAllowed","messagePattern":"OidcError::ForceLoginNoPrincipalsAllowed","errorType":"exception","errorClass":"OidcError","httpStatus":null,"severity":"error","filePath":"crates/codegen/xai-grok-shell/src/auth/oidc/protocol.rs","lineNumber":182,"sourceCode":"/// Reject a token whose principal isn't allowed, BEFORE persisting (no partial\n/// state). A restriction also rejects a token with no principal (else picking\n/// \"personal\" on the consent page defeats it); an empty `AnyOf` fails closed.\n///\n/// The `actual` principal comes from the access-token claim\n/// (`peek_access_token_principal`, an unverified `insecure_decode`). This\n/// client-side check is fail-fast UX / defense-in-depth — NOT the security\n/// boundary: the server re-validates the signed token on every API call and\n/// is authoritative, so a locally tampered token still cannot reach the API.\npub(crate) fn enforce_login_principal(\n    policy: Option<&ForceLoginTeam>,\n    actual: Option<&str>,\n) -> anyhow::Result<()> {\n    let allowed: &[String] = match policy {\n        None => return Ok(()),\n        Some(ForceLoginTeam::Single(id)) => std::slice::from_ref(id),\n        Some(ForceLoginTeam::AnyOf(ids)) if ids.is_empty() => {\n            tracing::warn!(\"OIDC: force_login_team_uuid is an empty list; failing closed\");\n            return Err(anyhow::Error::new(OidcError::ForceLoginNoPrincipalsAllowed));\n        }\n        Some(ForceLoginTeam::AnyOf(ids)) => ids,\n    };\n    if let Some(actual) = actual\n        && allowed.iter().any(|a| a == actual)\n    {\n        return Ok(());\n    }\n    let expected = if allowed.len() == 1 {\n        format!(\"team {}\", allowed[0])\n    } else {\n        format!(\"one of teams: {}\", allowed.join(\", \"))\n    };\n    tracing::warn!(\n        expected = %expected,\n        actual = ?actual,\n        \"OIDC: login principal does not satisfy required policy; rejecting\"\n    );","sourceCodeStart":164,"sourceCodeEnd":200,"githubUrl":"https://github.com/xai-org/grok-build/blob/bc7f02eddd3d84085849dc19ed216f11c23b0571/crates/codegen/xai-grok-shell/src/auth/oidc/protocol.rs#L164-L200","documentation":"OidcError::ForceLoginNoPrincipalsAllowed is raised by enforce_login_principal when the force_login_team_uuid policy is set to an empty AnyOf list. Since no team principal is permitted, the code fails closed and rejects the login instead of allowing everyone. It indicates a deployment misconfiguration, not a user mistake.","triggerScenarios":"Calling the OIDC login flow (run_login_flow_with_config -> enforce_login_principal) when config.oidc.force_login_team_uuid is ForceLoginTeam::AnyOf with a zero-length ids vector.","commonSituations":"An administrator writes `force_login_team_uuid: []` in the deployment config (or an env/secret expansion yields an empty list) intending to disable pinning, but the empty list means 'no team allowed'.","solutions":["Remove force_login_team_uuid entirely from config to allow any principal","Populate the list with at least one team UUID","If the intent was 'block logins', disable OIDC login instead of using an empty allow-list"],"exampleFix":"// before\nforce_login_team_uuid: []\n// after\nforce_login_team_uuid: [\"<team-uuid>\"]  // or delete the key to unpin","handlingStrategy":"validation","validationCode":"// validate config before starting the login flow\nfn validate_force_login(policy: Option<&ForceLoginTeam>) -> Result<(), String> {\n    match policy {\n        None | Some(ForceLoginTeam::Single(_)) => Ok(()),\n        Some(ForceLoginTeam::AnyOf(ids)) if ids.is_empty() => {\n            Err(\"force_login_team_uuid is empty; remove it or list >= 1 team\".into())\n        }\n        Some(ForceLoginTeam::AnyOf(_)) => Ok(()),\n    }\n}","typeGuard":"fn has_allowed_principals(policy: Option<&ForceLoginTeam>) -> bool {\n    match policy {\n        None => true,\n        Some(ForceLoginTeam::Single(_)) => true,\n        Some(ForceLoginTeam::AnyOf(ids)) => !ids.is_empty(),\n    }\n}","tryCatchPattern":null,"preventionTips":["Never commit force_login_team_uuid as an empty list; treat empty as a config error","Add a startup config check that rejects empty allow-lists before login","Validate env/secret expansions that populate the list are non-empty"],"tags":["oidc","config","fail-closed"],"backgroundTag":"empty-allowlist-config","analyzedSha":"bc7f02eddd3d84085849dc19ed216f11c23b0571","analyzedAt":"2026-08-31T04:59:42.031Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}