Files
OmniRoute/tests
Xiangzhe 2423ce3b35 fix(security): namespace the dedup hash by the calling API key
Follow-up to GHSA-6c7w-56xp-wpc6. `deduplicate()` shares ONE upstream call —
and therefore one response object — between every concurrent caller landing on
the same hash. `computeRequestHash()` canonicalized model, prompt, system and
sampling params: what was being asked, but never who was asking.

So two distinct OmniRoute API keys issuing the same request joined the same
in-flight promise. The response was produced with the initiator's provider
connection, under the initiator's per-key policy (allowedConnections /
allowedModels / disableNonPublicModels), billed to the initiator, and handed to
a different authenticated principal. Dedup is an optimization; it must not be a
hole in the auth boundary.

#10438 fixed the *prompt* half of this class — translated bodies whose prompt
lives under `input`/`contents` hashed as `null`, so different prompts collided.
This is the *identity* half, which that fix did not touch.

The tenant id is a PLAINTEXT prefix rather than digest input, matching
semanticCache.generateSignature (#3740): it is an internal namespace key, not a
credential, and keeping it out of the digest avoids a false-positive CodeQL
js/insufficient-password-hash on a dedup key.

Omitting it keeps the un-namespaced hash: keyless local-first deployments have
no tenant boundary to preserve and would otherwise silently lose dedup. That is
pinned by a test, not left to chance.

tests/unit/request-dedup-tenant-isolation.test.ts — 6 tests, 5 red before the
fix: two keys do not share a hash, the same key still dedups, two concurrent
identical requests each get their own response, the anonymous hash is unchanged,
the namespace prefix cannot be forged from the body, and chatCore actually
passes the key id through.
2026-08-26 11:45:02 -03:00
..