Skip to content

[P2] Concurrent failed-auth attempts lose increments and can avoid IP bans #1597

Description

@nickna

Concurrent failed authentication attempts can overwrite one another in the shared security service, preventing an IP ban even after the configured failure threshold has been exceeded. Both Admin and Gateway inherit this implementation.

Evidence and deterministic reproduction

At source commit ee5ea8a022da6a8c3f4f8b79bc8bf67ba499d6ef, SecurityServiceBase.RecordFailedAuthAsync performs an ordinary cache read, increments FailedAuthData.Attempts locally, and writes the serialized object back. The threshold comparison uses this locally computed value. There is no atomic increment or distributed synchronization.

A .NET 10 probe referenced the actual compiled ConduitLLM.Security assembly, derived a minimal service from SecurityServiceBase, and used two instances with separate memory caches and one instrumented IDistributedCache. Failed-auth protection and distributed tracking were enabled, with MaxAttempts=3.

The shared cache captures the first ten absent reads of failed_login:{ip} before releasing them, producing a valid deterministic cross-instance interleaving. All ten requests then execute the real base-class increment/write path. No Redis-specific behavior is required to establish the cache read/write race; this reproduction did not test Redis itself.

Observed:

Sequential: failedRequests=3, banned=True
Concurrent: failedRequests=10, services=2, threshold=3, storedAttempts=1, banned=False
Shared cache: attemptReads=10, attemptWrites=10, banWrites=0

Thus ten simultaneous failed attempts are recorded as one, and the configured threshold does not ban the IP. The sequential control confirms ordinary threshold enforcement still works.

Expected change

Use an atomic shared failed-auth counter and preserve the intended failure-window/ban semantics, including metadata and successful-auth clearing. Keep a synchronized memory implementation for deployments without distributed tracking. Add two-instance concurrency coverage that confirms crossing the threshold triggers a ban and does not lose increments.

Found while fixing #1591. Closed #1211 addressed atomic IP/discovery/model-capability rate counters, but did not cover failed-auth tracking. Searches across open and closed issues for failed-auth races/concurrency found no matching defect. The shared memory path also uses read/increment/write, but was not separately exercised by this reproduction.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions