Skip to content

/config/validate false-negatives 'signature' for a config JWT the /auth runtime accepts (+ minor /llm doc gaps) #13

@rafiki270

Description

@rafiki270

While integrating a new relying party (api.deepsignal.live) by following the /llm guide, the documented pre-flight validator gave a false negative that cost significant debugging time. Filing (at the integrator's request) to improve the validator/docs and save the next integrator the trouble.

1. POST /config/validate fails the signature stage for a config JWT that is cryptographically valid and that the live /auth runtime accepts — HIGH

The /llm doc presents /config/validate as a "production-safe validator that runs the same pipeline as the auth runtime." In practice the signature stage diverged from the runtime.

Validator says invalid:

POST /config/validate  { "config_url": "https://api.deepsignal.live/auth/config" }
→ ok: false
  source/fetch/decode/secret_scan/schema/runtime_policy: passed
  signature: failed — "The configured JWKS did not verify the config JWT signature.
                       Check that the JWT header includes a valid kid, uses RS256,
                       and matches the configured JWKS."

Same signature: failed with the { "config_jwt": "<jwt>" } input form.

But the same config JWT (fetched at the same time as the JWKS at its jwks_url) verifies cleanly under two independent verifiers:

  • Node crypto.createVerify('RSA-SHA256').verify(key, sig)true
  • WebCrypto crypto.subtle.verify({ name: 'RSASSA-PKCS1-v1_5' }, key, sig, data) (the JOSE code path) → true
  • Header kid matches the JWK kid; alg: RS256; modulus n is a correct 2048-bit value (342-char base64url); e: AQAB.

And the real onboarding accepted the identical signature: opening
/auth?config_url=…&redirect_url=…&code_challenge=…&code_challenge_method=S256
returned "Integration pending review", and after approval the branded /auth sign-in renders correctly (theme + redirect + Google/email-password). So the live auth runtime verified the very signature the validator rejected.

Notes that narrow it down:

  • Rotating the kid (new key id, same/served JWKS) and redeploying did not change the validator result — so it isn't a kid mismatch on the RP side.
  • decode passes (validator reads the JWT fine, reports alg: RS256), so the failure is specifically in fetching/matching the JWKS during signature verification.

Suggested fix: make the validator's signature stage use the same JWKS fetch/verify as the runtime; or, if there's a known limitation (e.g. JWKS caching keyed by URL), document that /config/validate may false-negative on signature and that /auth discovery is the source of truth — the current "same pipeline as the auth runtime" wording is misleading.

2. Minor /llm doc gaps found while integrating — LOW

  • Access-token claims table omits name and picture. Google passes both, and the reference integrations read them for the user's display name / avatar. Worth adding to the §4.2 claims table.
  • The config-JWT signing example omits exp and iss. The Phase 2.5 snippet calls setIssuedAt() only (no setExpirationTime() / setIssuer()), but working integrations set exp (~15m) and iss (= API origin). Please clarify whether exp/iss are required/recommended and whether an expired config JWT is rejected — otherwise integrators may ship unbounded config JWTs by following the example.
  • ui_theme is deferred to /api. The /llm page shows only a placeholder for ui_theme; a complete, copy-pasteable example inline would remove a round of trial-and-error (the real shape is colors(9 keys) / radii / density / typography / button / card / logo).

Environment: RP API api.deepsignal.live, console app.deepsignal.live; config JWT RS256 (kid=deepsignal-2026-07); integration approved and live. No secrets are included above (the config JWT + JWKS are public by design).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions