Skip to content

fix(auth): deprecate and retire the ?token= query parameter #468

Description

@EricAndrechek

Area: api / auth · security-hardening — the Go half of #203, which closes it.

#203 moved the TypeScript SDK's streaming transport onto fetch, so it now authenticates with Authorization: Bearer and never puts a credential in the URL. That was the first two of #203's three tasks. This is the third: retiring the ?token= query parameter server-side.

Splitting it out deliberately — #203's PR is SDK-only, and this half touches the auth middleware (security-labelled, heavier review) while blocking nothing.

Why it still matters after #203

The SDK no longer sends ?token=, but the server still accepts it (internal/auth/auth.go:268-282), so nothing has actually stopped: any consumer still using it — a hand-rolled browser EventSource, a shell script, another language's SSE client — keeps putting a JWT in the request URI, where every proxy, CDN, and load balancer in front of WaveHouse can log it. bearerToken strips ?token from r.URL after extraction, which keeps it out of WaveHouse's own logs, but that is the last hop; by then it has already crossed every intermediary in the request line.

The header has always been preferred when both are present, so the deprecation is about removing a fallback, not changing precedence.

Proposed staging

Phase 1 — signal and measure (this issue). Keep accepting it, make its use visible:

  • A counter. wavehouse_auth_query_token_total, following the existing wavehouse_auth_operator_key_failures_total precedent (internal/auth/auth.go:38-40) — an OTel Int64Counter on the wavehouse-auth meter. This is the load-bearing part: an operator cannot safely remove the parameter without knowing whether anything still uses it.
  • Response headers on requests that authenticated via the query parameter: Deprecation: true (RFC 9745) and a Sunset: date (RFC 8594) naming the removal release.
  • Not a per-request log line. /v1/stream is the main consumer of this path and connections are long-lived but numerous; a WARN per request is log spam that operators will filter out, defeating the point. A once-per-process log at startup-ish cadence, or nothing beyond the counter, is better. (Contrast the operator-key WARN, which is justified because a mismatch is a probing signal — ordinary ?token= use is not.)

Phase 2 — opt-in enforcement. An auth.allow_query_token config knob (default true initially) so an operator who has confirmed zero usage can fail closed immediately rather than waiting for a major.

Phase 3 — removal. Flip the default to false, then delete the fallback from bearerToken. Breaking; needs a major and a CHANGELOG entry.

Phases 2 and 3 could be folded or dropped depending on what the counter shows — if usage is zero across deployments, the runway can be short. Worth noting the whole surface is pre-1.0 and the SDK is not yet on npm, so removal will never be cheaper than it is now; the argument for a runway is entirely about non-SDK consumers, whom the counter will identify.

Implementation notes

  • The strip in bearerToken (auth.go:271-276) happens before either return, so a header-authenticated request carrying a stray ?token also strips it. Count only the case where the query token is actually used — i.e. no Authorization: Bearer was present — or the metric will over-report.
  • The operator key is deliberately never accepted via the URL (auth.go:249-250); nothing changes there.
  • Headers must be set before the SSE handler writes its first byte, since /v1/stream flushes headers immediately (internal/api/stream.go).

Docs to update when the behavior changes

  • docs/src/content/docs/api.md:20-28 — the ?token= section and the precedence paragraph
  • docs/src/content/docs/reverse-proxy.mdx:137 — the forwarding checklist
  • docs/src/content/docs/access-control.mdx:43 — the pointer to the query fallback
  • CHANGELOG.md

Acceptance

Related: #203 (SDK half, done), #239 (enforce token expiry on long-lived SSE — also touches this middleware), #228 (auth-hardening epic).

Activity

  1. added
    enhancementNew feature or request
    area/apiHTTP handlers, routing, middleware
    securitySecurity-sensitive issue or fix
    breaking-changeBreaking change to public API, CLI, or config
    area/authAuthentication: tokens, JWT/JWKS, keys, token expiry/revocation
    on Aug 13, 2026
  2. coderabbitai commented on Aug 13, 2026

    @coderabbitai
    🔗 Related PRs

    #123 - fix(api): drop CORS credentials + skip same-origin decoration [merged]
    #174 - refactor(table names): handle unsafe table names [closed]
    #378 - feat(auth): non-JWT operator key for admin + break-glass access [merged]
    #448 - fix(sdk): keep a baseURL path prefix instead of discarding it [merged]


    🧪 Issue enrichment is currently in open beta.

    You can configure auto-planning by selecting labels in the issue_enrichment configuration.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

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

    area/apiHTTP handlers, routing, middlewarearea/authAuthentication: tokens, JWT/JWKS, keys, token expiry/revocationbreaking-changeBreaking change to public API, CLI, or configenhancementNew feature or requestsecuritySecurity-sensitive issue or fix

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions