Repository navigation
v0.3.0
Security
- Machine-wide policy that the user's configuration cannot override, read from
HKLM\Software\Policies\SemanticRouter, macOS managed preferences or/etc/semantic-router/policy.json: turn off connector types and CLI presets, limit endpoints and the hosts that count as local, require local-only, cap prompt storage and retention, make the dashboard read-only or turn it off, control desktop updates and turn features off. A policy that cannot be read stops the router. See docs/enterprise.md. (#142) initdoes not add, or ask about, CLIs that the machine policy turns off. (#146)- Offline mode (
Network:OfflinewithNetwork:AllowedHosts, orOfflinein the machine policy): connectors, embeddings and webhooks reach only this machine and the allowed hosts, CLI and Copilot connectors are off, and the ONNX model is never downloaded. (#147) - Remote gateway clients need a key: with no
Middleware:GatewayKeysthey get 401 (AllowUnauthenticatedRemoteClientsrestores the old behaviour), and a key reference that does not resolve no longer opens the gateway. The container image readsROUTER_GATEWAY_KEY. (#129) - Default limits for remote clients (
Middleware:RemoteRequestsPerMinute, 120) and CLI queues (MaxQueue, 16), andForwardedHeaders:KnownProxies/KnownNetworksto trustX-Forwarded-Foronly from named proxies. (#129) - Web pages cannot use the gateway through DNS rebinding or a foreign
Origin;/v1/models,count_tokensand/semantic-router/classifyfollow the same gateway-key rule as the chat endpoints. (#114) - Dashboard responses forbid framing (
frame-ancestors 'none',X-Frame-Options: DENY) and sendnosniffandno-referrer; the dashboard bundles its fonts and sends a strict Content-Security-Policy. (#114, #129) - The admin API cannot change what a CLI runs (program, arguments, environment, home directory) or which paths memory sources read, unless
Dashboard:AllowProcessSettingsis on. (#114, #144) - The read-only dashboard role can no longer trigger calls that cost money or scan the disk (evaluate, classify, embeddings test, discover, check, memory refresh); dashboard sessions expire after
Dashboard:SessionLifetime(12 hours), and wrong tokens are throttled per address. (#144) - Dashboard identity from an authenticating reverse proxy (
Dashboard:TrustedIdentityHeader, believed only fromKnownProxies), the real actor on every configuration change, and an append-only audit log (GET /router/api/audit, an Audit log page,State:Retention:AuditDays). (#150) - A
Metricsdashboard role (Dashboard:MetricsToken, orMetricsinTrustedIdentityRoles) that sees figures only, never prompt or reply text. Signing out ends the session on the server, and an admin can sign out every session (POST /router/api/session/revoke-all); sessions from before the upgrade sign in once more. (#161) - With
State:StorePrompts: Off, a failed CLI run's output and the judge's reason for a grade are not stored or logged either;Redactedmasks credentials in them. (#162) - Local-only routing holds in more cases: a failed classification stays local while any route is local-only, a conversation kept local stays local (including named cloud models), local-only prompts stay off remote embedding endpoints (
Embeddings:IsLocal), and the judge respects routing profiles. (#114) - Secrets: CLI environment variables and more header names (auth, cookie, password, credential, session, signature) are masked; masked or stored keys are never sent to a connector that now points somewhere else; the configuration history keeps no secrets; keyed requests do not follow redirects; webhook URLs are not logged. (#114, #116)
- Keys written into addresses (
user:key@, key-named query values) are masked in the admin API, the configuration history and every address the dashboard anddoctorshow. (#117, #129) - CLIs run in a working directory only the user can write to and start by full path; a Custom CLI's
{prompt}cannot become an option; the router's files are readable by their owner only. (#114) - The data-protection key ring is encrypted at rest: to a certificate (
DataProtection:Certificate) on any OS, with DPAPI forSEMANTIC_ROUTER_KEYS_DIRon Windows; otherwise owner-only permissions and a start-up warning.doctorsays how the keys are protected. (#149) - Stored prompts:
State:StorePrompts(Redactedby default, masking credentials in previews;On;Off),State:Retention, and a purge (DELETE /router/api/activity, "Delete all activity") that leaves nothing readable in the database files. (#129) - Webhooks carry no prompt or reply text unless
Notifications:IncludeDetailsis on, and the stored project label is a hash of the client'suser_id, never the id. (#144) - Prompted tool calls are harder to forge: a random fence label per run that never reaches the client, defused tool results,
PromptedToolParsing: Strict(opt-in), calls copied from a tool result kept as text in both modes, and a prompted-call count in the activity log. (#129, #152) - No outbound traffic you did not configure: no web fonts from other hosts, Claude Code started without its auto-updater and non-essential traffic, Codex with update checks and analytics off.
doctorlists every host the router will contact. (#129, #143) - Desktop updates:
Updates:Publisherrequires an update to be code-signed by a given publisher before it is applied, and the updater refuses feeds over plain HTTP other than loopback. (#142, #151) - Verifiable releases: build provenance attestations for every asset,
SHA256SUMS, CycloneDX SBOMs, a keyless cosign signature and attestations on the container image, andTHIRD-PARTY-NOTICES.txt. See docs/releases.md. (#145) - Release pipeline: a tag must be on
main, tests pass before anything publishes, the desktop app is built in jobs without secrets, and the image is built and scanned (Trivy) before the registry login. CodeQL and OpenSSF Scorecard run on the repository. (#129) - The container runs on a chiseled ASP.NET Core image with no shell, with both base images pinned by digest. (#129)
- NuGet restores are locked to committed
packages.lock.jsonfiles in CI, the release and the container build. (#148)
Added
- A "Cut a release" workflow (Actions > Cut a release): the repository owner enters a version, it checks CI, code signing and NuGet publishing, tags
main, and the release workflow publishes everything with the CHANGELOG section as the release notes. Only the owner can release, by this workflow or a pushed tag. See docs/releases.md. - Cache-aware stickiness: a conversation stays on the model with the warm prompt cache when that is cheaper than moving, and returns to it after a failover (
Middleware:Conversation:CacheLifetime). (#137) - Prompt-cache markers on Anthropic requests translated from OpenAI or Responses clients (
Connectors:{name}:PromptCaching,CacheTtl). (#139) - Cache reads and writes per activity entry, and a cache hit rate on the Usage page. (#113)
- Endpoints card on the Overview (host, state, health and latency per connector, with a Check button) and a Service card (where the router runs, the URLs for clients, its health). (#117, #121)
Network:CaBundle, a PEM file trusted in addition to the system store, for TLS-inspecting proxies. (#143)- Desktop app: notify-only updates (
Updates:Apply: Notifyin the machine policy, or "Install updates when quitting" in the tray), and managed deployment docs. (#151) PromptRouterMiddlewareOptions.InjectedTags; the Copilot app's injected context blocks are ignored when classifying. (#118)- Model names
semantic-router,claude-semantic-router,-cheapestand-best;/v1/modelsentries have adisplay_name. (#156) tool-callsbenchmark for prompted tool calling. (#152)- A
quick-answersroute (tier 2) in the starting routes, for short factual and explain questions, and small-refactor utterances insimple-code; the sample evaluation set has 116 prompts. (#160) - Dashboard Feedback page and a Report button on each Activity entry: they open a GitHub issue (misroute, bug or idea) with the decision line, the model that answered, the embedding settings and the router version filled in. The prompt is left out unless you add it, and a local-only prompt answered by a cloud model points to private security reporting instead. The misroute and bug forms ask for the router version and the decision line. (#164)
Changed
- Targets .NET 10 (LTS) instead of .NET 9, whose support ends on 10 November 2026: the packages and the tool need the .NET 10 runtime, and the container runs on ASP.NET Core 10. Building needs a 10.0.4xx SDK. (#115, #148)
initasks before enabling a subscription CLI connector (Claude Code, Codex); declined or non-interactive runs add it disabled.--yesrestores the old behaviour and--no-clileaves CLIs out. (#141)- Prompts go to Claude Code and Copilot CLIs on standard input;
PromptInput: "Argument"restores the old behaviour. (#114) - Model comparisons count each model's prompt-cache prices, from the share of input it reads from cache (
Selection:CacheShareWindow,CacheShareSamples). (#138) - Only the prose of a prompt is embedded, without pasted code or data (
EmbedProseOnly, on by default); local-only routes are still checked against the whole prompt. (#157) - The
inittemplate routes Claude Code's*opus*asauto;opus-pinnedpins Opus, and an exact alias wins over a wildcard. (#156) - Routing profile
Projectsglobs match the hasheduser:…project label, and dashboard sessions sign in once more after the upgrade. (#144) - Dependency updates, among them GitHub.Copilot.SDK 1.0.22. (#134, #135, #136)
Fixed
- Cached-token counts on streamed and translated replies, from the CLIs and in "saved by routing". (#113)
- After a failover, the serving model is timed from its own attempt, so latency ranking and endpoint health no longer count the failed attempts against it. (#154)
- Claude Code: a conversation keeps its key across turns (with
Middleware:Conversation:SessionHeader), the session-title request is housekeeping, and a JSONmetadata.user_idis not used as the project. (#156) - A user message that holds only injected context (such as the Copilot app's
<system_reminder>) no longer hides the person's message from the classifier. (#118) - "What can you help me with?" goes to smalltalk; classify and Try a prompt apply callouts; computed properties are no longer written into
semantic-router.json. (#157) - Dashboard layout: steady Live rows, no sideways scrolling on Live, long tokens on Activity. (#155)
- A warm prompt cache on an Anthropic connector counts for its
CacheTtl(or the client's ownttl), not alwaysMiddleware:Conversation:CacheLifetime; a tool call written inside a line of a tool result is kept as text when the model repeats it. (#162)
What's Changed
- Get the prompt-cache numbers right (#93) by @siebe41 in #113
- Move to .NET 10 (LTS) before .NET 9 support ends by @siebe41 in #115
- Security fixes from the October audit by @siebe41 in #114
- Don't carry stored-secret references to a new address over the admin API by @siebe41 in #116
- Show each endpoint's host, state and health; hide credentials in shown addresses by @siebe41 in #117
- Support additional injected context blocks from agentic clients by @siebe41 in #118
- Show where the router runs, the URLs for clients and its health in the dashboard by @siebe41 in #121
- Safe to install at work: gateway defaults, no third-party traffic, stored prompts, tool-call forging, supply chain by @siebe41 in #129
- Cache-aware stickiness: stay on the model with the warm prompt cache (#94) by @siebe41 in #137
- Count prompt-cache prices when comparing models' cost (#96) by @siebe41 in #138
- Bump @playwright/test from 1.56.1 to 1.64.0 in /tests/dashboard-e2e in the e2e group by @dependabot[bot] in #134
- Bump the actions group with 4 updates by @dependabot[bot] in #135
- Bump the nuget-minor-and-patch group with 6 updates by @dependabot[bot] in #136
- Cache markers on translated Anthropic requests (#95) by @siebe41 in #139
- Machine-wide policy that the user's configuration cannot override (#100) by @siebe41 in #142
- Fix three timing-sensitive tests (judge agreement; state file on macOS and Windows) by @siebe41 in #140
- init asks before enabling subscription CLIs; "Using it at work" in TERMS (#101) by @siebe41 in #141
- Prompt data out of webhooks, hashed project labels, a read-only role that cannot spend, session expiry and sign-in throttling (#107, #108) by @siebe41 in #144
- Quiet Codex, add Network:CaBundle, and list egress in doctor (#110) by @siebe41 in #143
- init leaves out the CLIs the machine policy turns off (#100) by @siebe41 in #146
- Verifiable releases: provenance, SBOMs, SHA256SUMS, cosign-signed image, third-party notices (#102) by @siebe41 in #145
- Offline mode: reach only this machine and the hosts you allow (#110) by @siebe41 in #147
- NuGet lock files with locked restores, a pinned SDK band, and ruleset records (#104) by @siebe41 in #148
- Encrypt the data-protection key ring at rest, and say how it is protected by @siebe41 in #149
- Prompted tool calls: refuse copied calls, hide the run's label, mark them in the activity log by @siebe41 in #152
- Dashboard identity from a trusted proxy, the real actor, and an audit log by @siebe41 in #150
- Desktop updates: notify-only mode, publisher check, managed deployment docs by @siebe41 in #151
- Dashboard layout: steady Live rows, no sideways scroll, long tokens in Activity by @siebe41 in #155
- Time the serving model from its own attempt after a failover by @siebe41 in #154
- Claude Code compatibility: stable conversations, title turns, projects, model names by @siebe41 in #156
- Routing quality: embed the prose not the pasted code, callouts in classify, smalltalk, no computed keys by @siebe41 in #157
- Samples route Claude Code's Opus name as auto; new Live screenshot; roadmap up to date by @siebe41 in #158
- CHANGELOG, GOVERNANCE and response targets (drafts); releases fail when installers would be unsigned by @siebe41 in #159
- Default routes: a quick-answers route and small-refactor utterances by @siebe41 in #160
- Dashboard: a metrics role that never sees prompt text, and sign-out that ends the session by @siebe41 in #161
- Follow-ups: no model text stored with StorePrompts Off, cache TTL for stickiness, mid-line copied tool calls by @siebe41 in #162
- Dashboard feedback form that opens a pre-filled GitHub issue by @siebe41 in #164
- Owner-only "Cut a release" workflow by @siebe41 in #163
- Docs pass: correct, current and consistent before the first feedback by @siebe41 in #165
Full Changelog: v0.2.1...v0.3.0