Release 1.31.0 - Backend Identity, Tool-Level Security, and a Faster Hot Path
September 2026
Upgrading from 1.30.0
This section covers everything you need to know to upgrade from 1.30.0 to 1.31.0.
Breaking Changes
This release carries more behaviour changes on already-shipped paths than 1.30.0 did. Most come from security fixes that started enforcing a rule the code already described, so a deployment can be relying on the gap without knowing it.
1. basic_header token endpoints no longer receive client_id in the request body. RFC 6749 §2.3.1 forbids more than one client authentication method per request, so sending the Basic header and the body parameter together was out of spec. Entra, Okta and Keycloak all accept the corrected form. This lands on the existing per-user egress path, not only the new discovery one, so any operator who selected custom_token_auth_style: basic_header is affected by a change that otherwise reads as a discovery feature (#1781).
2. The OBO egress target-audience and scope allowlist now runs on the live paths. The rules existed in a ServerInfo model validator that the request paths never construct, so the write path enforced 2 of 5 checks and the vend path enforced none. A stored OBO directive that does not conform now returns 403 at vend time instead of being exchanged. Check egress_oauth.scopes and target_audience on any server using egress_auth_mode: obo_exchange before upgrading (#1760).
3. Only allowlisted headers reach the external registration gate. Anything depending on a header being forwarded to that gate stops receiving it (#1761).
4. Read surfaces now apply the access check their sibling paths already did. Callers see less than before, which is the fix:
server.jsonexport and virtual-server tool listing filter per caller (#1762).- Single-skill read and the skill catalog enforce the discovery gate (#1759).
- Tools blocked by a security scan are hidden from every read projection, so a
tools/listthat used to show them no longer does (#1782).
5. The IdP bootstrap refuses default, weak and unset credentials. A PingFederate deployment must now supply PF_ADMIN_PASS, PINGFEDERATE_CLIENT_SECRET and the new PF_REGISTRY_ADMIN_PASSWORD from the environment, with no fallback. .env.example no longer ships PINGFEDERATE_CLIENT_SECRET=changeme, which was itself the first entry of the denylist and made cp .env.example .env a guaranteed bootstrap failure. Three Keycloak changes also reach existing realms for the first time, because all three provisioners previously treated an existing realm or a 409 as "nothing to do" (#1763):
- The
+wildcardwebOriginsentry is gone. It expanded to the origin of every redirect URI, so CORS widened silently whenever a redirect was added. fullScopeAllowed: falseonmcp-gateway-webandmcp-gateway-m2m. Tokens no longer carry every role the user holds. Authorization here is group-driven, so nothing loses access.bruteForceProtectedis enabled withfailureFactor: 5. The seeded admin can now be locked out after five failed logins.
6. Search results are ordered differently, and search_registry returns more of them. relevance_score was hardcoded to 1.0 for keyword-matched tools on two of four code paths, so a caller had nothing to rank on. Scores are graded now, tools scoring 0.0 are dropped rather than returned with a meaningless number, and MatchingToolResult.relevance_score is required. A new similarity_score carries the raw cosine, which is comparable across queries where relevance_score is not. search_registry also stops discarding the virtual servers it had already scored and counted against max_results (#1755).
7. DEDUP_SCORE_THRESHOLD is now optional, with a per-model default. Cosine scales are not comparable across embedding models, so a single number was wrong for every deployment that changed models. Leave it unset and the registry resolves it from EMBEDDINGS_MODEL_NAME: 0.45 for all-MiniLM-L6-v2, 0.85 for text-embedding-ada-002, and a deliberately conservative 0.6 for a model with no calibration. The previous flat default was 0.7.
Two traps here. The three DEDUP_* variables were absent from all three compose files, so on Docker a value in .env never reached the container and the registry ran on code defaults. They are wired now, which means a stale DEDUP_SCORE_THRESHOLD in your .env starts taking effect on this upgrade. And expect far fewer advisory matches: the check used to return suggestions on nearly every registration regardless of what was typed (#1721, #1744).
8. The MongoDB init script runs from the image and no longer destroys data. This carries its own mongodb-configure image tag bump, so the Helm dependency step below is not optional (#1757).
9. A blank method label no longer drops rows from the auth metric. X-Auth-Method was written with an or "" fallback, so the middleware read a present-but-empty header, its "unknown" default never fired, and Prometheus treated the blank value as absent. Those requests fell out of sum by (method). They now appear as method="unknown", so a dashboard that looked complete may show a new slice. The header contract is unchanged. The telemetry heartbeat schema_version goes "5" to "6" (#1754).
No environment variables were removed or renamed. ENTRA_LOGIN_BASE_URL is not new: it existed for the auth-server in 1.30.0, and this release wires it through to the registry container and the registry chart.
New Environment Variables
19 additions, all with safe defaults. A deployment that sets none of them behaves as 1.30.0 did, apart from the breaking changes above.
| Variable | Default | Description |
|---|---|---|
| CIMD_PUBLISHER_ENABLED | false | Master switch for publishing the registry's own Client ID Metadata Document at GET /oauth/client-metadata.json. Ships dark.
|
| CIMD_CLIENT_NAME | AI Registry Tools | client_name in the published document.
|
| CIMD_REDIRECT_URIS | (unset) | Redirect URIs to publish. |
| CIMD_SCOPE | (unset) | Scopes to publish. |
| CIMD_CONTACTS | (unset) | Contact addresses to publish. |
| CIMD_LOGO_URI | (unset) | Logo URI to publish. |
| CIMD_CACHE_TTL | 3600 | Cache-Control: max-age on the published document.
|
| EGRESS_HTTP_POOL_MAX_CONNECTIONS | 100 | Max total connections per shared egress client pool. |
| EGRESS_HTTP_POOL_MAX_KEEPALIVE | 20 | Max idle keepalive connections kept warm. |
| EGRESS_HTTP_POOL_KEEPALIVE_EXPIRY_SECONDS | 30 | Seconds an idle keepalive connection survives. |
| EGRESS_HTTP_POOL_CONNECT_RETRIES | 1 | Connection-establishment retries, applied only to idempotent hops. |
| VECTOR_SEARCH_EF_SEARCH | 1000 | HNSW search-queue size. Larger trades latency for recall. |
| VECTOR_SEARCH_OVERREQUEST | 20 | Multiplier on k so post-$search filters have candidates left to keep.
|
| EMBEDDINGS_ENCODE_CONCURRENCY | 2 | Semaphore bound on concurrent model.encode() calls. Torch already parallelises inside one encode, so an unbounded executor makes every concurrent search slower.
|
| SEARCH_LOG_QUERY_TEXT | false | Opt-in to logging the raw user search query. Off by default; thirteen log lines used to write it verbatim, one next to the username. |
| SECURITY_ALLOW_UNSAFE_SERVERS | false | Opt-in to per-tool blocking. When false, a failed scan disables the whole server as before. |
| OPENBAO_ROLE_ID | (unset) | AppRole role ID, now forwarded to the registry container on Compose. |
| OPENBAO_SECRET_ID | (unset) | AppRole secret ID (secret), now forwarded to the registry container on Compose. |
| PF_REGISTRY_ADMIN_PASSWORD | (unset, required) | PingFederate registry admin password (secret). No fallback; bootstrap fails closed without it. |
13 of these are also available as Terraform variables in terraform/aws-ecs/variables.tf.
Upgrade Instructions
Docker Compose
cd mcp-gateway-registry
git pull origin main
git checkout 1.31.0
# Review new env vars in .env.example and update your .env if needed.
# IMPORTANT: the three DEDUP_* variables now actually reach the container.
# Check .env for a stale DEDUP_SCORE_THRESHOLD before starting, or unset it
# to take the per-model default.
./build_and_run.shKubernetes / Helm (EKS)
cd mcp-gateway-registry
git pull origin main
git checkout 1.31.0
# REQUIRED: 24 files changed under charts/ in this release, and the
# mongodb-configure image tag moved. The packaged subchart .tgz files are
# gitignored and only repackage when you run these, so a plain
# `helm upgrade` would deploy the OLD subcharts.
cd charts/mcp-gateway-registry-stack
helm dependency build
helm dependency update
# Update values.yaml if needed, then upgrade:
helm upgrade mcp-gateway . -f your-values.yamlTerraform / ECS
cd mcp-gateway-registry
git pull origin main
git checkout 1.31.0
# 13 new variables are available in terraform/aws-ecs/variables.tf, all with
# safe defaults. Set them in your .tfvars only if you are enabling the CIMD
# publisher, tuning the egress HTTP pool, or opting into per-tool blocking.
cd terraform/aws-ecs
terraform plan
terraform applyMajor Features
Easier onboarding for an MCP server that demands OAuth before it answers
Onboarding a server that returns 401 on initialize meant storing a static bearer token on the record, which then served health checks, tool discovery and security scanning alike, with no way to mark it discovery-only. Those are all headless calls with no user behind them, so the registry had nothing else to present.
with_bearer(server_info) is now the single entry point, resolving at most one credential across three tiers. They are dispatched rather than chained, so "which credential won" stays answerable:
auth_scheme
| Condition | Credential |
|---|---|---|
oauth
| the server's own backend_oauth config
| client_credentials, works with the egress feature off
|
none
| oauth_discovery.enabled
| a per-user OAuth 2.1 identity, consented once by an owner or admin and borrowed from the vault |
none
| egress_auth_mode: obo_exchange
| the gateway's app-only token, for servers whose runtime flow re-audiences a live user's JWT and so has no subject_token for a headless call
|
Tier 1 is terminal. If a machine grant is configured and its token endpoint has a transient error, the answer is "no credential", never "try a different principal", because falling through would quietly substitute a borrowed human token for a machine one. Tiers 2 and 3 bow out unless auth_scheme is none, so an explicitly stored static token is never shadowed.
Tier 2 is the novel part: a real person consents once and the registry borrows that person's vaulted token for its own background calls. That means a user's runtime credential and the identity the registry borrows share a principal, a provider and a server, so purpose became part of the vault address, and the separation is cryptographic rather than path-based. CredentialCodec._aad binds purpose into the AEAD associated data, so a ciphertext relocated between the egress and discovery addresses fails authentication in both directions. This closes an attack the codec's threat model already contemplated: a write-capable backend attacker copying a user's egress ciphertext to the discovery address so the registry borrows that user's credential. The segment is appended only for non-egress purposes, which keeps every pre-existing envelope byte-identical, so nothing needs migrating and nobody re-consents.
A borrowed identity lists tools and never calls one. That holds structurally rather than by convention: tools/call never enters the registry's Python at all, because nginx proxies it and forwards the caller's own Authorization header. Tests enforce it instead of a comment.
Provider support differs by tier, and this is the part to read before enabling it. Tier 2 borrows a credential from the egress vault, so it works with whatever OAuth provider the backend server uses, consented through the normal egress flow. Tier 3 mints a machine token against the gateway's own IdP, and AUTH_PROVIDER=entra is the only value implemented: every other value, including the shipped default cognito, plus keycloak, okta, auth0 and pingfederate, resolves nothing and the server reports unhealthy. So tier 3 needs Entra plus a manual app registration, and nothing in a default deployment reaches it.
Keycloak is refused outright on tier 3 rather than partially working, which is the more interesting decision. Two things are missing and only one is visible: the token request carries the gateway's client secret so it goes through the HTTPS-only credentialed profile, which refuses the in-cluster plain-HTTP KEYCLOAK_URL every shipped deployment uses. The dangerous one is that Keycloak binds the audience through a server-side mapper rather than a request scope, and neither the charts nor the realm bootstrap create that mapper. An operator who fixed only the HTTPS problem would mint a token audienced to whatever Keycloak defaults to, not target_audience, and send it to a third-party MCP server as a bearer. Nothing downstream re-checks the audience, so that failure would be silent and the token real.
Verified end to end against GitHub's hosted MCP server, a genuine OAuth 2.1 target that 401s on initialize and advertises resource_metadata: converting a server from a static bearer to a borrowed identity discovered 45 tools with no static credential stored.
Documentation: Authentication · OBO token exchange
A flagged tool is blocked on its own, instead of disabling the whole server
A security scan that flagged one tool disabled the entire server, so a single false positive cost every other tool on it. Per-tool blocking makes the unit of enforcement the tool.
Set SECURITY_ALLOW_UNSAFE_SERVERS=true and a failed scan blocks the flagged tools and leaves the server enabled. Leave it false and the previous behaviour stands. The gate is opt-in because silently keeping a scanned-unsafe server enabled is not a safe default.
Three properties are worth knowing:
- A block precedes admin and wildcard grants. No scope, including an admin one, reaches a blocked tool.
- Blocks reconcile on rescan, not only at registration. Without this, a tool blocked by a false positive stays blocked forever, because the only thing that could clear it never ran again.
- Blocked tools are hidden from every read projection, so an agent cannot discover a compromised tool, try to call it, and be refused. Read projections fail open by design and
tools/callstill fails closed, so a projection bug cannot become an authorization bypass.
An operator-facing annotation shows blocked tools where a human needs to see them, with a per-tool toggle in the UI and a bearer API sibling at POST /api/servers/toggle-tool/{path}.
Known limitation: a server with every tool blocked stays enabled and serves nothing.
PR #1719 · PR #1779 · PR #1782 (closes #1623)
Search: honest scores, and 641ms down to 86ms
Two independent problems on the same surface.
Ranking. relevance_score came back as exactly 1.0 for a dozen or more tools in one result, so an agent picking the best two had nothing to rank on. A graded scorer had existed since May, but the MongoDB $map that built matching_tools stamped a literal 1.0 over it on two of four paths. DocumentDB supports $search: {vectorSearch: ...} and MongoDB CE does not, so DocumentDB hit the flat score on every query while a local Compose stack ran the correct Python scorer and looked fine, which is why this survived four months. The pipeline now selects candidates and Python grades them, so one scorer runs on every path and both backends.
Latency. search_registry spiked to ~1.4s against a typical 130-300ms, from three causes:
| Before | After | |
|---|---|---|
| Vector + keyword stages | ~628ms | 76ms |
| Total in-app | 641ms | 86ms |
model.encode() was synchronous and CPU-bound inside an async def, so every other request on the worker waited, including health checks and auth. It runs on a bounded thread now: a heartbeat coroutine's worst tick fell from 50.1ms to 11.0ms while ten encodes ran.
Five sequential vector queries became one. They were redundant: $search selects the nearest k documents before any $match runs, so $search (k=30) then $match entity_type=skill does not return the top 30 skills, it returns whichever of the global top 30 happen to be skills. Four pipelines at k=30 produced 30 candidates in total, never 30 each. The keyword query no longer waits for the vector query either: the gap between them collapsed to 0.4-1.7ms from 32-1042ms.
A third defect surfaced while measuring the second: because k is spent before filtering and nothing refilled what the filters discarded, the vector stage was returning 0-2 candidates out of a possible 120 on ECS. VECTOR_SEARCH_OVERREQUEST and VECTOR_SEARCH_EF_SEARCH now give the filters something to keep.
PR #1755 · PR #1769 (closes #1752, #1751)
Pooled HTTP clients on the egress hot path
Every egress call built a fresh httpx client, so each one paid a TCP and TLS handshake. Process-lifetime pooled clients replace them, with four tuning knobs.
The security property this rests on is that pooling cannot bypass the SSRF guard. _pin_request_async rewrites the connect host to the validated IP before pool checkout, so httpx keys the pool on the pinned IP rather than the hostname. Sharing is therefore rebind-safe: a second request to the same hostname cannot reuse a connection opened to a different address. Verified with two probes, four requests sharing one connection for a 61% improvement on the warm path, and the guard still refusing metadata, loopback and RFC-1918 addresses on the shared client.
One caveat recorded in the code: on connection reuse there is no handshake, so nothing re-checks that the certificate covers the second hostname. Cert scope is checked only for the hostname that opens the connection.
The registry publishes its own Client ID Metadata Document
GET /oauth/client-metadata.json serves the registry's CIMD, so an authorization server can identify the registry as an OAuth client by URL instead of a pre-registered client ID. HTTPS is enforced on the published client_id and one builder is pinned so the value cannot diverge between callers. Ships disabled behind CIMD_PUBLISHER_ENABLED=false.
The auth-path traffic mix, per deployment and across the fleet
Every /validate call already recorded which of twelve auth paths it took, and three things stopped that from being usable. A blank label dropped rows from sum by (method). Only the operator of a single deployment could see the mix. And nothing said what a path costs.
All three are fixed. The telemetry heartbeat now carries auth_path_share_24h, auth_path_volume_bucket_24h and auth_path_window_hours, as shares rather than counts: raw per-path counts bound to a stable registry_id would turn an entity-count payload into a traffic meter, which is a new disclosure class. Largest-remainder rounding keeps the values summing to exactly 100, and all three go None together at zero volume. docs/OBSERVABILITY.md gains a cited per-path cost table, the queries, and a flush-health alert.
Two fixes ride along. The window reset became one conditional update_one whose filter is the concurrency control, which also repairs a pre-existing lost-update race on semantic_search_ctr where a find_one then $set could zero another writer's $inc. And mcpgw_registry_auth_path_flush_total makes a silently failing flush distinguishable from no traffic at all.
What's New
Security
- Harden the IdP bootstrap against default, weak and unset credentials, and stop granting the Keycloak web client wildcard CORS origins or a wildcard post-logout allowlist (#1763)
- Enforce per-caller tool visibility on
server.jsonexport and virtual-server tool listing (#1762) - Forward only allowlisted headers to the external registration gate (#1761)
- Enforce the OBO egress target-audience and scope allowlist on the live write and vend paths, where it previously ran on a model validator nothing constructed (#1760)
- Enforce the skill discovery gate on single-skill read and catalog surfaces (#1759)
- Close two data exposures on the audit path and make degradation visible. Both predate #1756: audit records were already propagating to the application log stream, and
X-Internal-Tokenwas absent from the forwarded-header filters so the plain-MCP path leaked it (#1758) - Mask the readable
usernameon the operator audit stream, which holds the same UPN as the already-redactedprincipal_nameon an Entra v1.0 token (#1775) - Keep the security-scan credential out of
argv, where/proc/<pid>/cmdlineis world-readable, by re-attaching the flag inside the child from its environment (#1781) - Take the raw user search query out of thirteen log lines, behind an opt-in flag (#1755)
- Apply per-tool security blocks to every read projection, so a blocked tool cannot be discovered and attempted (#1782)
Authentication and Authorization
- OAuth 2.1 discovery identity and machine discovery for backend-authenticated MCP servers (#1781)
- Record an identifiable user for Entra OBO calls in the audit log, instead of the opaque
sub(#1756) - Narrow the audit identity filter to fields a stream can populate, and harden claim coercion so a blank claim or a signed
anonymouscannot name a caller (#1774) - Publish a standalone Client ID Metadata Document (#1711)
Search and Discovery
- Grade keyword-matched tools instead of scoring every one 1.0, and surface the raw cosine as
similarity_score(#1755) - Offload embedding to a bounded thread and replace the per-type vector loop with one query (#1769)
- Stop
search_registrydiscarding virtual servers it had already scored againstmax_results(#1755) - Strip catalog boilerplate before embedding the duplicate-check query, refuse a query left with fewer than two distinct tokens, and report
similarity_search_available: falsewhen hits carry no similarity at all (#1744) - Filter duplicate advisories on cosine similarity rather than display rank, so the top hit of any search no longer clears the threshold by construction (#1721)
Performance
- Pool shared HTTP clients on the egress hot path (#1718)
- One conditional
update_onefor the metrics window reset, repairing a lost-update race onsemantic_search_ctr(#1754)
Observability
- Report the auth-path traffic mix per deployment and across the fleet, with a per-path cost table and a flush-health alert (#1754)
- Coerce the
methodlabel at emission so a blank value stops dropping rows fromsum by (method)(#1754)
Infrastructure
- Run the MongoDB init script from the image, and stop it destroying data (#1757)
- Stop indexing claim fields the audit API no longer searches, retiring four superseded index names (#1757)
- Expose
alb.ingress.kubernetes.io/ssl-policyandinboundCidrsas configurable chart values (#1720) - Forward
OPENBAO_ROLE_IDandOPENBAO_SECRET_IDto the registry on Compose, which madeOPENBAO_AUTH_METHOD=approleunusable there (#1772) - Wire
ENTRA_LOGIN_BASE_URLthrough to the registry container and chart for sovereign-cloud and air-gapped deployments (#1781)
API and CLI
- Per-tool toggle at
POST /api/servers/toggle-tool/{path}, with CLI and client wiring (#1782) GET/PUT/api/servers/{path}/oauth-configandGET/PUT/DELETE/api/servers/{path}/oauth-discovery, all owner-or-admin (#1781)
Documentation
- Refresh
api/openapi.jsonfor 1.31.0, catching up the CIMD path and three audit paths that had gone stale (#1776) - Make the
openapi.jsonrefresh a checked step rather than a remembered one: aCLAUDE.mdprocedure with its four traps, plus apr-reviewgate that blocks a PR adding a route without refreshing the spec (#1777) - State the scope limits, supported identity providers and operational constraints of backend discovery, and document the two-pass UI consent flow (#1781)
- Document per-tool blocking across the four documents that describe that area (#1782)
- Add the 54-second intro overview video to the demo video index, and refresh the published deck with the 1.30.0 any-resource slide (#1768)
Bug Fixes
- Honour
purposein the Secrets Manager read-repair, so a repair cannot rewrite a credential to the wrong address space (#1781) - Revoke the borrowed discovery credential when a server is deleted or its designation is cleared, so a stored designation never becomes unremovable (#1781)
- Stop
search_registrytripping the credential-harvesting YARA rule on its own description (#1782) - Apply the per-tool block on rescan, not only at registration, so a false positive can be cleared (#1779)
- Fail closed when a failed scan cannot block, rather than leaving a server enabled (#1779)
- Add the missing
entra_login_base_urlentry toCONFIG_GROUPS, the only one of eightentra_*settings absent from the System Config UI (#1781) - Validate the vector tuning knobs and leave the HNSW queue headroom (#1769)
- Stop a blank claim and a signed
anonymousfrom naming a caller in the audit log (#1774) - Log the all-boilerplate duplicate-check skip, and repair the agent example configs (#1744)
- Give
keycloak/setup/init-keycloak.shthe 409-to-PUT client upsert the other provisioners already had, so realm changes reach an existing realm instead of reporting success and doing nothing (#1763) - Send
sslRequiredonly when creating the Helm realm, so an operator who hardened it toexternalis not reverted on everyhelm upgrade(#1763) - Unbreak the discovery boundary tests, which walked into
auth_server/.venvand failed on any developer machine while passing in CI, where.venvis gitignored (#1781) - Fix an unquoted
CIMD_CLIENT_NAMEin.env.examplethat brokebuild_and_run.shviasource .env, and add a CI test for that whole class (#1711)
Closed Issues
| Issue | Title | Closed By |
|---|---|---|
| #1773 | Reuse long-lived HTTPX AsyncClient instances on gateway hot paths to enable connection pooling | PR #1718 |
| #1771 | OPENBAO_AUTH_METHOD=approle is unusable in Docker Compose: OPENBAO_ROLE_ID / OPENBAO_SECRET_ID never reach the registry
| PR #1772 |
| #1767 | docs: host the 70-second intro video for the AI Registry presentation deck | PR #1768 |
| #1753 | feat(observability): report the auth-path traffic mix per deployment and across the fleet | PR #1754 |
| #1752 | search_registry returns relevance_score: 1.0 for many/all keyword-matched tools instead of a graded score
| PR #1755 |
| #1751 | search_registry latency spikes up to ~1.4s under concurrent load due to blocking embedding calls and sequential queries
| PR #1769 |
| #1696 | Duplicate-check advisory produces false positives on names sharing a common catalog suffix (e.g. "-mcp-server") | PR #1744 |
| #1661 | Expose alb.ingress.kubernetes.io/ssl-policy as a configurable value
| PR #1720 |
| #1642 | Audit Log displays Entra sub instead of identifiable user for OBO-authenticated MCP calls
| PR #1756 |
| #1623 | Feature request: Per-tool blocking and enable/disable toggles | PR #1719 |
| #1576 | macos-setup skill Phase 15 cannot register the Cloudflare server: token filename never created | manual |
| #992 | [Phase 4] Publish CIMD (Client ID Metadata Document) for AI Registry Tools MCP client | PR #1711 |
| #930 | Valid group-based admin scopes can still result in empty UI permissions | manual |
| #536 | Add auto-generated Configuration Reference endpoint and UI tab | manual |
| #321 | Local MCP for Databases - credentials | manual |
| #168 | Implement Credentials Manager Container for Automated Token Management | manual |
Pull Requests Included
| PR | Title |
|---|---|
| #1782 | fix(security): apply per-tool blocks to every read projection |
| #1781 | feat(auth): OAuth 2.1 discovery identity and machine discovery for backend-authenticated MCP servers |
| #1780 | chore(deps): weekly lockfile update (2026-09-21) |
| #1779 | fix(security): connect the per-tool auto-block to the scan path |
| #1778 | chore(deps): bump the actions group in /.github/workflows with 4 updates |
| #1777 | docs: make the openapi.json refresh a checked step, not a remembered one |
| #1776 | docs(api): refresh openapi.json for 1.31.0 |
| #1775 | fix(audit): reduce the readable identity on the operator audit stream too |
| #1774 | fix(audit): narrow the identity filter to fields a stream can populate, and harden claim coercion |
| #1772 | fix(compose): forward OPENBAO_ROLE_ID and OPENBAO_SECRET_ID to the registry |
| #1769 | fix(search): offload embedding and replace the per-type vector loop with one query |
| #1768 | docs: add the 54-second intro overview video to the demo video index |
| #1763 | Harden IdP bootstrap against default credentials |
| #1762 | Enforce per-caller tool visibility on server.json export and virtual-server tool listing |
| #1761 | Forward only allowlisted headers to the external registration gate |
| #1760 | fix: enforce OBO egress target-audience/scope allowlist on live paths |
| #1759 | Enforce the skill discovery gate on single-skill read and catalog surfaces |
| #1758 | fix(audit): close two exposures on the audit path, and make degradation visible |
| #1757 | fix(mongodb): run the init script from the image |
| #1756 | fix(audit): record an identifiable user for Entra OBO calls |
| #1755 | fix(search): grade keyword-matched tools instead of scoring every one 1.0 |
| #1754 | feat(observability): report the auth-path traffic mix per deployment and across the fleet |
| #1750 | chore(deps): weekly lockfile update (2026-09-14) |
| #1749 | chore(deps): bump the actions group in /.github/workflows with 3 updates |
| #1744 | fix(dedup): strip catalog boilerplate before embedding, and report degraded similarity |
| #1743 | chore: update image tags to 1.30.0 |
| #1721 | fix(dedup): filter duplicate advisories on cosine similarity, not display rank |
| #1720 | fix(charts): ingress ssl policy |
| #1719 | feat(security): per-tool blocking for unsafe tools (#1623) |
| #1718 | feat(egress): pool shared HTTP clients on the egress hot path |
| #1711 | feat(cimd): standalone Client ID Metadata Document publisher |
Security Dependency Updates
| Package | Previous | Updated | Scope |
|---|---|---|---|
| astral-sh/setup-uv | v10.0.1 | v10.1.0 | CI workflows (#1749, #1778) |
| codecov/codecov-action | v7.0.0 | v7.1.0 | CI workflows (#1749, #1778) |
| docker/setup-qemu-action | v4.2.0 | v4.3.0 | CI workflows (#1778) |
| actions/deploy-pages | v5.0.0 | v5.0.1 | docs publishing workflow (#1778) |
| Python and frontend lockfiles | see PRs | see PRs | weekly automated refresh (#1750, #1780) |
Contributors
Thank you to everyone who landed work in this release:
- Amit Arora (@aarora79)
- omrishiv (@omrishiv)
- Vrinda Bhandari (@vrindabhandari)
- Ehsan Barkhordar (@ebarkhordar)
- Evolver-sweden (@Evolver-sweden)
Support
Full Changelog: 1.30.0...1.31.0