{"record":{"id":"e92cee892e94005e","repo":"PrefectHQ/fastmcp","slug":"identity-assertion-trusted-issuers-must-not-be-emp","errorCode":null,"errorMessage":"identity_assertion.trusted_issuers must not be empty","messagePattern":"identity_assertion\\.trusted_issuers must not be empty","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/auth/identity_assertion.py","lineNumber":146,"sourceCode":"            \"issuer string (mirroring `jwks_uris`). Issuers absent here fall \"\n            \"back to `algorithm`.\"\n        ),\n    )\n    access_token_expiry_seconds: int = Field(\n        default=300,\n        gt=0,\n        description=(\n            \"Lifetime, in seconds, of the short-lived access token minted from an \"\n            \"ID-JAG. SEP-990 relies on the client re-exchanging a fresh assertion, so \"\n            \"this is intentionally short and no refresh token is issued.\"\n        ),\n    )\n\n    @field_validator(\"trusted_issuers\")\n    @classmethod\n    def _validate_trusted_issuers(cls, v: list[str]) -> list[str]:\n        if not v:\n            raise ValueError(\"identity_assertion.trusted_issuers must not be empty\")\n        for issuer in v:\n            if not issuer or not issuer.strip():\n                raise ValueError(\"trusted_issuers entries must be non-empty strings\")\n        return v\n\n    @field_validator(\"algorithm\")\n    @classmethod\n    def _validate_algorithm(cls, v: str | None) -> str | None:\n        # Trusted issuers are verified via JWKS (public keys only), so the\n        # algorithm must be one of the asymmetric JWS algorithms JWTVerifier\n        # actually supports — HS* (shared-secret) has no JWKS equivalent, and\n        # anything else (EdDSA, or a typo like RS999) would otherwise surface\n        # as a 500 on the first exchange instead of a clean config error now.\n        if v is not None and v not in SUPPORTED_ASSERTION_ALGORITHMS:\n            supported = \", \".join(sorted(SUPPORTED_ASSERTION_ALGORITHMS))\n            raise ValueError(\n                f\"Unsupported algorithm {v!r} for identity assertion: trusted \"\n                f\"issuers are verified via JWKS, so algorithm must be one of \"","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/auth/identity_assertion.py#L128-L164","documentation":"The `trusted_issuers` field on the identity_assertion settings must be a non-empty list; a pydantic `field_validator` raises this ValueError at configuration load time when the list is empty. Trusted issuers are mandatory because assertion verification is entirely driven by the issuer/JWKS list.","triggerScenarios":"Configuring identity assertion settings with `trusted_issuers=[]` (or constructing the settings model with an empty list) — validation fails during model instantiation, not at request time.","commonSituations":"Environment/config file that yields an empty list (e.g. an empty `TRUSTED_ISSUERS` env var split into `[]`); YAML/JSON config with `trusted_issuers: []`; code that builds the settings conditionally and never populates the list.","solutions":["Add at least one trusted issuer URL to the `trusted_issuers` list in your identity assertion settings","Fix the env var / config parsing so the list is populated (check separators, quoting)","If no issuers should be trusted, disable the identity assertion feature entirely rather than passing an empty list"],"exampleFix":"# before\nIdentityAssertionSettings(trusted_issuers=[])\n# after\nIdentityAssertionSettings(trusted_issuers=[\"https://accounts.google.com\"])","handlingStrategy":"validation","validationCode":"issuers = [u.strip() for u in os.environ.get(\"TRUSTED_ISSUERS\", \"\").split(\",\") if u.strip()]\nif not issuers:\n    raise ValueError(\"trusted_issuers must contain at least one issuer URL\")","typeGuard":"def has_trusted_issuers(cfg: dict) -> bool:\n    v = cfg.get(\"trusted_issuers\")\n    return isinstance(v, list) and len(v) > 0","tryCatchPattern":"try:\n    settings = IdentityAssertionSettings(**cfg)\nexcept ValidationError as e:\n    logger.error(\"identity_assertion config invalid: %s\", e)\n    raise SystemExit(1)","preventionTips":["Validate identity assertion config at startup with a fast-fail check","Never build settings with a conditionally-empty trusted_issuers list","Filter empty strings when parsing issuer lists from env vars","Disable the feature explicitly instead of configuring an empty issuer list"],"tags":["config","pydantic","validation","identity-assertion"],"backgroundTag":"empty-required-config-list","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}