Skip to content

feat(token-rate-limit): key quotas by authenticated subject - #980

Merged
leseb merged 6 commits into
praxis-proxy:mainfrom
nerdalert:feat/authenticated-subject-token-quota
Sep 16, 2026
Merged

leseb merged 6 commits into
praxis-proxy:mainfrom
nerdalert:feat/authenticated-subject-token-quota

Conversation

@nerdalert

@nerdalert nerdalert commented Sep 7, 2026

Copy link
Copy Markdown
Member

🔴 Dependency and merge readiness

Praxis PR praxis-proxy/praxis#1108 has merged into Praxis main. This AI PR is therefore ready to leave draft and receive review.

It can merge after all of the following are complete:

  1. Praxis publishes a release containing feat(basic-auth): publish authenticated identity praxis#1108.
  2. AI updates its Praxis dependency and lockfile from 0.5.4 to that released version.
  3. A cross-filter integration test proves that Basic Auth establishes AuthenticatedIdentity and key: authenticated_subject consumes it successfully without any local path or [patch.crates-io] override.

Merging the code before that release would not change existing quota behavior because key: global remains the default. However, Basic Auth with key: authenticated_subject would fail closed with HTTP 401 while AI still resolves Praxis 0.5.4. The dependency bump and integration test are therefore required before merge, not merely before publication.

Summary

Adds an opt-in authenticated_subject key to the token-rate-limit filter. When selected, each verified application or user receives an independent quota while gateway replicas share that subject's budget through the existing Valkey backend.

The default remains global, preserving the current behavior and configuration compatibility.

Cross-repository contract

This complements the broader token-rate-limiting work tracked in #121. It implements one secure bucket-key source; it does not close the epic.

Praxis PR praxis-proxy/praxis#1108 is merged. It makes Basic Auth publish its verified username through the private request-local AuthenticatedIdentity extension. This filter consumes that same authentication-neutral type. Policy/JWT authentication already uses the type, allowing the quota key to be reused by JWT/OIDC/OAuth-backed authentication without coupling token quota to Basic Auth.

The important boundary is that AI never derives quota identity from a caller-controlled header.

Behavior

  • Adds key: authenticated_subject at the token-rate-limit filter level.
  • Keeps key: global as the default.
  • Reads only the trusted request-local AuthenticatedIdentity subject.
  • Fails closed with HTTP 401 when authenticated-subject keying is configured but no verified identity exists.
  • Hashes the subject with SHA-256 and URL-safe base64 before using it in memory, Valkey keys, metadata, or metrics.
  • Uses the same resolved opaque key for reservation and settlement.
  • Retains existing rule matching, quota algorithms, reservation bounds, and backend behavior.

Use case

Three applications can authenticate through the same endpoint and share the same quota rule configuration while receiving separate subject-keyed budgets. Requests for one application share quota state across gateway replicas, but cannot consume another application's capacity. Grid provider selection remains independent and occurs only after quota admission.

Tests and validation

Focused coverage includes:

  • backward-compatible global default;
  • parsing and rejection of key-source values;
  • stable, distinct, opaque subject keys;
  • fail-closed behavior without identity;
  • independent subjects under one rule;
  • shared subject state across filter instances;
  • reservation and settlement using the same key.

The complete Grid qualification passed twice, 9/9 scenarios per run, using this code with the merged Praxis identity producer. The committed branch contains no [patch.crates-io], sibling path dependency, generated evidence, or local build compatibility changes.

Related to #121 and praxis-proxy/grid#101.
Depends on praxis-proxy/praxis#1108.

Companion qualification: praxis-proxy/grid#127.
Companion demo: praxis-proxy/demos#20.

@leseb leseb left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need a new praxis core version

@praxis-bot praxis-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praxis-bot review: feat(token-rate-limit): key quotas by authenticated subject

Clean, well-structured change. The security model is sound: fail-closed on missing identity, SHA-256 hashing before storage, trusted extension only (never caller-controlled headers). Two findings, both medium.

# Severity File Finding
1 Medium mod.rs Stale doc comment on AdmittedReservation::key
2 Medium tests.rs Missing positive-path unit test for authenticated-subject keying

1. Stale doc comment (not in diff -- body-only note)

AdmittedReservation::key (mod.rs ~L709) still reads:

The budget key this reservation was admitted under (see FALLBACK_KEY -- always that sentinel in this milestone).

After this PR the key can be a subject-derived hash (subject:v1:<base64>), so "always that sentinel" is no longer accurate. Update to reflect that the key is either FALLBACK_KEY (global mode) or a hashed subject (authenticated-subject mode).

2. Missing positive-path unit test

See inline comment.

Comment thread filters/src/token_rate_limit/tests.rs
@nerdalert
nerdalert force-pushed the feat/authenticated-subject-token-quota branch from 0a93307 to 4faec3b Compare September 10, 2026 18:13
@nerdalert
nerdalert requested a review from a team as a code owner September 10, 2026 18:13
@nerdalert

Copy link
Copy Markdown
Member Author

need a new praxis core version

@leseb Thanks, sorry I should have mentioned it needs praxis-proxy/praxis#1108 That PR has merged, but am blocked until we cut a release containing it before I can update this PR. Ty!

@praxis-bot praxis-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praxis-bot re-review: feat(token-rate-limit): key quotas by authenticated subject

Prior finding #1 (stale AdmittedReservation::key doc comment) is fixed. Finding #2 (missing positive-path test) remains outstanding.

One new finding.

# Severity File Finding
1 Medium mod.rs Missing tracing on the 401 rejection path

Comment thread filters/src/token_rate_limit/mod.rs
@nerdalert
nerdalert force-pushed the feat/authenticated-subject-token-quota branch from 4faec3b to ad4f87d Compare September 14, 2026 15:41
@nerdalert nerdalert added the holding pattern Waiting for further discussion or updates before this can proceed. label Sep 14, 2026
@jordigilh

Copy link
Copy Markdown
Contributor

The existing integration tests cover generic token-rate-limit admission, reconciliation, and shared Valkey behavior, but they do not exercise the new authenticated-subject path.

Once the Praxis dependency is available, could we add a cross-filter integration test covering:

  1. Valid Basic Auth establishes AuthenticatedIdentity, and the quota filter admits the request under that subject.
  2. Missing or invalid credentials fail before provider contact; no anonymous/global fallback is used.
  3. Repeated requests from the same subject consume one shared quota across gateway replicas.
  4. A different authenticated subject receives an independent quota.
  5. Caller-supplied identity headers cannot override the trusted subject.
  6. An exhausted subject is rejected with 429 before routing/provider contact.
  7. Settlement updates the same subject bucket that was reserved during admission.

The key missing evidence is the positive authenticated path and cross-replica subject isolation. Current unit coverage only verifies parsing, hashing, and fail-closed behavior when no identity is present.

@nerdalert
nerdalert force-pushed the feat/authenticated-subject-token-quota branch from ad4f87d to 4b93b78 Compare September 15, 2026 20:00
@nerdalert

Copy link
Copy Markdown
Member Author

The existing integration tests cover generic token-rate-limit admission, reconciliation, and shared Valkey behavior, but they do not exercise the new authenticated-subject path.

Once the Praxis dependency is available, could we add a cross-filter integration test covering:

  1. Valid Basic Auth establishes AuthenticatedIdentity, and the quota filter admits the request under that subject.
  2. Missing or invalid credentials fail before provider contact; no anonymous/global fallback is used.
  3. Repeated requests from the same subject consume one shared quota across gateway replicas.
  4. A different authenticated subject receives an independent quota.
  5. Caller-supplied identity headers cannot override the trusted subject.
  6. An exhausted subject is rejected with 429 before routing/provider contact.
  7. Settlement updates the same subject bucket that was reserved during admission.

The key missing evidence is the positive authenticated path and cross-replica subject isolation. Current unit coverage only verifies parsing, hashing, and fail-closed behavior when no identity is present.

@jordigilh Thanks, addressed!

  • Added a feature-gated cross-filter integration test using two gateway replicas and shared Valkey state.
  • Covered valid/missing/invalid Basic Auth, trusted subject quota isolation, spoofed-header rejection, 429 before provider contact, settlement, and global-key default behavior.

Comment thread tests/integration/tests/suite/examples/token_rate_limit.rs Fixed
Comment thread tests/integration/tests/suite/examples/token_rate_limit.rs Fixed
Comment thread tests/integration/tests/suite/examples/token_rate_limit.rs Fixed
Comment thread tests/integration/tests/suite/examples/token_rate_limit.rs Fixed
Comment thread tests/integration/tests/suite/examples/token_rate_limit.rs Fixed
Comment thread tests/integration/tests/suite/examples/token_rate_limit.rs Fixed
Comment thread tests/integration/tests/suite/examples/token_rate_limit.rs Fixed
@nerdalert

Copy link
Copy Markdown
Member Author

Addressed the GitHub Advanced Security findings by removing embedded Basic Auth password literals from the integration fixture. Credentials are now generated deterministically at runtime; test behavior and diagnostics remain unchanged.

Comment thread tests/integration/tests/suite/examples/token_rate_limit.rs Fixed
Comment thread tests/integration/tests/suite/examples/token_rate_limit.rs Fixed
Comment thread tests/integration/tests/suite/examples/token_rate_limit.rs Fixed
Comment thread tests/integration/tests/suite/examples/token_rate_limit.rs Fixed
@nerdalert

Copy link
Copy Markdown
Member Author

Addressed the additional GHAS findings by deriving the test credentials only from runtime identifiers; no hard-coded password literals remain. The focused integration suite still passes formatting, diff, and compilation validation.

Comment thread tests/integration/Cargo.toml
Comment thread tests/integration/tests/suite/examples/token_rate_limit.rs Outdated
Comment thread tests/integration/tests/suite/examples/token_rate_limit.rs Outdated

@leseb leseb left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#1188 merged so this is ready - just need a rebase and conflict resolution

@leseb
leseb enabled auto-merge September 16, 2026 08:45
@leseb leseb removed the holding pattern Waiting for further discussion or updates before this can proceed. label Sep 16, 2026

@praxis-bot praxis-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review (3/3)

The five new commits add Basic Auth integration tests and runtime-generated credentials, resolving the earlier CodeQL alerts. The authenticated-subject Valkey integration test and the global-key regression test are thorough.

One regression found; the three unresolved threads from the other reviewer are noted but not duplicated here.

Severity Count
Medium 1

Comment thread filters/src/token_rate_limit/mod.rs
Add an opt-in authenticated_subject quota key that consumes Praxis request-local identity, hashes it into an opaque backend key, and fails closed when verified identity is unavailable. Global keying remains the default.

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>
Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>
….5.5

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>
Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>
Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>
@nerdalert
nerdalert force-pushed the feat/authenticated-subject-token-quota branch from ba7b0a5 to 9444bca Compare September 16, 2026 16:20
@praxis-bot-app

Copy link
Copy Markdown

Missing Signed-off-by: 9444bca. All commits require sign-off (via git commit --signoff).

@nerdalert

Copy link
Copy Markdown
Member Author

Rebased onto current main. The branch now resolves Praxis core 0.5.5 (and its compatible Pingora 0.9.0 dependency set), so the released core AuthenticatedIdentity producer required by key: authenticated_subject is present without a local path override or [patch.crates-io] entry.

Signed-off-by: Brent Salisbury <bsalisbu@redhat.com>
@nerdalert
nerdalert force-pushed the feat/authenticated-subject-token-quota branch from 9444bca to d82a6be Compare September 16, 2026 16:22
@leseb
leseb added this pull request to the merge queue Sep 16, 2026
Merged via the queue into praxis-proxy:main with commit 05ed7e9 Sep 16, 2026
35 of 36 checks passed
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.

5 participants