Keys with non-empty allowedQuotas may only use models whose provider belongs
to their pools' provider set; anything outside → 403 QUOTA_ONLY.
Normal allowedModels/allowedCombos checks are bypassed for quota-exclusive keys.
Introduces src/lib/quota/quotaKey.ts with resolveQuotaKeyScope(), a
pure async helper that maps an API key's allowedQuotas pool-ID list to
the concrete connectionIds and provider slugs it is permitted to use.
Covers empty/null/undefined input, missing pools, orphaned connectionIds,
and multi-pool deduplication. No behaviour change to existing code paths.
- Move !enforceScopes guard before MCP_TOOL_MAP lookup in evaluateToolScopes()
- Add inlineScopes parameter for dynamic tool scope resolution
- Add scopes to all 33 dynamic tool definitions across 5 tool files
- Wire toolDef.scopes through withScopeEnforcement in server.ts
- Preserves existing behavior: tool_definition_missing returned when
enforceScopes=true and no scopes found anywhere
The agy provider was registered in providerRegistry.ts with
executor: "antigravity" but the executor map in executors/index.ts
only had an "antigravity" entry. getExecutor("agy") fell through to
DefaultExecutor, which returned undefined for baseUrl (agy only has
baseUrls), causing fetch(undefined) → TypeError: Cannot read properties
of undefined (reading 'toString').
Closesdiegosouzapw/OmniRoute#2932
The authz pipeline runs in the Next middleware runtime (proxy.ts -> runAuthzPipeline)
where ctx.request is a NextRequest with no .socket/.ip. requestPeerAddress therefore
returned null, so isLoopbackRequest was ALWAYS false and every LOCAL_ONLY path 403'd
even from loopback (Services/MCP/Traffic-Inspector were unusable). Read the Host
header instead — exactly what isLoopbackHost/isPrivateLanHost were built to parse —
which restores loopback and, combined with isPrivateLanHost, enables the
owner-authorized private-LAN access. Spawn-capable endpoints still require
manage-scope auth after this gate.
Services + Traffic-Inspector (LOCAL_ONLY, spawn-capable) returned 403 when the
dashboard was reached via the LAN IP (192.168.0.x) instead of loopback. Add
isPrivateLanHost (RFC1918 IPv4 + IPv6 ULA/link-local) and widen ONLY the
local-only PATH gate to accept private-LAN socket peer IPs — based on the real
socket peer address (not the spoofable Host header), so public-internet clients
present public IPs and stay blocked. The CLI-token gate stays strictly loopback;
paths remain LOCAL_ONLY-classified (Hard Rules 15/17 unchanged). Enforcement-layer
carve-out for a LAN-deployed instance, authorized by the operator.
The pool usage snapshot can come back without a dimensions array (e.g. when the
plan resolves to empty for catalog-only providers). PoolCard.computeStatus and
hasDimensions read usage.dimensions.length directly, crashing the whole page
("Cannot read properties of undefined (reading 'length')"). Normalize to [] in
PoolCard and in usePoolsUsageAggregate (dimensions/perKey).
5 new test files covering all 13 changed production files:
- estimateSizeFast.test.ts: 16 tests for fast size estimator (circular ref
protection, early exit, nested structures, Map safety)
- eviction-guards-apiKeyRotator.test.ts: 5 tests for Map eviction guards
(!has() check prevents evicting existing keys on update)
- eviction-guards-codexQuotaFetcher.test.ts: 4 tests for connectionRegistry
and quotaCache eviction guards
- rateLimitManager-idle-eviction.test.ts: 6 tests for idle limiter cleanup,
limiterLastUsed tracking, and shutdown behavior
- registry-direct-exports.test.ts: 20 tests verifying all 8 registries export
plain objects (no Proxy traps, no lazy getters, mutable entries)
Extract estimateSizeFast/isSmallEnoughForSemanticCache into standalone
open-sse/utils/estimateSize.ts to make them testable without importing
the entire chatCore.ts dependency tree.
- estimateSizeFast: add WeakSet cycle detection to prevent infinite
loop on circular object references
- trace(): wrap JSON.stringify(extra) in try-catch to handle BigInt,
circular refs, or other non-serializable values gracefully
- Registry API change (Comment 3): verified all callers already use
new getter functions — no broken call sites
- Add !has(key) guard before eviction to avoid evicting entries
that are about to be updated (combo.ts, apiKeyRotator.ts,
codexQuotaFetcher.ts)
- Use optional chaining for provider?.toUpperCase() null safety
- Replace Object.values() with for-in in estimateSizeFast hot path
- Capture full search results (title/url/snippet) per provider, not just URLs.
- Render one column per selected provider: metrics header + result list
(title link, snippet, url), horizontal scroll for N providers.
- Mark results whose URL appears across providers with a star (overlap).
- Remove the 4-provider cap (MAX_PROVIDERS); add Select all / Clear; compare
every configured provider. Raise max_results 5 -> 10.