# SensitiveLogSanitizer Wiring — 2026-05-20

## Overview
`SensitiveLogSanitizer` replaces `HttpLoggingInterceptor.Level.BASIC` in all OkHttp clients.
BASIC level logs full URLs and headers — including debrid tokens, API keys, and unrestricted stream links.

## Per-Service Patterns

### TmdbModule
**Risk:** API key is in `?api_key=` query parameter. BASIC logging prints full URL.
**Fix:** Replace `HttpLoggingInterceptor` with sanitized interceptor.

```kotlin
// BEFORE (leaks API key):
val logging = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BASIC }
return OkHttpClient.Builder()
    .addInterceptor(logging)
    .build()

// AFTER (redacts query params):
return OkHttpClient.Builder()
    .addInterceptor { chain ->
        val safeUrl = SensitiveLogSanitizer.sanitizeUrl(chain.request().url)
        Log.d("TmdbModule", "→ $safeUrl")   // → https://api.themoviedb.org/3/configuration?…
        chain.proceed(chain.request())
    }
    .build()
```

### TraktModule
**Risk:** Bearer token in `Authorization` header. BASIC logging prints headers.
**Fix:** Redact Authorization header value.

```kotlin
// BEFORE:
.addInterceptor(logging)  // Leaks "Authorization: Bearer abc123..."

// AFTER:
.addInterceptor { chain ->
    val req = chain.request()
    val safeUrl = SensitiveLogSanitizer.sanitizeUrl(req.url)
    val safeAuth = SensitiveLogSanitizer.sanitizeHeader("authorization", req.header("Authorization") ?: "none")
    Log.d("TraktModule", "→ $safeUrl  auth=$safeAuth")  // → https://api.trakt.tv/trending  auth=[REDACTED]
    chain.proceed(req)
}
```

### DebridModule
**Risk:** Unrestricted stream URLs contain raw download links in path (e.g., `/d/abc123/MyMovie.mkv`). BASIC logging prints full URL.
**Fix:** `sanitizeUrl()` detects debrid paths and replaces with `[REDACTED stream path]`.

```kotlin
// BEFORE:
val logging = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BASIC }
// Logs: → https://api.real-debrid.com/rest/1.0/unrestrict/link  (acceptable)
// Logs: → https://abcd.download.real-debrid.com/d/TOKEN123/Movie.mp4  (TOKEN LEAK!)

// AFTER:
.addInterceptor { chain ->
    val safeUrl = SensitiveLogSanitizer.sanitizeUrl(chain.request().url)
    Log.d("DebridModule", "→ $safeUrl")  // → https://abcd.download.real-debrid.com[REDACTED stream path]
    chain.proceed(chain.request())
}
```

### StremioAddonClient (AddonHttpClientFactory)
**Risk:** No logging at all — no diagnostics for addon failures.
**Fix:** Add sanitized request logging.

```kotlin
// BEFORE: no logging in AddonHttpClientFactory

// AFTER:
.addInterceptor { chain ->
    val safeUrl = SensitiveLogSanitizer.sanitizeUrl(chain.request().url)
    Log.d("AddonClient", "→ $safeUrl")
    // ... existing headers ...
    chain.proceed(request)
}
```

## Sanitizer API

| Method | Input | Output |
|--------|-------|--------|
| `sanitizeUrl(HttpUrl)` | `https://api.trakt.tv/users/me?limit=50` | `https://api.trakt.tv/users/me?…` |
| `sanitizeUrl(HttpUrl)` (debrid path) | `https://cdn.real-debrid.com/d/abc/file.mkv` | `https://cdn.real-debrid.com[REDACTED stream path]` |
| `sanitizeHeader("Authorization", "Bearer xyz")` | `"Bearer xyz"` | `"[REDACTED]"` |
| `sanitizeHeader("Content-Type", "application/json")` | `"application/json"` | `"application/json"` |
| `sanitizeBody(payload, 200)` | Long JSON string with embedded token | Truncated + token-like strings replaced |

## Services NOT Sanitized (Safe)

| Service | Reason |
|---------|--------|
| JustWatchModule | No auth tokens in requests — public GraphQL API |
| CoilModule (images) | Public image URLs only — no credentials |
| StreamValidator (internal OkHttp) | HEAD requests only — no credentials in URL |
