Security advisory — Malicious litellm versions 1.82.7 and 1.82.8 were removed from PyPI (potential API key exfiltration). Uninstall them, rotate exposed credentials, and upgrade to a safe release (e.g. 1.82.9+). Run pip show litellm to verify. PyPI · README

Enterprise SSO (SAML 2.0) integration and fallback runbook

If SAML has a serious problem (bad assertions, unstable IdP, or a compliance pause), you can fall back quickly to an "OIDC only" state with an environment variable:

Enterprise SSO (SAML 2.0) integration and fallback runbook

Scope: dual-stack SAML SP configuration for enterprise/apps/web-portal and enterprise/apps/admin-console; coexists with the OIDC path.

>

This is the sibling of sso-oidc-setup.md. For OIDC, keep using the original OIDC runbook.

0. One-click fallback (most important)

If SAML has a serious problem (bad assertions, unstable IdP, or a compliance pause), you can fall back quickly to an "OIDC only" state with an environment variable:

bash
1# .env.local or the deployment environment
2SSO_SAML_DISABLED=true

After fallback (restart portal / admin-console), it takes effect immediately:

  • portal /api/auth/sso/saml/start and admin /api/auth/sso/saml/start: redirect straight back to the login page with error code saml.provider_not_configured.
  • portal/admin /api/auth/sso/saml/callback: return HTTP 400, body {"error":"saml.provider_not_configured"}, and write an audit event auth.sso.login_failed (protocol=saml).
  • Admin "Add SSO Provider" rejects SAML protocol selection (creating a SAML provider is forbidden when SSO_SAML_DISABLED=true).
  • The OIDC path, error codes, routes, and audit fields are completely unaffected.

This switch turns off SAML only. Do not introduce a global switch such as SSO_DISABLED or SSO_OIDC_DISABLED — an OIDC outage should not be caused by a SAML failure.

1. Route overview

RoleStart (GET)Callback (POST)
Portal/api/auth/sso/saml/start?provider=<id>&returnTo=<safe>/api/auth/sso/saml/callback
Admin/api/auth/sso/saml/start?provider=<id>/api/auth/sso/saml/callback

Implementation locations:

  • Protocol handling: enterprise/packages/auth/src/services/saml-protocol-handler.ts
  • RelayState: enterprise/packages/auth/src/services/saml-state.ts
  • Attribute mapping: enterprise/packages/auth/src/services/saml-attribute-mapper.ts
  • Portal routes: enterprise/apps/web-portal/src/app/api/auth/sso/saml/{start,callback}/route.ts
  • Admin routes: enterprise/apps/admin-console/src/app/api/auth/sso/saml/{start,callback}/route.ts

Underlying dependency: @node-saml/node-saml v5 (pinned; do not introduce the old passport-saml branch).

2. Required environment variables

VariableDefaultDescription
SSO_STATE_SIGNING_SECRETGenerated by bootstrap.shSymmetric key that encrypts RelayState/cookie; shared with OIDC
SSO_PROVIDER_SECRET_KEYGenerated by bootstrap.shEncryption key for sensitive provider fields such as clientSecret/PEM
SSO_SAML_DISABLEDfalseOne-click fallback switch; see the previous section
NEXT_PUBLIC_SSO_PROVIDERSdefault:Enterprise unified identityLogin-page button list; to add a SAML entry, append <providerId>:<display name>

After adding any SAML-specific environment variable, you must also update:

  • enterprise/.env.local.example
  • enterprise/turbo.json globalEnv
  • Default values in enterprise/scripts/bootstrap.sh
  • This document

3. Configure a SAML provider (admin console)

3.1 IdP preparation

  • On the IdP side (Feishu / Okta / the customer project's IDaaS), create a SAML application:

- Service provider / SP Entity ID: usually https://<your-portal-domain>/ (use the admin-console domain if it is shared with admin-console)

- ACS (Assertion Consumer Service) URL: https://<host>/api/auth/sso/saml/callback

- NameID Format: emailAddress recommended

- Required attributes: email is required; optional displayName / firstName / lastName / dept / roles

3.2 Enter it in admin-console

On /settings/sso → choose protocol SAML 2.0 → fill in:

  • Provider ID: matches the portal login page ?provider=
  • IdP Entity ID / IdP SSO URL / optional SLO URL
  • One or more IdP signing-certificate PEMs (keep the full BEGIN/END CERTIFICATE block for each certificate)
  • SP Entity ID / ACS URL (must match the IdP application configuration)
  • NameID Format / clock skew (default 60 seconds)
  • Whether to require Assertion signing (recommended) / Response signing
  • attribute mapping (email is required)

After save, the admin console writes PG sso_providers.saml_config and an audit event auth.sso.provider.create (including protocol).

3.3 Health check

The admin-console "Health check" button (POST /api/admin/sso/providers/<id>/health) will:

  • Parse each IdP certificate and report validFrom / validTo / expired or not.
  • Send one HTTP HEAD to idpSsoUrl (5s timeout) to confirm reachability; a non-200 is not fatal, only informational.

Health-check requests go through sso-url-guard for SSRF protection and do not send any user information or SAMLRequest to the IdP.

4. End-to-end acceptance (prefer the local mock IdP fixture)

ScenarioEntryExpected result
Valid happy pathportal /api/auth/sso/saml/start?provider=<id>Login succeeds, access/refresh cookies are written, redirect to returnTo
Missing email attributeSame as aboveError code saml.attribute_email_missing, redirect back to the login page
Bad signatureTamper with the SAMLResponse signatureError code saml.invalid_signature
Expired assertionSet NotOnOrAfter to the pastError code saml.expired_assertion
Issuer mismatchChange the IdP entityIDError code saml.invalid_issuer
Audience mismatchChange the SP entityIDError code saml.invalid_audience
RelayState tamperedModify the cookieError code saml.relay_state_invalid
Provider disabledTurn enabled off in the admin consoleError code saml.provider_disabled
Admin missing admin:enterLog in with an unauthorized accountError code admin_scope_missing

Every failure writes an auth.sso.login_failed audit event. Detail fields:

json
1{
2 "protocol": "saml",
3 "reason_code": "saml.invalid_signature",
4 "provider_id": "<id>",
5 "issuer": "<idpEntityId>",
6 "external_subject": "<NameID or null>",
7 "email_hint": "<optional>"
8}

Successful login writes auth.sso.login (portal) or auth.sso.admin_login (admin), also with protocol = "saml".

5. Common IdP configuration entry points (for operations index)

  • Feishu SAML SSO: Feishu Admin → Security → SSO → SAML 2.0 → custom application.
  • Okta SAML: Okta Admin → Applications → Create Custom SAML 2.0 App.
  • The customer project's IDaaS: placeholder; actual integration parameters are provided by the customer (see the "customer-cloud IDaaS intake checklist" in sso-oidc-setup.md).

6. Troubleshooting cheat sheet

SymptomFirst check
Login page returns to an error page immediately after redirectRead the ?sso_error= code in the URL; compare with the "End-to-end acceptance" table in this document
Callback reports saml.relay_state_invalidUsually the cookie was stripped by a proxy, or SameSite does not match the environment; production should use SameSite=None + Secure to support cross-site callbacks; non-production keeps SameSite=Lax to reduce local-debug risk
Callback reports saml.callback_failedCheck backend logs; usually the IdP certificate does not match the PEM, or audience is inconsistent
Audit only has protocol=saml failure eventsThe SP-init flow already reached the callback stage; continue with reason_code
Want to pause SAMLSet SSO_SAML_DISABLED=true and restart portal/admin-console

7. Local mock IdP fixture (local only)

To verify the SAML flow without a real IdP, the repo includes a read-only, local-only mock IdP at

enterprise/scripts/sso/mock-saml-idp/. Steps:

bash
1# 1) Generate a local signing keypair once (never commit)
2pnpm sso:saml-mock-setup
3# 2) Start the mock IdP (default 127.0.0.1:4444)
4pnpm sso:saml-mock

After start you can visit:

  • GET /metadata — IdP metadata XML (includes the X.509 public key)
  • GET /sso?acs=<acsUrl>&audience=<spEntityId>&email=<email>&displayName=<name>&roles=<csv>&RelayState=<state>&inResponseTo=<requestId>

— returns an HTML form that auto-POSTs to the SP acsUrl with SAMLResponse + RelayState

Note: to support the local fixture, admin-console allows idpEntityId/idpSsoUrl to be http://localhost or http://127.0.0.1 only in non-production. Production still requires HTTPS and forbids local addresses.

This keypair is for local integration tests only. Do not deploy it to any external environment. scripts/sso/mock-saml-idp/.fixture/

is in .gitignore, so a normal workflow will not put the private key in git history.

For an example of wiring the mock IdP to an admin-console SAML provider, see

enterprise/scripts/sso/mock-saml-idp/README.md.

8. Relationship to OIDC

  • No OIDC change may bypass via the SAML path, and vice versa.
  • Error-code namespaces: oidc.* and saml.* are maintained separately, centralized in enterprise/packages/auth/src/services/oidc-error-codes.ts.
  • Shared by both paths: SSO_STATE_SIGNING_SECRET, sso-url-guard SSRF protection, and sanitizeSsoAuditDetail audit redaction.
  • Independent between paths: two cookie families agenticx_oidc_state_* / agenticx_saml_state_*; start/callback routes live in separate directories.

Made-with: Damon Li