Commit Graph
185 Commits
Author SHA1 Message Date
whit3rabbitandClaude Sonnet 4.6 2e22cfef3c chore: stage pre-existing main branch changes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 15:28:55 -05:00
whit3rabbitandClaude Sonnet 4.6 1f9bb02968 feat: add Docker support, Gemini input handler, and batch engine improvements
Adds multi-arch Docker build with docker-compose, GitHub Actions CI workflow,
Gemini native input parsing, batch engine SQLite/file/webhook refactors,
and Bedrock streaming fixes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 12:01:24 -05:00
whit3rabbit b6158cf3fe feat: refactor admin routes into modular sub-modules and implement model management API endpoints 2026-04-04 20:30:13 -05:00
whit3rabbitandClaude Sonnet 4.6 72ba916241 fix: streaming tool execution iterates up to max_iterations like non-streaming path
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>
2026-04-04 20:30:13 -05:00
whit3rabbitandClaude Sonnet 4.6 0593d1ae1b feat: add has_more to pagination responses, disable Next when no more results
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>
2026-04-04 20:30:13 -05:00
whit3rabbitandClaude Sonnet 4.6 01e04fa6b3 feat: expose period_reset_at in list_keys and show in budget bar
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>
2026-04-04 20:30:13 -05:00
whit3rabbitandClaude Sonnet 4.6 28580ff311 fix: validate backend_name exists before adding model deployment
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>
2026-04-04 20:30:13 -05:00
whit3rabbitandClaude Sonnet 4.6 17b640b319 fix: remove deprecated claude-3-sonnet-20240229 from static model list
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>
2026-04-04 20:30:13 -05:00
whit3rabbit ab34b32816 fix: show empty-state message on all pages, not just page 0 2026-04-04 20:30:13 -05:00
whit3rabbit e0084d6630 fix: WebSocket reconnect uses exponential backoff, max 10 retries 2026-04-04 20:30:13 -05:00
whit3rabbitandClaude Sonnet 4.6 c27a7be80a fix: redirect to login on 401 in admin UI
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>
2026-04-04 20:30:13 -05:00
whit3rabbitandClaude Sonnet 4.6 5f1bc056b0 fix: re-fetch CSRF token before each admin mutation
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>
2026-04-04 20:30:13 -05:00
whit3rabbitandClaude Sonnet 4.6 84315840f8 fix: harden security, fix expires_at TTL, improve translation accuracy
- 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>
2026-04-04 13:33:23 -05:00
whit3rabbitandClaude Opus 4.6 04a58bfc6f style: run cargo fmt to fix CI formatting check
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 05:46:21 -05:00
whit3rabbitandClaude Opus 4.6 0fe011d718 fix(security): harden 4 medium findings from security audit
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>
2026-03-31 20:31:11 -05:00
whit3rabbitandClaude Opus 4.6 c7475c724b fix(security): harden proxy against 11 findings from security audit
- Critical: execute_bash requires ANYLLM_ALLOW_BASH_EXECUTION=1 env var gate
- High: move all unsafe set_var calls to sync fn main() before tokio runtime
- High: admin token uses 256-bit getrandom+hex instead of UUID v4 (122-bit)
- High: validate LANGFUSE_HOST with validate_base_url() to prevent SSRF
- Medium: rate-limit /admin/csrf-token to prevent unauthenticated map flooding
- Medium: cap query param string lengths (backend, status, action, target_type)
- Medium: add PATCH to CSRF method validation
- Low: read_file rejects execution when allowed_dirs is empty (was warn-only)
- Info: LOG_BODIES=true emits startup warning about sensitive data exposure
- Info: remove space from is_safe_model_name allowed charset
- Info: ADMIN_TOKEN_PATH rejects path traversal sequences

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:50:32 -05:00
whit3rabbitandClaude Sonnet 4.6 df975e739d refactor(security): extract helpers for CSRF cap and timestamp validation
- 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>
2026-03-31 17:30:02 -05:00
whit3rabbitandClaude Sonnet 4.6 9e58822583 fix(security): restore atomic CSRF token removal with DashMap + size cap
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>
2026-03-31 17:22:37 -05:00
whit3rabbit a00a05d598 fix(security): remove debug_assert that always fires inside #[tokio::main] 2026-03-31 17:21:06 -05:00
whit3rabbit 22f274a463 fix(security): assert single-threaded invariant before unsafe set_var calls 2026-03-31 17:15:37 -05:00
whit3rabbit b733a4cb3c fix(security): tighten looks_like_jwt to require valid Base64url segments 2026-03-31 17:14:28 -05:00
whit3rabbit 440357951d fix(security): validate since/until timestamps in admin log query handlers 2026-03-31 17:11:56 -05:00
whit3rabbit e0f41e0f5b fix(security): require explicit ADMIN_TOKEN on non-Unix (token file unsupported) 2026-03-31 17:08:59 -05:00
whit3rabbit 54c612a661 fix(security): replace unbounded CSRF DashMap with moka cache (1000 entries, 24h TTL) 2026-03-31 17:03:47 -05:00
whit3rabbitandClaude Sonnet 4.6 d49fe6a986 fix(security): validate webhook_url against SSRF rules in create_batch
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>
2026-03-31 17:00:10 -05:00
whit3rabbit 12078ab2dd fix(security): enforce model allowlist on Gemini native handler path 2026-03-31 16:57:58 -05:00
whit3rabbit c336c2cfcc fix(security): enforce model allowlist on Anthropic passthrough path 2026-03-31 16:54:37 -05:00
whit3rabbitandClaude Sonnet 4.6 2bc0125497 refactor(batch_engine): eliminate extra DB round-trip in cancel, consolidate format_epoch_iso8601
- 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>
2026-03-31 16:36:00 -05:00
whit3rabbitandClaude Sonnet 4.6 8ac9ef2677 Merge feat/batch-engine-phase1 into main
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>
2026-03-31 16:00:37 -05:00
whit3rabbitandClaude Sonnet 4.6 e6dd396b05 fix(security): apply 2026-03-31 security audit hardening
- Use getrandom for CSPRNG HMAC secret generation
- Case-insensitive admin path check prevents bypass via /Admin/, /ADMIN/
- Add TRUSTED_PROXY_DEPTH for multi-hop proxy X-Forwarded-For extraction
- Add dangerous-builtin-tools feature flag with security warning
- Remove repomix-output.xml, add .syntext to .gitignore

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 15:56:30 -05:00
whit3rabbitandClaude Sonnet 4.6 ef5f0f596c refactor(proxy): wire batch handlers through BatchEngine crate
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>
2026-03-31 06:49:40 -05:00
whit3rabbitandClaude Sonnet 4.6 bdf73c9057 test(admin): update virtual_keys tests for one-time CSRF tokens
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>
2026-03-31 06:48:44 -05:00
whit3rabbit 9803668b7d feat(proxy): wire batch_engine into proxy crate
- 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
2026-03-31 06:48:16 -05:00
whit3rabbitandClaude Sonnet 4.6 8bfc04e583 fix(tools): warn at startup when execute_bash policy is Allow
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>
2026-03-31 06:43:16 -05:00
whit3rabbitandClaude Sonnet 4.6 2019f92824 fix(admin): enforce one-time CSRF tokens tracked server-side
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>
2026-03-31 06:42:41 -05:00
whit3rabbitandClaude Sonnet 4.6 0efe5ae671 fix(mcp): use SSRF-safe HTTP client for MCP tool calls and discovery
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>
2026-03-31 06:38:25 -05:00
whit3rabbitandClaude Sonnet 4.6 52173ea8eb fix(admin): validate model_name/backend_name/actual_model in add_model
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>
2026-03-31 06:37:08 -05:00
whit3rabbitandClaude Sonnet 4.6 060c0e418f fix(mcp): validate server names to prevent tool routing ambiguity
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>
2026-03-31 06:36:41 -05:00
whit3rabbitandClaude Sonnet 4.6 babd0e6ff0 fix(tools): enforce allowed_dirs allowlist in ReadFileTool
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>
2026-03-31 06:33:47 -05:00
whit3rabbit d2a56c5ad2 feat(proxy): wire batch_engine into proxy crate
- 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
2026-03-31 06:30:25 -05:00
whit3rabbit 2b885a0938 feat(batch_engine): scaffold crate with core types, queue, file store, webhook, and engine facade
- BatchId, ItemId, BatchJob, BatchItem, RequestCounts, BatchStatus, ExecutionMode
- EngineError, QueueError types
- JSONL validation migrated from proxy
- SQLite schema init with migration from old tables
- FileStore for batch file storage
- JobQueue trait + SqliteQueue with full job lifecycle
- WebhookQueue trait + SqliteWebhookQueue with durable delivery
- WebhookDispatcher background loop with HMAC signing
- BatchEngine facade: submit, get, list, cancel, get_items
- 32 tests passing, clippy clean
2026-03-31 06:24:49 -05:00
whit3rabbitandClaude Opus 4.6 cf417c9cfb fix(tools): share reqwest client and add SSRF validation for MCP URLs
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>
2026-03-30 22:47:56 -05:00
whit3rabbitandClaude Opus 4.6 a5b095f988 fix(tools): handle Deny policy as error ToolResult instead of silent PassThrough
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>
2026-03-30 22:47:46 -05:00
whit3rabbitandClaude Sonnet 4.6 a88a48b50e fix: wire tool engine and MCP manager into main.rs from config
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>
2026-03-30 22:35:44 -05:00
whit3rabbitandClaude Sonnet 4.6 d7082b8812 test: add integration tests for tool execution engine
Covers partition_tool_calls, execute_tool_calls (success + ordering),
tool_results_to_user_message (success + error is_error flag),
is_duplicate, extract_tool_calls, and passthrough-policy behavior.
10 tests, all green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 22:24:14 -05:00
whit3rabbitandClaude Opus 4.6 a56bc95b5f feat(tools): add collect-then-execute streaming tool execution
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>
2026-03-30 22:21:37 -05:00
whit3rabbitandClaude Opus 4.6 4a6604f640 feat(tools): integrate non-streaming tool execution into messages handler
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>
2026-03-30 22:14:24 -05:00
whit3rabbitandClaude Opus 4.6 8675dec585 feat(tools): integrate non-streaming tool execution into chat completions handler
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>
2026-03-30 22:14:20 -05:00
whit3rabbitandClaude Opus 4.6 a4119dd75c feat(tools): add helper functions for tool call extraction and result building
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>
2026-03-30 22:14:16 -05:00
whit3rabbitandClaude Sonnet 4.6 3a62796a75 feat(tools): wire ToolEngineState into AppState with config-driven setup
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>
2026-03-30 22:07:23 -05:00