Document ANTHROPIC_AUTH_TOKEN, ANTHROPIC_API_KEY="", and
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 alongside ANTHROPIC_BASE_URL
so users know the full set of vars needed to run Claude Code against the proxy.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Wrap admin token in Zeroizing<String> so memory is wiped on drop
- Use SSRF-safe HTTP client for Langfuse and webhook dispatcher
- Wire up webhook dispatcher at startup (was previously un-started)
- Fix batch expires_at: was using now instead of now+24h
- Extract epoch_secs() helper; replace 4 inline SystemTime::now() blocks
- Gemini tool_choice {type:tool}: use ANY+allowedFunctionNames instead of AUTO
- Map Anthropic thinking budget_tokens to OpenAI reasoning_effort
- Preserve temperature/top_p for GA o-series models (o1/o3/o3-mini/o4-mini);
only strip for o1-preview and o1-mini which reject those params
- Azure simple config: always route through default_base_url; guard against
double-appending deployment path when user provides a full URL
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. CSRF token store: replace unbounded DashMap with moka::sync::Cache
(max 1,000 entries, 24h TTL) to prevent memory exhaustion.
2. Cloud metadata SSRF: warn at startup if QDRANT_URL/REDIS_URL points
at 169.254.169.254 or metadata.google.internal.
3. TLS P12 password: wrap in zeroize::Zeroizing<String> so it is zeroed
from heap on drop, preventing extraction from core dumps.
4. CSP nonce: replace unsafe-inline with per-request 128-bit nonce on
admin SPA script/style tags.
Also includes: batch/bedrock model allowlist enforcement, litellm
master_key extraction moved to single-threaded fn main().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- MAX_ISSUED_CSRF_TOKENS constant replaces magic 1_000 in get_csrf_token
- check_time_range() deduplicates since/until validation in get_requests and get_audit_log
- looks_like_jwt uses pattern match on splitn(4) to clarify exactly-3-parts invariant
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reverts issued_csrf_tokens from moka::sync::Cache back to Arc<DashMap>
to restore the atomic remove() semantics that prevent two concurrent
requests with the same token from both passing the get() check before
either invalidates it. Adds a 1,000-entry size cap in get_csrf_token to
prevent memory exhaustion (the original motivation for the moka change).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add webhook_url field to CreateBatchRequest and validate it via
validate_base_url before submitting, rejecting private/loopback/metadata
IP targets. Pass the field through to BatchSubmission instead of None.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move format_epoch_iso8601 from queue/sqlite.rs to db.rs (single canonical location)
- Change cancel() to return BatchJob instead of BatchStatus so callers
get the full job without a second query
- Pass batch_webhook_url directly to fire_webhook to avoid re-fetching the job
- Remove is_openai_or_azure_backend (duplicate of is_batch_supported)
- Replace magic literal 3 with DEFAULT_MAX_RETRIES constant
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolves conflicts: take HEAD (security audit) for mcp.rs imports,
register_server_blocking error handling, and maybe_execute_tools loop.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Proxy batch handlers now use BatchEngine for job lifecycle, file storage,
and webhook delivery instead of direct SQLite calls. Old batch/db.rs
stripped to Anthropic-specific mapping only. Cancel endpoint at
POST /v1/batches/{id}/cancel. BatchEngine initialized in main.rs startup
with second SQLite connection. Cancel integration test added.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CSRF tokens are now one-time use. Tests updated to:
- Pre-insert TEST_CSRF_TOKEN in test_admin_router() for unit tests
- Call reinsert_csrf() before each additional mutation in multi-step tests
- Add fetch_csrf() helper for real-server tests
- update create_key_via_admin() to fetch a fresh token per call
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace batch/mod.rs types with re-exports from anyllm_batch_engine
- Strip batch/db.rs to Anthropic->OpenAI ID mapping only (batch_file/batch_job owned by engine)
- Rewrite batch/routes.rs to use BatchEngine for upload, create, get, list, cancel
- Remove batch_file/batch_job table creation from admin/db.rs init_db
- Add batch_engine parameter to app_multi_with_shared (5th arg, Option<Arc<BatchEngine>>)
- Initialize BatchEngine in main.rs with its own SQLite connection (admin-enabled path)
- Update batch_api.rs tests to use make_test_batch_engine() helper
- Fix anthropic_batch.rs to call init_anthropic_batch_map_table instead of removed init_batch_tables
- Add cancel_queued_batch integration test
Emits tracing::warn! when execute_bash is configured with policy: allow
so operators see an explicit reminder that this permits arbitrary OS
command execution. The dangerous-builtin-tools compile-time feature flag
remains the primary gate; this is an additional runtime visibility measure.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GET /admin/csrf-token now stores the generated token in
SharedState::issued_csrf_tokens (DashMap). validate_csrf middleware
verifies the X-CSRF-Token header was server-issued and removes it on
first use, preventing replay of previously issued tokens across multiple
mutating requests. validate_csrf switched to from_fn_with_state to
receive SharedState. Adds test: post_with_unissued_csrf_returns_403.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces reqwest::Client::new() in McpServerManager::new() and
discover_tools() with build_http_client(ssrf_protection: true), which
attaches SsrfSafeDnsResolver. This prevents DNS rebinding: a domain that
passes the registration-time check but later resolves to a private/metadata
IP (e.g. 169.254.169.254) is blocked at connection time by the resolver.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Applies is_safe_model_name to all three AddModelRequest fields before use,
preventing log injection via newlines or control characters in audit log
detail entries. Consistent with existing validation in put_config.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
MCP tool names use mcp_{server}_{tool}; underscores in server names make
parse_mcp_tool_name ambiguous. is_valid_mcp_server_name rejects names
containing underscores (allows alphanumerics + hyphens only).
register_server_blocking now returns Result<(), String> so callers handle
invalid names explicitly. Callers in main.rs and admin routes updated.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds allowed_dirs config field to BuiltinToolConfig. ReadFileTool now
rejects reads outside the configured base directories after canonicalize(),
blocking both path traversal and symlink attacks. Logs a warning when
allowed_dirs is empty. Threads config through register_all so constructors
receive per-tool settings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add anyllm_batch_engine dependency to proxy Cargo.toml
- Add batch_engine field to AppState (Option<Arc<BatchEngine<...>>>)
- Add POST /v1/batches/{batch_id}/cancel route and handler
- cancel_batch handler uses BatchEngine when available, falls back to not_implemented
McpServerManager now holds a shared reqwest::Client (built once in new()).
call_tool uses self.client instead of creating a new client per call.
discover_tools_impl extracted as a free fn; both the instance method
(discover_tools_with_client) and the static fallback delegate to it.
SSRF protection added at both registration points:
- admin add_mcp_server endpoint: validate_base_url() before calling discover
- main.rs startup: skip and log any MCP server URL that fails SSRF check
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
partition_tool_calls now returns three buckets (auto_execute, pass_through,
denied). Denied tools generate ToolResult entries with is_error:true and a
"denied by policy" message, which are fed back to the LLM in a follow-up
turn so it can see the rejection. Updated all callers (maybe_execute_tools,
streaming handler in chat_completions.rs) and added unit + integration tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ToolEngineState and McpServerManager were hardcoded to None. Now:
- SimpleParsed carries a ToolStartupConfig with the three tool sections
- LoadResult exposes that config to main.rs
- main.rs constructs ToolEngineState (registry, policy, loop config) when
any tool section is present; MCP servers are discovered async at startup
with a warning on failure (no panic)
- tool_engine and mcp_manager on SharedState are populated from the same
Arc so both proxy handlers and admin API share the same instance
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Accumulates tool call fragments during the chat_completions streaming
loop, then after the initial stream completes, checks for auto-executable
tools via the ToolEngine. If found, executes tools in parallel, builds a
follow-up request with tool results, and streams the follow-up response
through the same SSE channel. Defers [DONE] until after any tool
execution follow-up completes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When tool_engine is configured, the non-streaming /v1/messages handler
now checks for tool_use blocks in the response, executes registered
tools, and makes a follow-up backend call with the results.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When tool_engine is configured, the non-streaming /v1/chat/completions
handler now checks for tool_use blocks in the response, executes registered
tools, and makes a follow-up backend call with the results.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds extract_tool_calls, tool_results_to_user_message, and
response_to_assistant_message to execution.rs for use by non-streaming
handlers. Includes two tests for extract_tool_calls.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ToolEngineState struct (registry, policy, loop_config, mcp_manager)
and tool_engine field to AppState. Update app_multi_with_shared signature
to accept the new parameter; all callers pass None until config-driven
wiring is implemented in a future task.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements the Tool trait on McpToolAdapter, delegating execute() to
McpServerManager::call_tool(). Adds register_mcp_tools() to bulk-register
all discovered MCP tools into a ToolRegistry.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds SimpleConfig::build_tool_config() that converts builtin_tools and
mcp_servers entries into a ToolExecutionPolicy (with exact-match and
glob rules) and converts tool_execution into a LoopConfig with sensible
defaults. Two tests cover both paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- dispatch() is now async (webhook enqueue is async)
- All event emission routed through NotificationManager, not direct
event_bus.emit() calls
- Native batch poller emits Started/Progress events during polling
- SSE handler sends snapshot event on connect for late subscribers
- WebhookQueue trait gets reclaim_expired_leases() for stuck deliveries
- WebhookDispatcher runs lease reclaim loop
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds three new optional top-level fields to SimpleConfig for upcoming
tool execution support: ToolExecutionConfig (loop limits/timeouts),
BuiltinToolConfig (per-tool enable/policy/timeout), and McpServerConfig
(name/url/policy). Two unit tests verify both present and absent cases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements ToolCall/ToolResult types, LoopConfig, partition_tool_calls(),
execute_tool_calls() (parallel via JoinSet with per-tool timeout), and
is_duplicate(). Adds list_names() to ToolRegistry. All 6 unit tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>