Adds `expose_degradation_warnings: bool` to `Config`. Defaults to false
(simple mode). Set ANYLLM_DEGRADATION_WARNINGS=true/1 to opt in, or it
enables automatically when PROXY_CONFIG is set (advanced/config-file mode).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Expand get_csrf_token doc comment to explain why the route is public
(login form needs a token before auth), and document the two layers
that make it safe: reject_cross_origin middleware and SameSite=Strict
cookie.
Add 2026-03-30 security audit fix summary to CLAUDE.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add validate_base_url() check in with_named() URL filter. URLs pointing
to private RFC 1918 ranges, loopback (127.x, ::1, localhost), and
cloud metadata (169.254.169.254) are now rejected with a warning instead
of accepted.
Replace plain reqwest::Client::builder() with build_http_client()
configured with ssrf_protection:true for DNS-level SSRF protection.
Update tests: filters_non_url_callbacks now expects localhost to be
rejected; add rejects_private_and_loopback_webhook_urls test;
rename http_urls_accepted_not_rejected to http_plaintext_to_public_host_accepted.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add validate_oidc_url() that delegates to validate_base_url(). Call it
before fetching the discovery document and before fetching the JWKS URI
from the discovery response.
Replace plain reqwest::Client::builder() with build_http_client()
configured with ssrf_protection:true, so DNS-level SSRF protection
applies to both the discovery and JWKS refresh calls.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add ConnectInfo<SocketAddr> extractor to all seven admin mutation
handlers (put_config, delete_config_override, create_key, update_key,
revoke_key, add_model, remove_model). Pass addr.ip().to_string() as
source_ip in each emit_audit call.
Update integration tests to use into_make_service_with_connect_info
and MockConnectInfo so the ConnectInfo extractor is satisfied in test
servers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The old implementation used a (window_start, count) tuple that reset every
60 seconds, allowing burst-reset attacks achieving 2x the intended RPM. The
new implementation uses a VecDeque<u64> of millisecond timestamps, evicting
entries older than 60 000 ms on each check, matching the approach already
used by the virtual key rate limiter in keys.rs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract check_admin_rate_limit_with_rpm(ip, rpm) so the test passes
rpm directly instead of mutating the global ADMIN_RPM atomic, which
raced with test_router() calling set_admin_rpm(10_000) in parallel.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
iso8601_to_epoch returned seconds, losing sub-second precision. The
start_time was always off by up to 999ms. New iso8601_to_epoch_ms parses
the fractional-seconds component (.SSS or .SSSSSS) and returns epoch
milliseconds directly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
check_and_reset_period resets period_spend_usd in the DashMap but never
writes to SQLite. accumulate_spend then adds new costs to the stale
old-period total in SQLite. On restart, the bloated value locks keys out.
Fix: propagate period_reset through VirtualKeyContext so record_cost can
call reset_period_spend before accumulate_spend in the same blocking task.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The IPv6 arm of is_private_ip only checked loopback, unspecified, and
IPv4-mapped addresses. Unique Local Addresses and link-local addresses
were not blocked, allowing SSRF via URLs like http://[fc00::1]/v1.
Uses bitwise checks since Ipv6Addr::is_unique_local() is unstable.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The leftmost IP in X-Forwarded-For is attacker-controlled. A trusted
reverse proxy appends the real client IP to the right. Taking .next()
allowed bypassing the IP allowlist with a spoofed header like
"X-Forwarded-For: 127.0.0.1". Now uses .last() on the comma-split.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rewrote the "Using as a Library" README section: added cargo dependency
snippets, reorganized HTTP client/pure translation/reverse translation
examples, and added error handling + tool calling code blocks.
Changed AnthropicClient::forward/forward_stream to accept
`&[(&str, &str)]` instead of `&[(String, String)]` so the passthrough
handler can pass string literals directly without allocating.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude Code v2.1.86+ sends x-claude-code-session-id for session routing/aggregation.
The anthropic-beta header enables beta API features that must reach the upstream to
take effect (e.g., extended thinking, interleaved thinking).
- passthrough.rs: collect and forward the two headers via extra_headers
- anthropic_client.rs: accept extra_headers in forward/forward_stream/send_with_retry
- middleware.rs: log x-claude-code-session-id at debug level alongside anthropic-beta
- rate_limit.rs: parse and forward anthropic-organization-id response header; 3 new tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Test count: ~555 -> ~906
- Remove stale streaming metrics note (fixed)
- Expand Working section with 14 new features added since last update
- Fix batch endpoint from "stub" to real implementation
- Update routes.rs architecture description
- Add 20260327 entry to Recent Changes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Separate thought parts (Part.thought=true) from answer parts in each
streaming response. Emit ContentBlockStart(Thinking), ThinkingDelta,
and ContentBlockStop events using the same full-response diffing pattern
as text. Close the thought block before opening the text block. Handle
thought block cleanup in finish(). 3 new streaming tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add `thought: Option<bool>` to `Part` for thought parts produced by
Gemini 2.5 thinking models. Add `ThinkingConfig` struct and wire it into
`GenerationConfig.thinking_config` for request-side control.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolves Dependabot alert #1 (moderate severity).
The jsonwebtoken v10 API is compatible with the existing oidc.rs usage.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- translate.rs: add translate_request_gemini, translate_response_gemini,
new_gemini_stream_translator wrappers; re-export from lib.rs
- gemini_native.rs: POST /v1/messages handler for GeminiNative backend;
non-streaming calls generate_content, streaming uses read_sse_frames +
GeminiStreamingTranslator to diff full responses into Anthropic SSE events
- streaming.rs: expose read_sse_frames, send_events, StreamOutcome as
pub(super) so gemini_native.rs can reuse the SSE reading infrastructure
- backend/mod.rs: construct BackendClient::GeminiNative when
GEMINI_API_FORMAT=native (both single-backend and multi-backend paths)
- routes.rs: add HandlerMode::GeminiNative; detect from BackendClient
variant at AppState build time; dispatch to gemini_native_handler
GEMINI_API_FORMAT=openai (default) preserves existing behavior.
GEMINI_API_FORMAT=native uses the new direct translation path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New GeminiNativeClient with generate_content and generate_content_stream methods
- GeminiNative variant added to BackendClient enum and BackendError
- All existing match arms updated to handle the new variant
- 10 unit tests for URL construction, model mapping, error display
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove needless borrows in openai_batch_client.rs
- Replace redundant closure with function reference in CSRF middleware
- Narrow handler visibility to pub(crate) for anthropic_batch routes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- fetchCsrfToken(): fetches from GET /admin/csrf-token on load, stores in _csrfToken
- mutatingHeaders(): returns authHeaders merged with X-CSRF-Token header
- All POST/PUT/DELETE fetch calls now use mutatingHeaders() instead of authHeaders
- fetchCsrfToken() called before loadDashboard() so token is ready at startup
- normalize_schema_for_strict: recursively ensures all object schema
properties are listed in required and sets additionalProperties: false
- apply_strict_to_forced_tool: sets strict=true and normalizes the
parameter schema for the named forced tool, leaves others unchanged
- 6 unit tests covering normalization, nesting, merge, non-object, and
apply_strict cases
Gemini and Vertex only accept the OpenAPI 3.0 subset of JSON Schema in
function parameters. Adds sanitize_schema_for_gemini() in tools_map.rs and
applies it to all tool parameter schemas in both the non-streaming (routes.rs)
and streaming (streaming.rs) Gemini/Vertex code paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>