Skip to content

v9.0.0

Latest

Choose a tag to compare

@github-actions github-actions released this 05 Oct 12:15
· 10 commits to main since this release
8f6abca

9.0.0 (2026-10-05)

  • Fix OIDF FAPI2 conformance coverage (#1079) (446e875)
  • fix(authorization)!: bound refresh tokens, enforce S256 for interactive authorization, honor built-in token settings (#1135) (42cfbdb), closes #1104
  • fix(authorization)!: verify DPoP proofs, require PKCE for every authorization code, bind codes to their grant and verify interactive authorization (#1143) (500f50d)
  • fix(issuance)!: call external and upstream authorization servers under the outbound URL policy (#1157) (def2051)
  • fix(platform)!: fail closed on broken TLS configuration and clean up repository tooling (#1140) (b98cc00)
  • fix(registrar)!: match registration certificate claims by path and add SKIP_* flags (#1097) (474e249)
  • fix(security)!: protect key material and KMS secrets, enforce key attestation requirements and federation trust (#1146) (d5dca08)
  • fix(session)!: enforce session expiry at request time and count sessions from the database (#1127) (03b2591), closes #1120 #1123 #1120 #1123
  • fix(session)!: scope session access by role and fix the session event stream (#1145) (21c667e), closes #1142 #820 #820 #1141
  • fix(status-list)!: reject values that do not fit the list, make revocation final and restrict status changes to issuance roles (#1142) (a83e1b5)
  • fix(storage)!: replace MinIO with RustFS across deployments (#1075) (57d70a6)
  • fix(trust)!: derive DCQL aki values from listed issuers and renew managed trust lists (#1134) (ed0bbe9)
  • fix(trust)!: fetch trust lists, status lists, federation entities and CRLs under the outbound URL policy (#1155) (af23b1d)
  • fix(verifier)!: enforce DCQL claim values in OpenID4VP presentations (#1148) (4b1d728)
  • fix(verifier)!: report failed presentations to webhooks, classify mDOC failures and finish SSE streams (#1133) (f50c3a2)

Bug Fixes

  • verifier: accept wallet error responses with HTTP 200 and record them on the session (#1112) (950ae62)
  • storage: allow S3 credentials to be omitted for IRSA/instance-profile auth (#1073) (89088d2)
  • database: bootstrap fresh schemas through migrations (#1072) (74d83f3)
  • trust: bound the status list caches (#1159) (2f454a0), closes #1155
  • trust: clear the federation trust cache from the cache endpoints (#1105) (5af1761)
  • security: enforce outbound URL policy on schema metadata fetches and remove ReDoS-prone regex (#1109) (7a6b13b)
  • issuance: enforce PKCE with S256 on the chained authorization servers (#1104) (2bf496f)
  • config-import: follow symlinked tenant folders during discovery (#1107) (5cca3ed)
  • improve protocol debugging logs (#1077) (b992728)
  • issuance: keep mDOC signed date within the signing certificate validity (#1113) (5c8a904)
  • move to the EUDIPLO organization and LF Decentralized Trust (#1161) (d3706be)
  • issuance: open OID4VP wallet request via tap instead of bare redirect (#1098) (672625e)
  • deployment: pin the bundled PostgreSQL to 16 in the Compose files (#1139) (3c4fbcd), closes #1129
  • client: preserve array child wildcard paths during credential config nesting (#1071) (66cc9c9)
  • verifier: require non-empty vp_token entries and enforce DCQL multiple (#1106) (4e47fd9)
  • client: require status management for Single Active Credential and default built-in token lifetime to 300 s (#1144) (fb37ece)
  • issuance: resolve notification sessions like the credential endpoint and validate activeCredentials in the API (#1138) (545119e)
  • webhook: return mandatory mDL claims from the example claims webhook (#1111) (6eb6376)
  • issuance: send presented credentials to the attribute provider (#1108) (0c0759d)
  • database: store the session OAuth expiry columns as timestamp on PostgreSQL (#1151) (728e41e)
  • issuance: treat children of array claim fields as item properties (#1099) (53974dc), closes #1080
  • sdk,cli: use the /api prefix where the backend serves it and await the stored request object (#1141) (05183ed)
  • client: use the logged-in instance URL after an SSO login (#1118) (e8b8571), closes #1076
  • security: verify CRLs against the issuing CA before trusting them (#1156) (6401287)
  • client: warn when client and backend come from different builds (#1115) (b6dd020)

Documentation

  • upgrade: cover role-scoped sessions, the event stream header and the DB_SYNCHRONIZE default in the 9.0 guide (#1152) (4ab71f0), closes #1145

Features

  • security: allow restricting CORS origins for the management API via CORS_ORIGINS (#1114) (bc53368), closes #1088
  • cli: expand doctor with production readiness checks (#1078) (f137547)
  • session: filter and search the session list and correlate logs with sessions (#1147) (7e6a44b), closes #1121
  • issuance: validate resolved claims against the credential configuration schema (#1080) (4b7fd4c)

BREAKING CHANGES

  • external authorization servers and upstream providers
    of chained authorization servers on HTTP or on private addresses, and
    their JWKS and introspection endpoints, are rejected unless
    OUTBOUND_URL_ALLOW_HTTP or OUTBOUND_URL_ALLOW_PRIVATE_NETWORK is set. The
    upstream token request no longer follows redirects.

Signed-off-by: Mirko Mollik mirko.mollik@eudi.sprind.org
Co-authored-by: Claude Opus 5.5 noreply@anthropic.com

  • trust lists, status lists and federation entities on
    HTTP or on private addresses, and CRLs on private addresses, are rejected
    unless OUTBOUND_URL_ALLOW_HTTP or OUTBOUND_URL_ALLOW_PRIVATE_NETWORK is
    set. URLs on EUDIPLO's own PUBLIC_URL or INTERNAL_URL are not affected.

Signed-off-by: Mirko Mollik mirko.mollik@eudi.sprind.org
Co-authored-by: Claude Opus 5.5 noreply@anthropic.com

  • upgrade: changes, and it never mentioned that #1072 changed the
    DB_SYNCHRONIZE default.

Upgrade guide:

  • New "Clients see only the sessions of their side": session list, read,
    logs and delete only cover the client's side; what the other side gets
    (empty list, 404, 204 without delete) and the fix (a role of each side).
  • "The session event stream needs the Authorization header and ends": the
    header-only token, the scope, ?token= rejected with 401, the SDK 9.0
    fetch client, reading the stream from the backend.
  • New "Schema changes only through migrations": DB_SYNCHRONIZE defaults to
    false (8.x: true); keep it out of production env files.
  • Intro, audience table, "After the upgrade" checklist and the summary on
    the upgrade overview name the access-control and default changes; the
    overview no longer says "configuration format v2" (PresentationConfig is
    v3).

Env examples:

  • Compose minimal/standard/full: commented OUTBOUND_URL_* block for
    services reached over HTTP inside the Compose network; CORS_ORIGINS in
    standard and full.
  • Root .env.example: SESSION_TTL, STATUS_BITS (suspension needs 2 bits) and
    CORS_ORIGINS.
  • KMS links in the Compose and Kubernetes examples point to /operate/kms.

Also: the Compose page pairs S3_ACCESS_KEY_ID with RUSTFS_ACCESS_KEY, and
the release checklist describes the /upgrade URLs the 9.0 CLI prints.

Signed-off-by: Mirko Mollik mirko.mollik@eudi.sprind.org
Co-authored-by: Claude Opus 5.5 noreply@anthropic.com

  • OpenID4VP presentations whose disclosed claim is not one
    of the claim query's values now fail with claim_value_mismatch;
    values must match the claim type ([true], not ["true"]).
    PresentationConfig exports use file format v3.

Signed-off-by: Mirko Mollik mirko.mollik@eudi.sprind.org
Co-authored-by: Claude Opus 5.5 noreply@anthropic.com

  • a client with only presentation:request (and optionally
    presentation:manage) no longer sees issuance sessions; a client with only
    issuance:offer (and optionally issuance:manage) no longer sees
    presentation sessions. Give a client that must read both kinds a role of
    each side.

Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
Signed-off-by: Mirko Mollik mirko.mollik@eudi.sprind.org

  • fix(session)!: authorize the session event stream like reading the session

GET /api/session/{id}/events read the tenant from payload.entity, which
a token from the token endpoint never contains (it carries tenant_id), so

  • GET /api/session/{id}/events requires the access token in
    the Authorization header and issuance:offer or presentation:request,
    scoped like GET /api/session/{id}; the token query parameter is rejected
    with 401. The browser's EventSource cannot send the header: read the
    stream from your backend.

Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
Signed-off-by: Mirko Mollik mirko.mollik@eudi.sprind.org

  • key export and the KMS provider configuration require
    tenant:admin or tenants:manage; KMS credentials are returned redacted;
    configurations with keyAttestationsRequired reject proofs without a
    matching key attestation; federation-only DCQL queries reject issuers
    outside the federation; federation.role other than leaf and
    enforceSigningPolicy: false are rejected.

Signed-off-by: Mirko Mollik mirko.mollik@eudi.sprind.org
Co-authored-by: Claude Opus 5.5 noreply@anthropic.com

  • invalid DPoP proofs are rejected; a DPoP-bound
    authorization or refresh requires a proof with the bound key; codes are
    only accepted for the grant they were issued for.

Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
Signed-off-by: Mirko Mollik mirko.mollik@eudi.sprind.org

  • fix(issuance)!: verify interactive authorization presentations and authenticate web completion
  • The openid4vp_presentation step of interactive authorization only
    JSON-parsed the wallet's openid4vp_response and then issued an
    authorization code, so any JSON completed the step. The response now
    goes through the regular OID4VP verification (decryption, nonce,
    audience, DCQL, trust, single use) for the request created for this
    auth session; only a completed verification finishes the step.
    Verified credentials are forwarded to attribute providers like for the
    OID4VP authorization server, and issuer_state must name a redeemable
    authorization code offer. The verifier session is now created before
    the request so wallets can fetch the request object.
  • complete-web-auth was unauthenticated, so the wallet that knows the
    auth session could complete the redirect_to_web step itself. It now
    requires an issuance:offer token of the same tenant and only completes
    an unexpired auth session whose current step is redirect_to_web.
  • Follow-up requests must match the current step, an auth session yields
    one code, and interactive authorization codes expire after 60 s.
  • interactive authorization requires S256 PKCE and the
    encrypted OpenID4VP authorization response; complete-web-auth requires an
    issuance:offer management token of the tenant; interactive authorization
    codes expire after 60 s.

Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
Signed-off-by: Mirko Mollik mirko.mollik@eudi.sprind.org

  • startup fails when TLS is enabled but the certificate,
    key or CA file cannot be read; TLS_CA_PATH serves the intermediate chain
    and does not enable mTLS.

Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
Signed-off-by: Mirko Mollik mirko.mollik@eudi.sprind.org

  • test(platform): keep certificate validation on in the TLS chain test

The handshake test disabled certificate validation to inspect the served chain (CodeQL js/disabling-certificate-validation). Only the root is trusted, so with validation on the handshake fails unless the server sends the intermediate, which is what the test checks.

Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
Signed-off-by: Mirko Mollik mirko.mollik@eudi.sprind.org

  • suspending (status: 2) a credential on a status list
    with 1 bit per entry is rejected with 400; use lists with 2 or more bits
    (STATUS_BITS, the tenant status list config, or bits when creating a
    list). Lists that already contain such values are no longer published
    until their entries are set to a valid value.

Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
Signed-off-by: Mirko Mollik mirko.mollik@eudi.sprind.org

  • docs: document the status value range check and the remediation for corrupted lists

Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
Signed-off-by: Mirko Mollik mirko.mollik@eudi.sprind.org

  • fix(status-list)!: make revocation final and restrict status changes to issuance roles
  • A revoked credential can no longer be reinstated (1 -> 0) or suspended
    (1 -> 2): the update is rejected with 409 before any entry changes.
    Suspensions can still be lifted or turned into a revocation. The rule is
    checked for every entry up front and again inside the versioned write.
  • POST /api/session/revoke requires issuance:offer or
    issuance:manage; verifier-only clients (presentation:request) can no
    longer change credential status.
  • Describe the endpoint correctly in the OpenAPI document (it changes the
    status of a session's credentials; 400 and 409 responses).
  • Docs: revocation page, revocable credentials cookbook (suspension is
    shown on a new credential) and the 9.0 upgrade guide.
  • reinstating or suspending a revoked credential returns 409;
    POST /api/session/revoke requires an issuance role.

Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
Signed-off-by: Mirko Mollik mirko.mollik@eudi.sprind.org

  • chained/OID4VP servers issue 30-day refresh tokens unless
    disabled, and refresh tokens without a stored expiry expire 30 days after
    session creation; interactive authorization requires S256; the built-in
    server applies its token lifetime, signing key and DPoP settings;
    /issuers/{tenant}/chained-as-vp/* is removed.

Signed-off-by: Mirko Mollik mirko.mollik@eudi.sprind.org
Co-authored-by: Claude Opus 5.5 noreply@anthropic.com

  • provided_attestations is rejected; PresentationConfig
    exports use file format v2; concurrent trust list updates return 409.

Signed-off-by: Mirko Mollik mirko.mollik@eudi.sprind.org
Co-authored-by: Claude Opus 5.5 noreply@anthropic.com

  • presentation webhooks also fire for failed and declined
    presentations; consumers must check the new status field. The SSE stream
    now completes after a terminal status.

Signed-off-by: Mirko Mollik mirko.mollik@eudi.sprind.org
Co-authored-by: Claude Opus 5.5 noreply@anthropic.com

  • expired or finished sessions are rejected at request time
    (HTTP 400/404 or OAuth invalid_grant); expiresAt is a full timestamp;
    presentations emit a fetched status; the sessions metric is a gauge that
    every replica reports, so aggregate it with max; IssuanceConfig exports use
    file format v2.
  • OUTBOUND_URL_ALLOW_HTTP and
    OUTBOUND_URL_ALLOW_PRIVATE_NETWORK now default to false in every
    environment instead of true outside NODE_ENV=production. Set them
    explicitly for local development against HTTP/localhost services.

Signed-off-by: Mirko Mollik mirko.mollik@eudi.sprind.org
Co-authored-by: Claude Opus 5.5 noreply@anthropic.com

  • PAR now requires a redirect_uri and S256 PKCE, and request_uri and authorization-code lifetimes are reduced to 60 seconds.

Signed-off-by: Mirko Mollik mirko.mollik@eudi.sprind.org

  • chore: remove file

Signed-off-by: Mirko Mollik mirko.mollik@eudi.sprind.org

  • Bundled deployments now use RustFS service names, credentials, and fresh storage volumes. Existing MinIO objects require an S3-based migration before switching endpoints; see the migration guide.

Signed-off-by: Mirko Mollik mirko.mollik@eudi.sprind.org


This release is also available on: