fix(security): harden public error boundaries - #12506
Merged
diegosouzapw merged 9 commits intoSep 4, 2026
Merged
Conversation
…3851-public-error-boundaries # Conflicts: # open-sse/executors/theoldllm.ts
…3851-public-error-boundaries
…attern #12620 had that it lacked #12620 landed first and fixed GHSA-qv45-56jc-4wmj by adding RAW_CREDENTIAL_PATTERNS to error.ts and importing them from upstreamErrorPassthrough.ts. This branch fixes the same class differently, by splitting error.ts into errorSanitization.ts + errorPathRedaction.ts. Kept this branch's split. Its STRONG_CREDENTIAL_TOKEN already covered sk-/sk_ (with a lookbehind and an embedded-prefix variant that catches sk-proj-…), plus Slack xox-, AWS AKIA/ASIA, github_pat_/ghp_/glpat- and three-segment JWTs — a strictly wider vocabulary than the three patterns #12620 introduced. The one shape #12620 carried and this set did not is Google's AIza…, now added here with the same bounded quantifier its siblings use. Verified against #12620's own suites rather than by inspection: 48/48 across error-sanitizer-sk-key-qv45, bifrost-relay-response-leak-9m72, search-baseurl-client-override-3f8g and search-baseurl-ssrf-guard — including that suite's anti-drift assertion (for every body the passthrough layer refuses as leaky, the fallback sanitizer must not return it unchanged). This branch's own 21 test files: 259/259. typecheck:core clean.
diegosouzapw
marked this pull request as ready for review
September 4, 2026 00:31
linhdmn
added a commit
to linhdmn/omniroute-INITIAL_PASSWORD-fix
that referenced
this pull request
Sep 4, 2026
…om release/v3.8.51 All 15 failing tests across shards 1-4 reproduce on pristine base c41ec7f; this drains them at the source so every PR stops inheriting them. Sanitizer pipeline (regressions introduced by 2265ce7's path-span rewrite): - errorPathRedaction: credential assignments are now redacted BEFORE path spans resolve (errorSanitization runs an early redactLabeledCredentialAssignments pass in sanitizeErrorMessageWithStackPolicy), and the span scanner treats sanitizer output as prose boundaries — 'label=[REDACTED]', bare '[REDACTED]', and 'label:' introducers (CREDENTIAL_LABEL_BOUNDARY mirrors BLOCKED_KEYS) stop the fail-closed span instead of being swallowed with it. 'with' joins CLEAR_PROSE_BOUNDARIES. Fixes: error-sanitizer-sk-key-qv45 (AIza 33-char fixture), huggingchat transport + stream boundaries, stream-handler public boundary, dashboard request-failed redaction, tunnel canary. - credentialPatterns: google pattern /AIza[0-9A-Za-z_-]{35}/ → {20,} to match the GHSA-qv45-56jc-4wmj test oracle (the qv45 fixture key is 33 chars after AIza; the {35} form missed it and redactSensitiveErrorText returned it verbatim). - error.ts SAFE_PUBLIC_ERROR_IDENTIFIERS: + huggingchat_generation_error, zai_stream_error — both were passing through projectPublicErrorIdentifier as raw provider codes before diegosouzapw#12506 added the bounded vocabulary; without them huggingchat-stream-error-boundary and zai-web-silent-empty-repro see 'bad_gateway' instead of the pinned codes. - imageGeneration.saveImageErrorResult: String(error) throws 'Cannot convert object to primitive value' on the null-prototype objects sanitizeUpstreamDetails returns — use JSON.stringify. Fixes 3 codex image-generation tests. - streamFailureBoundary: new keepReadable option; emitTranslatedFailureAndAbort (parsed.error mid-stream path) uses it so the forwarded response.failed frame is not discarded when the readable errors — restores the kiro response.failed contract from diegosouzapw#12454/diegosouzapw#12455 that 2265ce7 broke. - chat.ts image-model rejection hint: 'Use POST /v1/images/generations instead.' → 'Then POST …' — 'Use' is not a prose boundary, so the fail-closed span swallowed the endpoint and the diegosouzapw#6457 assertion never matched. - sync-models route: parseError messages gain a 'GET' route-context marker ('Invalid JSON response from GET /models') so the path redactor's route shield preserves the endpoint (test updated to the new strings). - tunnel-routes canary: updated for the sanitizer's intentional growth — quoted-path and windows-shape leaks are now covered by the shared sanitizer; the canary pins the new coverage map per its own instructions.
linhdmn
added a commit
to linhdmn/omniroute-INITIAL_PASSWORD-fix
that referenced
this pull request
Sep 4, 2026
…sthrough failure contract stream-passthrough-error-redaction and stream-utils pin that a mid-stream translated failure must TERMINATE the readable (reader.read() rejects after the forwarded failure frame is consumed). The keepReadable option from dabdad7 made the readable end cleanly instead, flipping result.error to null — 4 CI failures in shard 3. Reverts both files to the 9d3340f (diegosouzapw#12506) semantics, which pass the full cluster: stream-passthrough 9/9, stream-utils 52/52, hardening fixture 23/23, huggingchat boundaries, kiro (CI Node 24).
guanbear
added a commit
to guanbear/OmniRoute
that referenced
this pull request
Sep 7, 2026
…zapw#12506 (base-red) diegosouzapw#12179 added StreamOptions.highWaterMark plus trailing optional highWaterMark parameters on createSSETransformStreamWithLogger and createPassthroughStreamWithLogger, and glm.ts passes the 64KB buffer as that argument. diegosouzapw#12506 rewrote stream.ts against a pre-diegosouzapw#12179 version and dropped the plumbing while glm.ts kept the 16-arg call, so the api-typecheck gate fails on release/v3.8.51 (TS2554, baseline 0) and GLM streams fall back to the default 16KB buffer. This restores the exact hunks from e2e330a (diegosouzapw#12179); defaults are unchanged. api-typecheck returns to 289 baselined errors.
guanbear
added a commit
to guanbear/OmniRoute
that referenced
this pull request
Sep 7, 2026
…zapw#12506 (base-red) diegosouzapw#12179 added StreamOptions.highWaterMark plus trailing optional highWaterMark parameters on createSSETransformStreamWithLogger and createPassthroughStreamWithLogger, and glm.ts passes the 64KB buffer as that argument. diegosouzapw#12506 rewrote stream.ts against a pre-diegosouzapw#12179 version and dropped the plumbing while glm.ts kept the 16-arg call, so the api-typecheck gate fails on release/v3.8.51 (TS2554, baseline 0) and GLM streams fall back to the default 16KB buffer. This restores the exact hunks from e2e330a (diegosouzapw#12179); defaults are unchanged. api-typecheck returns to 289 baselined errors.
This was referenced Sep 7, 2026
diegosouzapw
added a commit
that referenced
this pull request
Sep 8, 2026
…12964) Vazamento de credencial em corpo de erro. `tests/unit/error-sanitizer-sk-key-qv45.test.ts` falhava no tip em 8ms: ``` AssertionError: Google key survived: Bad credentials for AIzaSyA1B2C3D4E5F6G7H8I9J0KaLbMcNdOeP ``` O padrão era `/AIza[0-9A-Za-z_-]{35}/` — comprimento **exato**. Uma chave Google padrão tem 39 caracteres e casa; qualquer credencial `AIza…` mais curta ou mais longa passava direto para o corpo do erro. Os dois lados divergiram na reconciliação de dois PRs do mesmo GHSA: o padrão com `{35}` veio do #12506, o teste anti-drift que cobra `/\\bAIza[A-Za-z0-9_-]{20,}/` veio do #12620. Está vermelho desde que os dois entraram em sequência. `{20,}` no lugar de `{35}`. Numa mensagem de erro, redigir demais uma string que apenas começa com `AIza` não custa nada; redigir de menos vaza credencial — o lado errado para errar é claro. Evidência: o arquivo vai de 7/9 para **9/9**. Bateria de sanitização com 538 testes: 533 passam, e as 5 restantes são pré-existentes no tip, não desta mudança (4 levam 21–25s por spawn de processo isolado sob carga; `tunnel-routes-error-sanitization` falha igual no tip puro, verificado). Nenhum teste foi enfraquecido — o padrão foi ampliado para satisfazer uma asserção que já existia.
guanbear
added a commit
to guanbear/OmniRoute
that referenced
this pull request
Sep 9, 2026
…zapw#12506 (base-red) diegosouzapw#12179 added StreamOptions.highWaterMark plus trailing optional highWaterMark parameters on createSSETransformStreamWithLogger and createPassthroughStreamWithLogger, and glm.ts passes the 64KB buffer as that argument. diegosouzapw#12506 rewrote stream.ts against a pre-diegosouzapw#12179 version and dropped the plumbing while glm.ts kept the 16-arg call, so the api-typecheck gate fails on release/v3.8.51 (TS2554, baseline 0) and GLM streams fall back to the default 16KB buffer. This restores the exact hunks from e2e330a (diegosouzapw#12179); defaults are unchanged. api-typecheck returns to 289 baselined errors.
guanbear
added a commit
to guanbear/OmniRoute
that referenced
this pull request
Sep 9, 2026
…zapw#12506 (base-red) diegosouzapw#12179 added StreamOptions.highWaterMark plus trailing optional highWaterMark parameters on createSSETransformStreamWithLogger and createPassthroughStreamWithLogger, and glm.ts passes the 64KB buffer as that argument. diegosouzapw#12506 rewrote stream.ts against a pre-diegosouzapw#12179 version and dropped the plumbing while glm.ts kept the 16-arg call, so the api-typecheck gate fails on release/v3.8.51 (TS2554, baseline 0) and GLM streams fall back to the default 16KB buffer. This restores the exact hunks from e2e330a (diegosouzapw#12179); defaults are unchanged. api-typecheck returns to 289 baselined errors.
diegosouzapw
added a commit
that referenced
this pull request
Sep 10, 2026
… redaction from eating credential markers Three defects the #12506 hardening left behind, each red on the base tip: - SAFE_PUBLIC_ERROR_IDENTIFIERS listed 250 codes while the code emits 49 more (huggingchat_generation_error, the uc_* family, zai_stream_error, provider_circuit_open, model_lockout, heap_pressure, unsupported_media_type, upgrade_required, …). Every one of them was silently rewritten to the generic status-derived code, so clients lost the specific reason. Each added code was verified against its emitting call site. - The path redactor swallowed the credential marker an earlier pass had already written: 'TLS request failed at /srv/…/client.ts:44:9 access_token=[REDACTED]' collapsed to a bare '<path>', hiding which credential leaked. The span now ends at a redaction marker. - Labeled credential assignments (api_key=…, access_token=…) are projected before the path tokenizer for the same reason raw URI credentials already were, otherwise the path span absorbs the assignment before it can be marked. The tunnel characterization test now states, per leak shape, what the shared sanitizer covers: the four path-shaped leaks are covered upstream now, the tailscale auth key is not — which is exactly why publicSafeTunnelError stays. The public-body assertions are untouched.
diegosouzapw
added a commit
that referenced
this pull request
Sep 11, 2026
…ll logs sanitizeUpstreamDetails() builds every object with Object.create(null) on purpose (#12506) so a hostile upstream key such as `__proto__` can never reach a real prototype. saveImageErrorResult() then handed that object to a bare String(), which throws "TypeError: Cannot convert object to primitive value" — so every Codex image failure came out as an unhandled crash instead of the sanitized error the caller was supposed to get. The null prototype is the correct behavior at the source, so the sink is what has to be total: `error` is typed `unknown`, and it now serializes objects structurally (the same way the Antigravity branch already logs its sanitized payload) and keeps String() semantics for everything else. The sanitized payload returned to the caller is untouched, so the redaction guarantees are unchanged. Covered by the three existing regression tests in tests/unit/image-generation-handler.test.ts (codex sanitizes upstream HTTP errors / model-access 400 retryable / ordinary 400 not retryable).
LMPrado-DZ23
pushed a commit
to LMPrado-DZ23/OmniRoute
that referenced
this pull request
Sep 12, 2026
…r sanitizer CI shard 3/4 (tests/unit/error-sanitizer-sk-key-qv45.test.ts, 2 tests): the public-boundary hardening on the fork lineage (2265ce7, diegosouzapw#12506) had narrowed the shared Google credential pattern to exactly 35 characters, so a 'Bad credentials for AIza…' upstream message with a 39-character key reached clients unredacted while the passthrough layer still refused it. The pattern is bounded again (20-200 characters) so both layers agree. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
LMPrado-DZ23
pushed a commit
to LMPrado-DZ23/OmniRoute
that referenced
this pull request
Sep 12, 2026
…ver the raw secret CI shard 3/4 (dashboard-request-failed-redaction): the probe still expected the persisted call log to keep the raw provider diagnostic (path + api key) while only the delivered request.failed event was redacted (diegosouzapw#12469 policy). sanitizeErrorForLog runs before persistence since diegosouzapw#12506, and that is the stronger contract this mission keeps (a leaked key must not be recoverable from the database either), so the probe now asserts the stored error equals the delivered sanitized text and contains neither the secret nor the path (the only 'sk-' literal in the diff is the pre-existing fake fixture key). No production code changed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
7 tasks done
diegosouzapw
added a commit
that referenced
this pull request
Sep 17, 2026
Merge the current release tip into the security hardening branch and reconcile 57 conflicting files. Most of this PR's original scope landed on the tip while it sat: #12506/#12945/ #13635 shipped a stricter public error boundary (allowlist-based `isSafePublicErrorIdentifier`, `errorSanitization.ts`, `errorPathRedaction.ts`, `upstreamErrorResponse.ts`), #12429 migrated the web-cookie TLS transport to wreq-js, and #11754 retired the common ChatGPT Web executor. Those parts are resolved to the tip, which is strictly more restrictive in every case, and the now-dead tls-client provenance stream is dropped (the tip's `tls-client-wreq-residue` guard forbids reintroducing `tls-client-node`). What survives is the part the tip does not cover: - chatCore reads rejection metadata through `getSafeErrorMetadata`, wraps `isLocalStreamLifecycleError`/`formatProviderError` so a hostile Proxy cannot escape the boundary, sanitizes the failure message before call logs and console, projects the failure-usage code through the bounded vocabulary, and sanitizes the upstream body before it reaches the persisted attempt logs. - Perplexity's non-streaming quota/upstream error body sanitizes the upstream message and projects the provider-supplied code (`toPublicPerplexityErrorCode`). - Arena (lmarena) maps every public failure onto a fixed vocabulary instead of echoing upstream text; `lmarena_stream_error` is registered in the public identifier allowlist. - Notion keeps the tip's fail-closed transport (no plain-fetch proxy bypass) and sanitizes the transport error before the response body. - `chatgptWebTools` returns a non-ok buffered response untouched. Tests for superseded behaviour are dropped; the surviving contributions keep their tests, split into `lmarena-public-error-boundary-11742` and `perplexity-web-public-error-boundary-11742` to stay under the test size cap. Prunes three now-stale eslint suppression entries: notion-web.ts no longer has an unused var after this merge, and the two chipotle entries were left behind by the tip's own provider removal (#13913).
diegosouzapw
added a commit
that referenced
this pull request
Sep 18, 2026
) (#14039) The property test asserted an absolute 250ms ceiling on sanitizeErrorMessage() for adversarial inputs. That ceiling had no margin over the pipeline's real fixed cost (3x redact + 2x normalize passes added by #12506), so it failed on cost under any machine load, not on backtracking. Replace it with a check that the sanitizer's cost does not scale with input length beyond a generous noise allowance, which is what a bounded-backtracking guarantee actually claims; keep a coarse absolute hang ceiling as a backstop.
diegosouzapw
added a commit
that referenced
this pull request
Sep 18, 2026
… budget card The 57 commits merged since the previous validation moved two things. #13295 changed how an unknown-root path with an ambiguous tail is answered: where `Provider failed at /custom/internal secret directory` used to become `Provider failed at <path>` it now ships verbatim. The #12506 boundary guard caught it. Two candidate fixes were tried and each breaks one of the two live contracts — #12506's fail-closed swallow, or #13144's rule that a route in prose must survive — so the choice is the owner's (#14110). The one contested assertion is suspended inline with the exact line and the issue; the other nine stay active. The isolated-child harness requires tests == pass, which is why it is a comment and not a todo. The free-tier budget card was one wave behind again (491 -> 489 models). Refs #13866, #14110
diegosouzapw
added a commit
that referenced
this pull request
Sep 18, 2026
…the write, drain the 09-18 base-reds (#14101) * fix(quality): drain the 09-18 base-reds, part 1 — thinking gate parity, inventory, webpack externals Reproduced on the clean tip 7cc454d before touching anything. Five of the failures trace to one commit, #12905 (b7192b7): it gated thinking-block emission on `requestedThinking === true` in the streaming translator, while its own non-streaming path documents `undefined` as the legacy caller shape that keeps "always a thinking block". The two paths disagreed on the same input, and the streaming side also synthesized the reasoning into a TEXT block for that legacy shape. chatCore always resolves a boolean, so production never sends `undefined` — but every direct caller and the older #5786 suites do. Aligned the streaming gate to the documented tri-state: `false` suppresses, `true` and `undefined` relay, and the fix-B text synthesis fires only on an explicit opt-out. The #12905 test that asserted suppression used a bare createState() (`undefined`) to mean "client did not request thinking"; it now passes `requestedThinking: false`, which is what that sentence resolves to in production. The whole thinking family — dsml, adapter, translator, non-stream parity, #13620, #5786, markdown boundary — is 77/77. #12864 added requestRejectedFailure.ts with a getProviderConnectionById read that seeds the refusal streak across restarts; inventoried as a connection state read next to the family-cooldown site it resembles. #13909 made machineToken.ts import ./dataPaths; the isolated webpack compile has no repo tree, so it joins the sibling externals. The free-tier budget card SVG was one wave behind again (482 -> 491 models). Refs #13866 * fix(sse): restore maxQueueDepth=0 as unbounded, sanitize refusals at the write, drain the rest Part 2 of the 09-18 base-red drain. Two of the remaining failures were not stale tests but production defects the tests had caught. #12911 taught accountSemaphore to read `maxQueueSize: 0` as "reject when the slot is busy", which is what its Codex WS lease wants. But chatCore forwards `resilienceSettings.requestQueue.maxQueueDepth` into that option, and that setting's documented default since #6593 is `0 = disabled`. Under default settings every request that found its account slot occupied was answered 429 "Semaphore queue full (0)" instead of waiting — the managed-lease routing test saw exactly that. `0` (and any non-positive value) is unbounded again; the lease gets an explicit `failFast` option and its four tests stay green, so the #12911 behaviour is preserved where it was meant to apply. A contract test pins the #6593 semantics on the semaphore itself. #12864 moved two providerFailure persistence branches out of chatCore into requestRejectedFailure.ts and the sanitization did not travel with them: three `lastError` writes stored the message as received. The only caller already hands in the projected persistentMessage, so nothing leaks today, but a persistence branch must be safe at its own write (docs/security/ ERROR_SANITIZATION.md) rather than trust whoever calls it. The module now sanitizes on entry, and the public-boundary guard — which caught this by counting sanitized writes in chatCore and coming up two short — covers the extracted module too, verified by mutating one write back to raw. The rest are tests that had fallen behind legitimate changes: - #12905 inserted `requestedThinking` as the 14th positional argument of createSSETransformStreamWithLogger; two tests passed customToolNames or the buffer budget at their old positions. Both production callers were already correct. - #12754 added a per-connection reset-card fetch after the quota fetch; the spacing test now marks a chunk at the quota request only. - #13910 renamed `error` to `errorMetadata` in the timeout classification; the probe matches the identifier with a backreference and still fails when BodyTimeoutError is removed from both sites. Refs #13866 * fix(test): pin the opt-out thinking cases to requestedThinking=false; keep acquireMany under the complexity ceiling The #12905 gate-restore suite encoded 'requestedThinking absent' as opt-out, the same undefined-means-false shape its non-streaming twin documents the other way and that the two-month-old #5786 suites contradict. The three opt-out cases now set the flag explicitly, which is what chatCore resolves for an opted-out client; the two opt-in cases already did. Both suites pass together (27/27). The failFast branch pushed acquireMany over the complexity ceiling it already sat on; the admission policy (fail-fast / bounded / unbounded queue) moves to findQueueRejection() and the new-code ratchet is back at its base. Refs #13866 * fix(test): suspend the #14110 redaction assertion inline; refresh the budget card The 57 commits merged since the previous validation moved two things. #13295 changed how an unknown-root path with an ambiguous tail is answered: where `Provider failed at /custom/internal secret directory` used to become `Provider failed at <path>` it now ships verbatim. The #12506 boundary guard caught it. Two candidate fixes were tried and each breaks one of the two live contracts — #12506's fail-closed swallow, or #13144's rule that a route in prose must survive — so the choice is the owner's (#14110). The one contested assertion is suspended inline with the exact line and the issue; the other nine stay active. The isolated-child harness requires tests == pass, which is why it is a comment and not a todo. The free-tier budget card was one wave behind again (491 -> 489 models). Refs #13866, #14110 * fix(providers): type the TinyCMS DOM stub global as a loose record #13957 typed the mock global as `typeof globalThis & Record<string, unknown>`. The api-route typecheck loads lib.dom, so that intersection carries the real Window / HTMLCanvasElement / document signatures — every stub assignment fails against a DOM constructor, and `delete g.window` narrows the object to `never` (13 diagnostics, the API Route Typecheck base-red on the tip). The function exists to overwrite those globals with stubs; it is now typed as the plain record it manipulates. 29/29 tinycms tests unchanged. Refs #13866 * fix(test): pin the last opt-out thinking sibling to requestedThinking=false translator-reasoning-gate-502-repro is the third #12905 test that encoded a bare state as opt-out; the previous sweep matched files by glob and missed it. The family is now enumerated by grep on requestedThinking (7 files) plus the two pre-#12905 suites: 83/83 together. Refs #13866
JJAbrams-eng
added a commit
to JJAbrams-eng/OmniRoute
that referenced
this pull request
Sep 22, 2026
Combining diegosouzapw#12454 (adds zai_stream_error) with the error-boundary hardening commits (diegosouzapw#12506/diegosouzapw#12620) left the new code missing from SAFE_PUBLIC_ERROR_IDENTIFIERS, so buildErrorBody() silently downgraded it to the generic bad_gateway fallback.
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
Validado sobre o tip de `release/v3.8.51` depois de reconciliar com o diegosouzapw#12620, que entrou primeiro nesta mesma sessão e ataca a mesma classe de problema por outra arquitetura. **A colisão e como foi resolvida.** O diegosouzapw#12620 consertou o GHSA-qv45-56jc-4wmj adicionando `RAW_CREDENTIAL_PATTERNS` a `error.ts` e importando-os em `upstreamErrorPassthrough.ts`. Este PR resolve o mesmo problema quebrando `error.ts` em `errorSanitization.ts` + `errorPathRedaction.ts`. Mantive a divisão em módulos deste PR, porque ao comparar os dois vocabulários o dele já era mais amplo: o `STRONG_CREDENTIAL_TOKEN` daqui cobre `sk-`/`sk_` **com lookbehind e uma variante para a forma embutida** (que pega `sk-proj-…`), mais Slack `xox-`, AWS `AKIA`/`ASIA`, `github_pat_`/`ghp_`/`glpat-` e JWT de três segmentos. A única forma que o diegosouzapw#12620 carregava e este conjunto não tinha era a chave do Google (`AIza…`) — adicionada aqui, com o mesmo quantificador limitado que os irmãos usam (AGENTS.md → PII §1, já que isso roda sobre corpos upstream não confiáveis). **A verificação não foi por inspeção.** Rodei as suítes do próprio diegosouzapw#12620 contra esta estrutura: **48/48** em `error-sanitizer-sk-key-qv45`, `bifrost-relay-response-leak-9m72`, `search-baseurl-client-override-3f8g` e `search-baseurl-ssrf-guard` — incluindo a asserção anti-drift daquela suíte, que é o oráculo certo aqui: *para todo corpo que a camada de passthrough recusa como vazante, o sanitizador de fallback não pode devolvê-lo intacto*. Ela passa, então a propriedade de segurança dos três GHSAs sobrevive à troca de arquitetura. Os 21 arquivos de teste deste PR: **259/259**. `typecheck:core` limpo.
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…iegosouzapw#12964) Vazamento de credencial em corpo de erro. `tests/unit/error-sanitizer-sk-key-qv45.test.ts` falhava no tip em 8ms: ``` AssertionError: Google key survived: Bad credentials for AIzaSyA1B2C3D4E5F6G7H8I9J0KaLbMcNdOeP ``` O padrão era `/AIza[0-9A-Za-z_-]{35}/` — comprimento **exato**. Uma chave Google padrão tem 39 caracteres e casa; qualquer credencial `AIza…` mais curta ou mais longa passava direto para o corpo do erro. Os dois lados divergiram na reconciliação de dois PRs do mesmo GHSA: o padrão com `{35}` veio do diegosouzapw#12506, o teste anti-drift que cobra `/\\bAIza[A-Za-z0-9_-]{20,}/` veio do diegosouzapw#12620. Está vermelho desde que os dois entraram em sequência. `{20,}` no lugar de `{35}`. Numa mensagem de erro, redigir demais uma string que apenas começa com `AIza` não custa nada; redigir de menos vaza credencial — o lado errado para errar é claro. Evidência: o arquivo vai de 7/9 para **9/9**. Bateria de sanitização com 538 testes: 533 passam, e as 5 restantes são pré-existentes no tip, não desta mudança (4 levam 21–25s por spawn de processo isolado sob carga; `tunnel-routes-error-sanitization` falha igual no tip puro, verificado). Nenhum teste foi enfraquecido — o padrão foi ampliado para satisfazer uma asserção que já existia.
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…gosouzapw#13907) (diegosouzapw#14039) The property test asserted an absolute 250ms ceiling on sanitizeErrorMessage() for adversarial inputs. That ceiling had no margin over the pipeline's real fixed cost (3x redact + 2x normalize passes added by diegosouzapw#12506), so it failed on cost under any machine load, not on backtracking. Replace it with a check that the sanitizer's cost does not scale with input length beyond a generous noise allowance, which is what a bounded-backtracking guarantee actually claims; keep a coarse absolute hang ceiling as a backstop.
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…the write, drain the 09-18 base-reds (diegosouzapw#14101) * fix(quality): drain the 09-18 base-reds, part 1 — thinking gate parity, inventory, webpack externals Reproduced on the clean tip 7784784 before touching anything. Five of the failures trace to one commit, diegosouzapw#12905 (8b5f4dd): it gated thinking-block emission on `requestedThinking === true` in the streaming translator, while its own non-streaming path documents `undefined` as the legacy caller shape that keeps "always a thinking block". The two paths disagreed on the same input, and the streaming side also synthesized the reasoning into a TEXT block for that legacy shape. chatCore always resolves a boolean, so production never sends `undefined` — but every direct caller and the older diegosouzapw#5786 suites do. Aligned the streaming gate to the documented tri-state: `false` suppresses, `true` and `undefined` relay, and the fix-B text synthesis fires only on an explicit opt-out. The diegosouzapw#12905 test that asserted suppression used a bare createState() (`undefined`) to mean "client did not request thinking"; it now passes `requestedThinking: false`, which is what that sentence resolves to in production. The whole thinking family — dsml, adapter, translator, non-stream parity, diegosouzapw#13620, diegosouzapw#5786, markdown boundary — is 77/77. diegosouzapw#12864 added requestRejectedFailure.ts with a getProviderConnectionById read that seeds the refusal streak across restarts; inventoried as a connection state read next to the family-cooldown site it resembles. diegosouzapw#13909 made machineToken.ts import ./dataPaths; the isolated webpack compile has no repo tree, so it joins the sibling externals. The free-tier budget card SVG was one wave behind again (482 -> 491 models). Refs diegosouzapw#13866 * fix(sse): restore maxQueueDepth=0 as unbounded, sanitize refusals at the write, drain the rest Part 2 of the 09-18 base-red drain. Two of the remaining failures were not stale tests but production defects the tests had caught. diegosouzapw#12911 taught accountSemaphore to read `maxQueueSize: 0` as "reject when the slot is busy", which is what its Codex WS lease wants. But chatCore forwards `resilienceSettings.requestQueue.maxQueueDepth` into that option, and that setting's documented default since diegosouzapw#6593 is `0 = disabled`. Under default settings every request that found its account slot occupied was answered 429 "Semaphore queue full (0)" instead of waiting — the managed-lease routing test saw exactly that. `0` (and any non-positive value) is unbounded again; the lease gets an explicit `failFast` option and its four tests stay green, so the diegosouzapw#12911 behaviour is preserved where it was meant to apply. A contract test pins the diegosouzapw#6593 semantics on the semaphore itself. diegosouzapw#12864 moved two providerFailure persistence branches out of chatCore into requestRejectedFailure.ts and the sanitization did not travel with them: three `lastError` writes stored the message as received. The only caller already hands in the projected persistentMessage, so nothing leaks today, but a persistence branch must be safe at its own write (docs/security/ ERROR_SANITIZATION.md) rather than trust whoever calls it. The module now sanitizes on entry, and the public-boundary guard — which caught this by counting sanitized writes in chatCore and coming up two short — covers the extracted module too, verified by mutating one write back to raw. The rest are tests that had fallen behind legitimate changes: - diegosouzapw#12905 inserted `requestedThinking` as the 14th positional argument of createSSETransformStreamWithLogger; two tests passed customToolNames or the buffer budget at their old positions. Both production callers were already correct. - diegosouzapw#12754 added a per-connection reset-card fetch after the quota fetch; the spacing test now marks a chunk at the quota request only. - diegosouzapw#13910 renamed `error` to `errorMetadata` in the timeout classification; the probe matches the identifier with a backreference and still fails when BodyTimeoutError is removed from both sites. Refs diegosouzapw#13866 * fix(test): pin the opt-out thinking cases to requestedThinking=false; keep acquireMany under the complexity ceiling The diegosouzapw#12905 gate-restore suite encoded 'requestedThinking absent' as opt-out, the same undefined-means-false shape its non-streaming twin documents the other way and that the two-month-old diegosouzapw#5786 suites contradict. The three opt-out cases now set the flag explicitly, which is what chatCore resolves for an opted-out client; the two opt-in cases already did. Both suites pass together (27/27). The failFast branch pushed acquireMany over the complexity ceiling it already sat on; the admission policy (fail-fast / bounded / unbounded queue) moves to findQueueRejection() and the new-code ratchet is back at its base. Refs diegosouzapw#13866 * fix(test): suspend the diegosouzapw#14110 redaction assertion inline; refresh the budget card The 57 commits merged since the previous validation moved two things. diegosouzapw#13295 changed how an unknown-root path with an ambiguous tail is answered: where `Provider failed at /custom/internal secret directory` used to become `Provider failed at <path>` it now ships verbatim. The diegosouzapw#12506 boundary guard caught it. Two candidate fixes were tried and each breaks one of the two live contracts — diegosouzapw#12506's fail-closed swallow, or diegosouzapw#13144's rule that a route in prose must survive — so the choice is the owner's (diegosouzapw#14110). The one contested assertion is suspended inline with the exact line and the issue; the other nine stay active. The isolated-child harness requires tests == pass, which is why it is a comment and not a todo. The free-tier budget card was one wave behind again (491 -> 489 models). Refs diegosouzapw#13866, diegosouzapw#14110 * fix(providers): type the TinyCMS DOM stub global as a loose record diegosouzapw#13957 typed the mock global as `typeof globalThis & Record<string, unknown>`. The api-route typecheck loads lib.dom, so that intersection carries the real Window / HTMLCanvasElement / document signatures — every stub assignment fails against a DOM constructor, and `delete g.window` narrows the object to `never` (13 diagnostics, the API Route Typecheck base-red on the tip). The function exists to overwrite those globals with stubs; it is now typed as the plain record it manipulates. 29/29 tinycms tests unchanged. Refs diegosouzapw#13866 * fix(test): pin the last opt-out thinking sibling to requestedThinking=false translator-reasoning-gate-502-repro is the third diegosouzapw#12905 test that encoded a bare state as opt-out; the previous sweep matched files by glob and missed it. The family is now enumerated by grep on requestedThinking (7 files) plus the two pre-diegosouzapw#12905 suites: 83/83 together. Refs diegosouzapw#13866
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Hardens OmniRoute's public and persistent error boundaries so upstream stack traces,
credentials, absolute paths, provider diagnostics, hidden reasoning, tool arguments, and
commentary do not escape through HTTP, SSE, MCP, provider-validation, health-state, proxy-log,
call-log, or request-detail surfaces.
This candidate is self-contained. It does not depend on any other open pull request.
What changes
Error.message, upstream bodies, or nested detailsbuildErrorBody()/sanitizeErrorMessage()with narrow allowlistsstream-passthrough-error-redaction, Gemini Responses, and upstream passthrough suitesresponse.outputcould retain reasoning, encrypted content, tool calls, annotations, commentary, or unknown diagnosticsoutput_text/refusal; upstream annotations are discarded and the required schema field is rebuilt as trustedannotations: []Security invariants
encrypted replay state, tool arguments, diagnostics, or unknown siblings.
TDD receipts
response.failed.outputevent: response.failedoutput_textschema-invalidannotations: []is emittedFocused final receipts on
373b183ddbca476f609195058d4723a643ef8adb:tests/unit/request-log-payloads.test.ts: PASS 26/26tests/unit/stream-passthrough-error-redaction.test.ts: PASS 9/9Validation status
release/v3.8.51@bf0d902dfc5369bd025f64808d96fe6cb473ea75conflict-free. The GitHub base then advanced externally toe243b04de22da2d78900c27fde5f83f4fbc3d7f9; GitHub reports the candidateCLEANandMERGEABLEagainst that tipgit diff --checksecretFindings=0on the final candidate@eloqnt/*packages withUNKNOWNmetadata. No allowlist, exception, dependency, or license decision was changedcheck:docs-allexited 0; link, fabricated-docs, count, and environment checks passed. It reported 86 non-blocking stale-documentation hintsDATA_DIRA shared-process diagnostic invocation is not treated as green because this repository has known
global DB/Skills state contamination; the canonical changed-test receipt ran every file in its own
process and temporary data directory. Likewise, the initial broad Vitest invocation ran amid many
parallel worktrees and produced timeouts; every affected file passed when repeated in isolation.
Reconciliation and scope
bf0d902dfc5369bd025f64808d96fe6cb473ea75.373b183ddbca476f609195058d4723a643ef8adb.e243b04de22da2d78900c27fde5f83f4fbc3d7f9;the PR was
CLEAN/MERGEABLE. Later release commits are intentionally not chased forever.chatCore.reintroduced.
Out of scope / follow-ups