fix(coderd/x/chatd/chatdebug): allow Anthropic per-modality ratelimit headers (#24592)

Previously, Anthropic's per-modality, Priority Tier, and fast-mode rate-limit headers (`Anthropic-Ratelimit-Input-Tokens-*`, `Anthropic-Ratelimit-Output-Tokens-*`, `Anthropic-Priority-Input-Tokens-*`, `Anthropic-Priority-Output-Tokens-*`, `Anthropic-Fast-Input-Tokens-*`, and `Anthropic-Fast-Output-Tokens-*`) were shown as `[REDACTED]` in the Debug panel because they contain `"token"` in the name and fell through the generic credential filter.

Add them to the allowlist in `coderd/x/chatd/chatdebug/redaction.go` alongside the existing `Anthropic-Ratelimit-Tokens-*` entries so the limits/remaining/reset values surface in the raw response view.
This commit is contained in:
Thomas Kosiewski
2026-04-22 15:14:31 +02:00
committed by GitHub
parent 26b64fa523
commit b7c2c59931
2 changed files with 79 additions and 18 deletions
+37 -12
View File
@@ -162,19 +162,44 @@ func consumeJSONEOF(decoder *json.Decoder) error {
return err
}
// safeRateLimitHeaderNames lists rate-limit headers that contain
// "token" in the name but carry numeric usage counters, not
// credentials. They are checked in isSensitiveName before the
// generic "token" substring match so they pass through unredacted.
// Add new entries here when a provider introduces a rate-limit
// header family containing "token" (e.g. Anthropic's per-modality,
// Priority Tier, or fast-mode headers).
var safeRateLimitHeaderNames = map[string]struct{}{
"anthropic-ratelimit-requests-limit": {},
"anthropic-ratelimit-requests-remaining": {},
"anthropic-ratelimit-requests-reset": {},
"anthropic-ratelimit-tokens-limit": {},
"anthropic-ratelimit-tokens-remaining": {},
"anthropic-ratelimit-tokens-reset": {},
"x-ratelimit-limit-requests": {},
"x-ratelimit-limit-tokens": {},
"x-ratelimit-remaining-requests": {},
"x-ratelimit-remaining-tokens": {},
"x-ratelimit-reset-requests": {},
"x-ratelimit-reset-tokens": {},
"anthropic-ratelimit-requests-limit": {},
"anthropic-ratelimit-requests-remaining": {},
"anthropic-ratelimit-requests-reset": {},
"anthropic-ratelimit-tokens-limit": {},
"anthropic-ratelimit-tokens-remaining": {},
"anthropic-ratelimit-tokens-reset": {},
"anthropic-ratelimit-input-tokens-limit": {},
"anthropic-ratelimit-input-tokens-remaining": {},
"anthropic-ratelimit-input-tokens-reset": {},
"anthropic-ratelimit-output-tokens-limit": {},
"anthropic-ratelimit-output-tokens-remaining": {},
"anthropic-ratelimit-output-tokens-reset": {},
"anthropic-priority-input-tokens-limit": {},
"anthropic-priority-input-tokens-remaining": {},
"anthropic-priority-input-tokens-reset": {},
"anthropic-priority-output-tokens-limit": {},
"anthropic-priority-output-tokens-remaining": {},
"anthropic-priority-output-tokens-reset": {},
"anthropic-fast-input-tokens-limit": {},
"anthropic-fast-input-tokens-remaining": {},
"anthropic-fast-input-tokens-reset": {},
"anthropic-fast-output-tokens-limit": {},
"anthropic-fast-output-tokens-remaining": {},
"anthropic-fast-output-tokens-reset": {},
"x-ratelimit-limit-requests": {},
"x-ratelimit-limit-tokens": {},
"x-ratelimit-remaining-requests": {},
"x-ratelimit-remaining-tokens": {},
"x-ratelimit-reset-requests": {},
"x-ratelimit-reset-tokens": {},
}
// isSensitiveName reports whether a name (header or query parameter)
+42 -6
View File
@@ -88,18 +88,54 @@ func TestRedactHeaders(t *testing.T) {
t.Parallel()
headers := http.Header{
"Anthropic-Ratelimit-Tokens-Limit": {"1000000"},
"Anthropic-Ratelimit-Tokens-Remaining": {"999000"},
"Anthropic-Ratelimit-Tokens-Reset": {"2026-03-31T08:55:26Z"},
"X-RateLimit-Limit-Tokens": {"120000"},
"X-RateLimit-Remaining-Tokens": {"119500"},
"X-RateLimit-Reset-Tokens": {"12ms"},
"Anthropic-Ratelimit-Tokens-Limit": {"1000000"},
"Anthropic-Ratelimit-Tokens-Remaining": {"999000"},
"Anthropic-Ratelimit-Tokens-Reset": {"2026-03-31T08:55:26Z"},
"Anthropic-Ratelimit-Input-Tokens-Limit": {"200000"},
"Anthropic-Ratelimit-Input-Tokens-Remaining": {"199000"},
"Anthropic-Ratelimit-Input-Tokens-Reset": {"2026-03-31T08:55:26Z"},
"Anthropic-Ratelimit-Output-Tokens-Limit": {"80000"},
"Anthropic-Ratelimit-Output-Tokens-Remaining": {"79500"},
"Anthropic-Ratelimit-Output-Tokens-Reset": {"2026-03-31T08:55:26Z"},
"Anthropic-Priority-Input-Tokens-Limit": {"10000"},
"Anthropic-Priority-Input-Tokens-Remaining": {"9618"},
"Anthropic-Priority-Input-Tokens-Reset": {"2026-03-31T08:55:26Z"},
"Anthropic-Priority-Output-Tokens-Limit": {"10000"},
"Anthropic-Priority-Output-Tokens-Remaining": {"6000"},
"Anthropic-Priority-Output-Tokens-Reset": {"2026-03-31T08:55:26Z"},
"Anthropic-Fast-Input-Tokens-Limit": {"50000"},
"Anthropic-Fast-Input-Tokens-Remaining": {"49000"},
"Anthropic-Fast-Input-Tokens-Reset": {"2026-03-31T08:55:26Z"},
"Anthropic-Fast-Output-Tokens-Limit": {"25000"},
"Anthropic-Fast-Output-Tokens-Remaining": {"24000"},
"Anthropic-Fast-Output-Tokens-Reset": {"2026-03-31T08:55:26Z"},
"X-RateLimit-Limit-Tokens": {"120000"},
"X-RateLimit-Remaining-Tokens": {"119500"},
"X-RateLimit-Reset-Tokens": {"12ms"},
}
redacted := chatdebug.RedactHeaders(headers)
require.Equal(t, "1000000", redacted["Anthropic-Ratelimit-Tokens-Limit"])
require.Equal(t, "999000", redacted["Anthropic-Ratelimit-Tokens-Remaining"])
require.Equal(t, "2026-03-31T08:55:26Z", redacted["Anthropic-Ratelimit-Tokens-Reset"])
require.Equal(t, "200000", redacted["Anthropic-Ratelimit-Input-Tokens-Limit"])
require.Equal(t, "199000", redacted["Anthropic-Ratelimit-Input-Tokens-Remaining"])
require.Equal(t, "2026-03-31T08:55:26Z", redacted["Anthropic-Ratelimit-Input-Tokens-Reset"])
require.Equal(t, "80000", redacted["Anthropic-Ratelimit-Output-Tokens-Limit"])
require.Equal(t, "79500", redacted["Anthropic-Ratelimit-Output-Tokens-Remaining"])
require.Equal(t, "2026-03-31T08:55:26Z", redacted["Anthropic-Ratelimit-Output-Tokens-Reset"])
require.Equal(t, "10000", redacted["Anthropic-Priority-Input-Tokens-Limit"])
require.Equal(t, "9618", redacted["Anthropic-Priority-Input-Tokens-Remaining"])
require.Equal(t, "2026-03-31T08:55:26Z", redacted["Anthropic-Priority-Input-Tokens-Reset"])
require.Equal(t, "10000", redacted["Anthropic-Priority-Output-Tokens-Limit"])
require.Equal(t, "6000", redacted["Anthropic-Priority-Output-Tokens-Remaining"])
require.Equal(t, "2026-03-31T08:55:26Z", redacted["Anthropic-Priority-Output-Tokens-Reset"])
require.Equal(t, "50000", redacted["Anthropic-Fast-Input-Tokens-Limit"])
require.Equal(t, "49000", redacted["Anthropic-Fast-Input-Tokens-Remaining"])
require.Equal(t, "2026-03-31T08:55:26Z", redacted["Anthropic-Fast-Input-Tokens-Reset"])
require.Equal(t, "25000", redacted["Anthropic-Fast-Output-Tokens-Limit"])
require.Equal(t, "24000", redacted["Anthropic-Fast-Output-Tokens-Remaining"])
require.Equal(t, "2026-03-31T08:55:26Z", redacted["Anthropic-Fast-Output-Tokens-Reset"])
require.Equal(t, "120000", redacted["X-RateLimit-Limit-Tokens"])
require.Equal(t, "119500", redacted["X-RateLimit-Remaining-Tokens"])
require.Equal(t, "12ms", redacted["X-RateLimit-Reset-Tokens"])