Skip to content

Gateway auth: adaptive refresh margin, stale-token fallback, replay-safe retry#145

Merged
danielrmerskine merged 1 commit into
mainfrom
de/gateway-auth-refresh
Jul 23, 2026
Merged

Gateway auth: adaptive refresh margin, stale-token fallback, replay-safe retry#145
danielrmerskine merged 1 commit into
mainfrom
de/gateway-auth-refresh

Conversation

@danielrmerskine

Copy link
Copy Markdown
Collaborator

Summary

Fixes a class of spurious mid-command "Box requires sign-in" failures on gated boxes, found by the box-lifecycle CI's first supervised runs (#144).

The bug: EXPIRY_MARGIN_SECONDS = 60 is compared against the token's remaining life on every request. Against an auth server issuing short-lived access tokens (60 s observed in the field), a freshly issued token is always inside the margin — so every box request triggered a refresh round-trip. Each refresh rotates the server-side refresh-token family, so rapid sequences of requests (e.g. lager nets add-all, a verbose lager update) hammered the rotation machinery, and any hiccup — a timeout, out-of-order rotation, replay detection — killed the session mid-command.

Changes (cli/gateway_auth.py)

  1. Adaptive margin: refresh-ahead margin is now min(60, token_lifetime / 4) (lifetime read from the JWT's iat/exp). A 15-minute token keeps the old 60 s margin; a 60 s token gets a 15 s margin and is actually usable from cache.
  2. Stale-token fallback: if a refresh fails but the stored token has not expired, the token is returned anyway and the gateway judges it. A transient auth-server error no longer fails a command whose credentials are still valid.
  3. Replay-safe retry: a refresh is retried once only on ConnectionError (request never reached the server). Read timeouts are deliberately not retried — the server may have already rotated the refresh token, and replaying can trip its replay detection and burn the whole session.

Tests

  • New test/unit/cli/test_gateway_auth_refresh.py (7 tests) pins all three behaviors plus rotation-cookie persistence. gateway_auth previously had no unit coverage.
  • Full test/unit/cli/ suite green in a clean python:3.11 container.

Checklist

  • Code follows the project's style guidelines
  • Copyright headers are present on new files
  • CHANGELOG.md updated
  • Documentation updated (if applicable) — n/a, behavior fix

The refresh-ahead margin was a fixed 60 seconds. Against an auth server
issuing 60-second access tokens, a freshly issued token always sits
inside the margin, so EVERY box request became a refresh round-trip.
Each refresh rotates the server-side refresh-token family, so rapid
request sequences hammered the rotation machinery and any hiccup (a
timeout, an out-of-order rotation, replay detection) killed the whole
session mid-command with a bare "Box requires sign-in."

Three fixes in cli/gateway_auth.py:

- The margin now scales with the token's issued lifetime,
  min(60, lifetime/4), so short-TTL servers cannot push the CLI into
  refreshing on every request.
- A refresh that fails while the stored token is still valid falls back
  to that token and lets the gateway judge it, instead of hard-failing
  the command.
- A refresh whose connection never reached the server is retried once.
  Ambiguous failures (read timeouts) are deliberately NOT retried:
  the server may have already rotated the refresh token, and replaying
  would trip its replay detection and burn the session.

New unit tests pin all three behaviors (test_gateway_auth_refresh.py);
gateway_auth previously had no unit coverage. Found by the box-lifecycle
CI's first supervised runs, which lost sessions mid-run against a
short-TTL auth server.
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@danielrmerskine
danielrmerskine merged commit 532a35e into main Jul 23, 2026
1 check 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.

1 participant