Skip to content

fix: a client without a secret cannot authenticate to token introspection (0.5.0-rc71) - #322

Merged
ciresnave-bot merged 1 commit into
release/0.5.xfrom
fix/introspection-secretless-client-0.5.x
Oct 27, 2026
Merged

ciresnave-bot merged 1 commit into
release/0.5.xfrom
fix/introspection-secretless-client-0.5.x

Conversation

@ciresnave-bot

@ciresnave-bot ciresnave-bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

Backport of #321 to release/0.5.x; stacked on #320 (and so on #319): merge those first. /oauth/introspect let a client record with no, an empty or a non-string client_secret authenticate with an empty presented secret; such a client now gets invalid_client (401). Clients with a secret, and clients from POST /oauth/register, are unaffected. Only secret-less (public) clients an operator wrote into storage were affected, and the caller still needs the token it asks about.

Red first, in Docker (rust:latest, --features api-server,axum-integration, jobs=2): with only the new test file, a_client_without_a_secret_cannot_introspect fails on this line's code and a_client_with_a_secret_still_introspects passes; with the fix both pass, plus auth_flow_tests 12, oauth2_client_secret_hash_tests 3, oauth2_code_grant_client_auth_tests 7, oauth_advanced_tests 19; cargo fmt --all -- --check clean. Not run: the rest of the suite. This line has no introspection_status_tests.

Version 0.5.0-rc71 (PM-allocated). GHSA (Low) and RustSec after the fixed versions are live.

🤖 Generated with Claude Code

Summary by Sourcery

Require a valid client secret for OAuth token introspection while preserving access for properly credentialed clients.

Bug Fixes:

  • Prevent clients without a configured secret from authenticating to the OAuth token introspection endpoint with an empty credential.

Tests:

  • Add coverage for secretless clients being rejected through Basic authentication and request bodies, while preserving authentication for clients with valid secrets.

Chores:

  • Bump the release version to 0.5.0-rc71 and record the fix in the changelog.

@sourcery-ai

sourcery-ai Bot commented Oct 26, 2026

Copy link
Copy Markdown

Sorry @ciresnave-bot, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 2 days and 16 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@sourcery-ai

sourcery-ai Bot commented Oct 26, 2026

Copy link
Copy Markdown

Reviewer's Guide

This backport centralizes client-secret handling with Argon2id storage and legacy migration, prevents secretless clients from authenticating to introspection, authenticates confidential clients before authorization-code processing while preserving public-client PKCE rules, and adds focused regression tests for the affected OAuth2 flows.

Sequence diagram for introspection client authentication

sequenceDiagram
    participant Caller
    participant Introspection as oauth_advanced
    participant Storage
    participant Secret as client_secret

    Caller->>Introspection: verify_client_credentials
    Introspection->>Storage: get_kv
    Storage-->>Introspection: client record
    Introspection->>Secret: is_confidential
    alt Client has hashed or legacy secret
        Introspection->>Secret: authenticate
        Secret-->>Introspection: true or false
        Introspection-->>Caller: introspection response or invalid_client
    else Client has no secret
        Secret-->>Introspection: false
        Introspection-->>Caller: invalid_client 401
    end
Loading

Sequence diagram for authorization-code client authentication

sequenceDiagram
    participant Client
    participant Token as oauth2
    participant Storage
    participant Secret as client_secret

    Client->>Token: handle_authorization_code_grant
    Token->>Token: authenticate_token_client
    Token->>Storage: load_client_record
    Storage-->>Token: client record
    Token->>Secret: is_confidential
    alt Confidential client
        Token->>Secret: authenticate
        Secret-->>Token: authenticated or rejected
        alt Authentication succeeds
            Token->>Storage: load authorization code
            Token-->>Client: token response
        else Authentication fails
            Token-->>Client: invalid_client
        end
    else Public client
        Token->>Storage: load authorization code
        Token-->>Client: token response or invalid_request without PKCE
    end
Loading

Flow diagram for hashed and legacy client-secret handling

flowchart TD
    Register[POST /oauth/register] --> Hash[hash_secret]
    Hash --> HashedRecord[Store client_secret_hash]
    Record[oauth2_client record] --> Classify[is_confidential]
    Classify -->|hashed secret| Verify[authenticate]
    Classify -->|legacy plain secret| Verify
    Classify -->|no usable secret| Public[Public client]
    Verify -->|success for legacy record| Upgrade[upgrade]
    Upgrade --> HashedRecord
    Verify -->|success| Allow[Allow confidential-client operation]
    Verify -->|failure| Reject[Return invalid_client]
    Public --> Reject
Loading

File-Level Changes

Change Details Files
Centralize OAuth2 client-secret classification, verification, hashing, and legacy-record migration.
  • Distinguish confidential clients by non-empty hashed or legacy secrets, while treating missing, empty, and non-string values as public.
  • Store newly registered secrets as Argon2id hashes and transparently upgrade legacy plaintext records after successful authentication.
  • Use blocking tasks for password hashing and verification without logging credentials or hashes.
src/api/client_secret.rs
src/api/mod.rs
src/api/oauth2.rs
src/api/oauth_advanced.rs
Require authenticated confidential clients for authorization-code exchanges and preserve public-client PKCE behavior.
  • Authenticate the registered client before reading or consuming the authorization code.
  • Reject missing, incorrect, or empty secrets with invalid_client and prevent cross-client code redemption without burning the code.
  • Determine public versus confidential status from registration rather than request contents, retaining mandatory PKCE for public clients.
src/api/oauth2.rs
Add regression coverage for secretless introspection, secret hashing, and authorization-code client authentication.
  • Verify public, empty-secret, and non-secret records cannot introspect with empty credentials while valid confidential clients still can.
  • Verify registration stores only hashes, legacy secrets authenticate and migrate, and hashed clients require valid credentials.
  • Verify code-grant authentication, code preservation on failures, client binding, public-client PKCE, and unregistered-client rejection.
tests/introspection_secretless_client_tests.rs
tests/oauth2_client_secret_hash_tests.rs
tests/oauth2_code_grant_client_auth_tests.rs
Publish the 0.5.0-rc71 release metadata and document the security fixes.
  • Bump the crate version and add changelog entries covering secretless introspection and related backported OAuth2 fixes.
Cargo.toml
Cargo.lock
CHANGELOG.md

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@ciresnave-bot
ciresnave-bot force-pushed the fix/introspection-secretless-client-0.5.x branch 2 times, most recently from 3e70bfe to 3cc50a9 Compare October 27, 2026 01:25
…tion (0.5.0-rc71)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013YEhSGGFctL3d4hGevi7tC
@ciresnave-bot
ciresnave-bot force-pushed the fix/introspection-secretless-client-0.5.x branch from 3cc50a9 to 7b5177b Compare October 27, 2026 04:25
@ciresnave-bot
ciresnave-bot merged commit 4f4e339 into release/0.5.x Oct 27, 2026
21 checks passed
@ciresnave-bot
ciresnave-bot deleted the fix/introspection-secretless-client-0.5.x branch October 27, 2026 05:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants