The single follow-up pass after initial stream tool calls is replaced with
a loop bounded by engine.loop_config.max_iterations. Each iteration collects
new tool calls from the follow-up stream, builds growing current_messages,
and breaks early if no new tool calls appear or total_timeout is reached.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fetch limit+1 rows in get_requests and get_audit_log; if the extra row
exists set has_more=true and truncate back to limit. Admin UI disables
the Next button when has_more is false. Two new integration tests verify
the field is present and false on an empty DB.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add period_reset_at_from_row() to keys.rs for DB rows (budget_duration
as string), wire it into list_keys response, render reset date below
the budget bar in admin UI. Also fix with_http_client missing max_retries
field after retry.rs signature change.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rejects POST /admin/api/models with 400 if the backend_name is not in
backend_metrics, preventing silent routing failures at dispatch time.
Includes a regression test that confirms the guard fires correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a regression test to prevent re-introduction, then removes the
deprecated entry from STATIC_CLAUDE_MODELS.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds handleUnauthorized() to clear the session and show the login
overlay when any apiFetch or fetchCsrfToken call returns 401.
Also adds auth headers to the CSRF token request so the server can
validate the session.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The server issues one-time-use CSRF tokens (invalidated after first use).
The UI was fetching a single token on page load and reusing it, causing
all mutations after the first to fail with 403. Replace the six mutation
call sites with a new mutatingFetch() helper that fetches a fresh token
before each request, and remove the upfront fetchCsrfToken() at startup.
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>