auth: no audit logging of authentication failures #9

Closed
opened 2026-05-14 08:44:17 +00:00 by mathias · 1 comment
Owner

Failed auth attempts are silently dropped — no structured log entry, no forensic trail. Impossible to distinguish a misconfigured client from a brute-force attempt after the fact.

File: internal/auth/bearer.go:22-42

Suggestion: On every 401, emit a structured log line with:

  • Failure reason (jwt_invalid, jwt_dex_unavailable, static_token_mismatch, no_token)
  • Remote IP
  • Token type attempted (JWT vs static)
  • First 8 chars of the presented token (enough to correlate client, not enough to reconstruct)
  • Timestamp
Failed auth attempts are silently dropped — no structured log entry, no forensic trail. Impossible to distinguish a misconfigured client from a brute-force attempt after the fact. **File:** `internal/auth/bearer.go:22-42` **Suggestion:** On every 401, emit a structured log line with: - Failure reason (`jwt_invalid`, `jwt_dex_unavailable`, `static_token_mismatch`, `no_token`) - Remote IP - Token type attempted (JWT vs static) - First 8 chars of the presented token (enough to correlate client, not enough to reconstruct) - Timestamp
Author
Owner

Done — the bearer auth now lives in the shared mcp-chassis (v0.3.0), adopted by gitea-mcp v0.5.0.

BearerMiddleware emits a structured slog line on every rejection:

  • reason — one of no_token / static_token_mismatch / jwt_invalid / jwt_dex_unavailable
  • remote — client IP (X-Forwarded-For aware, falls back to transport peer)
  • token_typejwt (three dotted segments) vs opaque vs none
  • token_fp — a truncated SHA-256 fingerprint of the token
  • status, path; slog adds the timestamp

Security deviation from the ticket (flagging it): the issue suggested logging the first 8 chars of the raw token. I logged a hashed fingerprint instead — gitea-mcp's own secret-handling rule is that logs flow to stdout→k8s→brain ingestion, so a raw prefix of a static bearer would leak secret entropy into searchable storage. The hash still correlates repeated attempts from one client ("enough to correlate, not to reconstruct") without that risk. If you'd rather have the literal prefix, say so and I'll switch it.

Wired via slog.SetDefault in gitea-mcp so the lines use its JSON handler. Tests: TestBearerMiddleware_AuditLogsRejection asserts the fields present and the raw token absent. Implemented in mcp-chassis (feat commit + v0.3.0 tag).

Done — the bearer auth now lives in the shared `mcp-chassis` (v0.3.0), adopted by gitea-mcp v0.5.0. `BearerMiddleware` emits a structured `slog` line on **every** rejection: - `reason` — one of `no_token` / `static_token_mismatch` / `jwt_invalid` / `jwt_dex_unavailable` - `remote` — client IP (X-Forwarded-For aware, falls back to transport peer) - `token_type` — `jwt` (three dotted segments) vs `opaque` vs `none` - `token_fp` — a truncated SHA-256 fingerprint of the token - `status`, `path`; slog adds the timestamp **Security deviation from the ticket (flagging it):** the issue suggested logging the *first 8 chars of the raw token*. I logged a **hashed fingerprint** instead — gitea-mcp's own secret-handling rule is that logs flow to stdout→k8s→brain ingestion, so a raw prefix of a **static** bearer would leak secret entropy into searchable storage. The hash still correlates repeated attempts from one client ("enough to correlate, not to reconstruct") without that risk. If you'd rather have the literal prefix, say so and I'll switch it. Wired via `slog.SetDefault` in gitea-mcp so the lines use its JSON handler. Tests: `TestBearerMiddleware_AuditLogsRejection` asserts the fields present and the raw token absent. Implemented in mcp-chassis (feat commit + v0.3.0 tag).
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mathias/gitea-mcp#9