check:file-size flagged `open-sse/handlers/search.ts: 1824 > frozen 1789`. The
growth was mine — the GHSA-3f8g fix added the trust split plus the reasoning for
it to a file that was already at its ceiling.
Shrinking beats loosening the ratchet, and the gate's own message says so
("modularize/extraia (DRY) para encolher"), so the resolver moves to
`open-sse/handlers/search/baseUrl.ts` with its refusal error and the
single-source reader. search.ts drops 1824 -> 1772, under the ceiling, and the
base-URL trust decision — which source is operator, which is caller, which
providers may be redirected — is now one small unit instead of a passage inside
a 1800-line handler. search.ts re-exports both symbols, so every existing
importer is untouched.
Also drops the `parseAndValidateNonMetadataUrl` import that the extraction left
orphaned in search.ts; eslint on that file is back to exactly its 34 frozen
violations.
The remaining file-size failures (`open-sse/executors/codex.ts` 1503 > 1499,
`open-sse/utils/stream.ts` 3078 > 3072) are base debt in files this branch does
not touch.
search + baseUrl suites 52/52, typecheck:core exit 0, check:api-typecheck OK
(289, within baseline), check:cycles OK, check:mutation-test-coverage no drift.
Correcting my own over-tightening in this branch. The previous commit refused
EVERY caller-supplied `provider_options.baseUrl`, which broke a documented,
tested flow: `tests/unit/search-route.test.ts` covers "v1 search POST accepts
authless SearXNG with provider_options baseUrl". SearXNG is self-hosted by
definition and the caller naming their own instance is the supported way to use
it — I read the field as purely hostile when part of its use is legitimate.
This is the gate the reporter actually recommended and I went past: allow the
caller override only for a provider that is BOTH keyless and explicitly opted in
via the new `allowClientBaseUrlOverride` flag. Today that is `searxng-search`
alone. Every `authType: "apikey"` provider still refuses, which is the half that
matters — those are the ones whose builder attaches the operator's key to
whatever host the base URL resolves to.
The flag is checked alongside `authType === "apikey"` at the callsite AND pinned
by a registry-wide invariant in the test: no provider may carry
`allowClientBaseUrlOverride` while holding a key. Inferring the capability from
which builder happens to call the resolver is what let this drift in the first
place, so it is now declared, not inferred.
The opted-in path keeps the block-metadata guard: loopback/LAN is the entire
point of a self-hosted instance, IMDS is not.
search-route 8/8, the 3f8g + j7j4 suites 33/33, sibling sweep 68/68,
typecheck:core exit 0, check:api-typecheck OK (289, within baseline).
Also registers error-sanitizer-sk-key-qv45 in stryker tap.testFiles —
check:mutation-test-coverage flagged it once error.ts entered the mutated set.
`check:public-creds` has been failing on every open PR against release/v3.8.51,
twice over for the same literal:
✗ 1 entrada(s) obsoleta(s) na allowlist — zcodeProtocol.ts:302
✗ 1 credencial(is) pública(s) como string literal — zcodeProtocol.ts L313
Both are the same `clientId: \`omniroute-${process.pid}\`` in the local ZCode
handshake. Nothing regressed: the allowlist key is `file:line:value`, so an edit
that shifted the statement from 302 to 313 invalidated the frozen key and the
gate reported the entry as stale AND the literal as new.
Re-pointed the key and its comment. The literal itself is unchanged and still
frozen — the entry is not removed and the detector is not weakened (the gate's
own test still asserts that renaming the value to `upstream-client-` is flagged).
`tests/unit/check-public-creds.test.ts` synthesizes the source with a newline
count to land the statement on the allowlisted line; that count moves with it,
302 -> 313, so the test keeps pinning the real contract instead of a stale one.
Also documented the sharp edge inline: keying by line number means any edit near
this statement breaks the gate in two places at once, and the fix is to re-point
the line, never to drop the entry. Tightening the key to `file:value` would
remove the trap but widens what the entry freezes, so it is left as a note
rather than folded into a base-red drain.
check:public-creds OK (3 frozen literals), check-public-creds tests 20/20,
check:tracked-artifacts OK, prettier clean.
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51` (já com a leva anterior dentro): os nove boardaram **sem um único conflito**, `typecheck:core` limpo e **80/80** nos 6 arquivos de teste que os PRs trazem.
O crescimento de arquivo próprio da leva foi rebaselinado num registro datado (`_rebaseline_2026_09_03_hartmark_batch`): `combos/page.tsx` 5012→5018 (#12355, tratar o estado degradado quando o bundling de tiktoken de um provider sem relação falha) e `open-sse/services/combo.ts` 4023→4036 (#12338, os fixes do universal-handoff). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
Obrigado, @hartmark.
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51` (já com a leva anterior dentro): os nove boardaram **sem um único conflito**, `typecheck:core` limpo e **80/80** nos 6 arquivos de teste que os PRs trazem.
O crescimento de arquivo próprio da leva foi rebaselinado num registro datado (`_rebaseline_2026_09_03_hartmark_batch`): `combos/page.tsx` 5012→5018 (#12355, tratar o estado degradado quando o bundling de tiktoken de um provider sem relação falha) e `open-sse/services/combo.ts` 4023→4036 (#12338, os fixes do universal-handoff). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
Obrigado, @hartmark.
The previous commit added `buildErrorBody`/`sanitizeErrorMessage` and pulled
`parseUpstreamError` from `handlers/usageExtractor`, where it does not live —
it is exported from `utils/error.ts` alongside the other two. That produced
TS2300 (duplicate `buildErrorBody`, already imported below) and TS2305 on the
same route, caught by check:api-typecheck.
One import from utils/error.ts for all three. check:api-typecheck now reports
OK — 289 pre-existing errors, all within the frozen baseline (was FAIL with 2
new/regressed). Relay tests 6/6, prettier clean.
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51` (já com a leva anterior dentro): os nove boardaram **sem um único conflito**, `typecheck:core` limpo e **80/80** nos 6 arquivos de teste que os PRs trazem.
O crescimento de arquivo próprio da leva foi rebaselinado num registro datado (`_rebaseline_2026_09_03_hartmark_batch`): `combos/page.tsx` 5012→5018 (#12355, tratar o estado degradado quando o bundling de tiktoken de um provider sem relação falha) e `open-sse/services/combo.ts` 4023→4036 (#12338, os fixes do universal-handoff). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
Obrigado, @hartmark.
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51` (já com a leva anterior dentro): os nove boardaram **sem um único conflito**, `typecheck:core` limpo e **80/80** nos 6 arquivos de teste que os PRs trazem.
O crescimento de arquivo próprio da leva foi rebaselinado num registro datado (`_rebaseline_2026_09_03_hartmark_batch`): `combos/page.tsx` 5012→5018 (#12355, tratar o estado degradado quando o bundling de tiktoken de um provider sem relação falha) e `open-sse/services/combo.ts` 4023→4036 (#12338, os fixes do universal-handoff). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
Obrigado, @hartmark.
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51` (já com a leva anterior dentro): os nove boardaram **sem um único conflito**, `typecheck:core` limpo e **80/80** nos 6 arquivos de teste que os PRs trazem.
O crescimento de arquivo próprio da leva foi rebaselinado num registro datado (`_rebaseline_2026_09_03_hartmark_batch`): `combos/page.tsx` 5012→5018 (#12355, tratar o estado degradado quando o bundling de tiktoken de um provider sem relação falha) e `open-sse/services/combo.ts` 4023→4036 (#12338, os fixes do universal-handoff). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
Obrigado, @hartmark.
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51` (já com a leva anterior dentro): os nove boardaram **sem um único conflito**, `typecheck:core` limpo e **80/80** nos 6 arquivos de teste que os PRs trazem.
O crescimento de arquivo próprio da leva foi rebaselinado num registro datado (`_rebaseline_2026_09_03_hartmark_batch`): `combos/page.tsx` 5012→5018 (#12355, tratar o estado degradado quando o bundling de tiktoken de um provider sem relação falha) e `open-sse/services/combo.ts` 4023→4036 (#12338, os fixes do universal-handoff). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
Obrigado, @hartmark.
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51` (já com a leva anterior dentro): os nove boardaram **sem um único conflito**, `typecheck:core` limpo e **80/80** nos 6 arquivos de teste que os PRs trazem.
O crescimento de arquivo próprio da leva foi rebaselinado num registro datado (`_rebaseline_2026_09_03_hartmark_batch`): `combos/page.tsx` 5012→5018 (#12355, tratar o estado degradado quando o bundling de tiktoken de um provider sem relação falha) e `open-sse/services/combo.ts` 4023→4036 (#12338, os fixes do universal-handoff). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
Obrigado, @hartmark.
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51` (já com a leva anterior dentro): os nove boardaram **sem um único conflito**, `typecheck:core` limpo e **80/80** nos 6 arquivos de teste que os PRs trazem.
O crescimento de arquivo próprio da leva foi rebaselinado num registro datado (`_rebaseline_2026_09_03_hartmark_batch`): `combos/page.tsx` 5012→5018 (#12355, tratar o estado degradado quando o bundling de tiktoken de um provider sem relação falha) e `open-sse/services/combo.ts` 4023→4036 (#12338, os fixes do universal-handoff). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
Obrigado, @hartmark.
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51` (já com a leva anterior dentro): os nove boardaram **sem um único conflito**, `typecheck:core` limpo e **80/80** nos 6 arquivos de teste que os PRs trazem.
O crescimento de arquivo próprio da leva foi rebaselinado num registro datado (`_rebaseline_2026_09_03_hartmark_batch`): `combos/page.tsx` 5012→5018 (#12355, tratar o estado degradado quando o bundling de tiktoken de um provider sem relação falha) e `open-sse/services/combo.ts` 4023→4036 (#12338, os fixes do universal-handoff). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
Obrigado, @hartmark.
GHSA-9m72-44hg-w32g: the standalone bifrost relay copied ALL upstream response
headers via `new Headers(upstream.headers)` and returned non-2xx bodies verbatim,
while its TypeScript sibling routed non-2xx through parseUpstreamError +
buildErrorBody + stripStaleEncodingHeaders.
The relay sends `Authorization: Bearer ${BIFROST_API_KEY}` to the sidecar, so
anything the sidecar or a further upstream echoes back — that header, its own
`set-cookie`, an `x-api-key` — reached whoever holds the relay token. `set-cookie`
matters as much as the credential: it is a session, and a browser would store it
against our origin.
Both fixes:
- New shared `stripSensitiveResponseHeaders()` in upstreamResponseHeaders.ts,
built on the `filterUpstreamResponseHeaderEntries` helper that already
existed there unused. Drops authorization / proxy-authorization / x-api-key /
x-goog-api-key / api-key / cookie / set-cookie on top of the stale framing
set. Applied to BOTH relay routes — the TS sibling copied headers wholesale
too, so fixing only the reported one would have left the same hole next door
and a second copy waiting to drift, which is the exact failure mode of
GHSA-v7g9 and GHSA-qv45.
- The bifrost route normalizes non-2xx through parseUpstreamError +
buildErrorBody + sanitizeErrorMessage, reaching parity with the sibling and
Hard Rule #12.
tests/unit/bifrost-relay-response-leak-9m72.test.ts — 6 tests, red before the
fix: the helper's behaviour (including that it does not mutate its input), plus
source guards pinning BOTH routes to the shared strip with a negative assertion
that the wholesale copy has not come back.
Reported by @skeletonsec.
Closes GHSA-9m72-44hg-w32g
GHSA-qv45-56jc-4wmj: two copies of one redaction rule, and only one of them
learned about `sk-`.
`upstreamErrorPassthrough.ts` recognized `\bsk-[…]{8,}` and REFUSED verbatim
passthrough for any upstream 4xx carrying it — correctly treating it as a leak.
The body then fell through to `buildErrorBody` → `sanitizeErrorMessage` →
`redactSensitiveErrorText`, which had no such pattern. So the layer that
identified the credential handed it to a layer that could not see it, and
OpenAI-style `Incorrect API key provided: sk-proj-…` bodies came back to the
caller intact. For a gateway pooling operator keys that is cross-tenant
disclosure of the operator's upstream credential.
The passthrough file's comment said it "mirrors the vocabulary of
redactSensitiveErrorText". The mirror had drifted, which is why this is fixed at
the source rather than by adding a pattern on each side: the raw-token shapes now
live once, in `error.ts` as `RAW_CREDENTIAL_PATTERNS`, and the passthrough layer
imports them. Both sides move together or not at all.
Covers `sk-`/`sk_`, Google `AIza…`, and three-segment JWTs. Quantifiers are
upper-bounded per AGENTS.md → PII learnings §1 — these run over untrusted
upstream error bodies.
tests/unit/error-sanitizer-sk-key-qv45.test.ts — 9 tests, 7 red before the fix.
The last one is the anti-drift assertion: for every body the passthrough layer
refuses as leaky, the fallback sanitizer must not return it unchanged. It also
pins that ordinary prose containing "risk"/"task sk" is left alone.
Reported by @skeletonsec.
Closes GHSA-qv45-56jc-4wmj
GHSA-3f8g-pfh9-j687: a caller with search scope could set
`provider_options.baseUrl` and redirect the SaaS search builders, so the
OPERATOR's third-party API key went to a caller-chosen host — `key=`/`api_key=`
in the query for google-pse/searchapi, `X-API-Key`/`Authorization` for
you.com/linkup/nimble/ollama — and the parsed response came back, making the
same primitive an SSRF with readback.
My own GHSA-j7j4-g9qc-q69c fix is the one this slipped past. That fix added
`parseAndValidateNonMetadataUrl` to this exact resolver, which blocks IMDS and
nothing else: against key exfiltration it does nothing at all, because the
attacker just names their own public host. I treated a credential-destination
problem as an SSRF problem.
The j7j4 regression test missed it for a sharper reason worth recording: its
fixture was `searxng-search`, the one keyless provider where redirecting the base
URL leaks no credential. The test picked the benign case and passed.
The two override sources are not equally trusted, and reading them through one
`getProviderSettingString()` call erased the distinction:
- `providerSpecificData` comes from the stored provider connection
(`credentials?.providerSpecificData`) — operator config. This is how an
operator points at a self-hosted searxng, so it keeps working on
loopback/LAN under block-metadata, and IMDS stays rejected.
- `providerOptions` is `body.provider_options` — caller input. Now refused
outright rather than validated. No provider needs a per-request
caller-chosen fetch target; the self-hosted story is served by the operator
field. If one ever does, it needs a per-provider opt-in plus a host
allow-list, not a validated free-form URL.
Operator config now also wins over a caller value instead of being shadowed by
it.
Behaviour change, deliberate: a caller that today sends
`provider_options.baseUrl` gets an explicit refusal instead of a silently
redirected request. Silently ignoring it would leave the caller believing the
override applied.
tests/unit/search-baseurl-client-override-3f8g.test.ts — 23 tests, red before
the fix. It iterates EVERY keyed provider in the registry instead of one
fixture, and asserts the registry still has keyed providers so the suite cannot
degrade to zero coverage.
search-baseurl-ssrf-guard.test.ts had one assertion encoding the wrong contract
(caller-supplied loopback allowed). Moved to the operator source with the
reasoning inline — the j7j4 metadata cases are untouched.
Reported by @skeletonsec.
Closes GHSA-3f8g-pfh9-j687
Rebaseline medido no tip com os 9 PRs da leva mergeados. Desfaz o vermelho de file-size que os PRs empilhados deixaram; não toca codex.ts nem stream.ts, que já violavam antes da leva.
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51`: `typecheck:core` limpo, `check:provider-consistency` OK (273 entradas REGISTRY, **356** providers canônicos), `check-docs-counts-sync` exit 0 e **300/300** nos testes que a leva toca.
O crescimento de arquivo que os PRs empilham uns sobre os outros foi rebaselinado num único registro datado (`_rebaseline_2026_09_03_houminxi_batch`), com a decomposição por arquivo: `providers/page.tsx` +18 (import CSV do #12504 + busca do #12495 no mesmo painel), `accountFallback.ts` +6 (o #12566 sobre o rebaseline que o #12590 já registrou — os dois tocam `checkFallbackError`) e `chatCore.ts` +3 (invalidez de cache de quota no 429 do #12325). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51`: `typecheck:core` limpo, `check:provider-consistency` OK (273 entradas REGISTRY, **356** providers canônicos), `check-docs-counts-sync` exit 0 e **300/300** nos testes que a leva toca.
O crescimento de arquivo que os PRs empilham uns sobre os outros foi rebaselinado num único registro datado (`_rebaseline_2026_09_03_houminxi_batch`), com a decomposição por arquivo: `providers/page.tsx` +18 (import CSV do #12504 + busca do #12495 no mesmo painel), `accountFallback.ts` +6 (o #12566 sobre o rebaseline que o #12590 já registrou — os dois tocam `checkFallbackError`) e `chatCore.ts` +3 (invalidez de cache de quota no 429 do #12325). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51`: `typecheck:core` limpo, `check:provider-consistency` OK (273 entradas REGISTRY, **356** providers canônicos), `check-docs-counts-sync` exit 0 e **300/300** nos testes que a leva toca.
O crescimento de arquivo que os PRs empilham uns sobre os outros foi rebaselinado num único registro datado (`_rebaseline_2026_09_03_houminxi_batch`), com a decomposição por arquivo: `providers/page.tsx` +18 (import CSV do #12504 + busca do #12495 no mesmo painel), `accountFallback.ts` +6 (o #12566 sobre o rebaseline que o #12590 já registrou — os dois tocam `checkFallbackError`) e `chatCore.ts` +3 (invalidez de cache de quota no 429 do #12325). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51`: `typecheck:core` limpo, `check:provider-consistency` OK (273 entradas REGISTRY, **356** providers canônicos), `check-docs-counts-sync` exit 0 e **300/300** nos testes que a leva toca.
O crescimento de arquivo que os PRs empilham uns sobre os outros foi rebaselinado num único registro datado (`_rebaseline_2026_09_03_houminxi_batch`), com a decomposição por arquivo: `providers/page.tsx` +18 (import CSV do #12504 + busca do #12495 no mesmo painel), `accountFallback.ts` +6 (o #12566 sobre o rebaseline que o #12590 já registrou — os dois tocam `checkFallbackError`) e `chatCore.ts` +3 (invalidez de cache de quota no 429 do #12325). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51`: `typecheck:core` limpo, `check:provider-consistency` OK (273 entradas REGISTRY, **356** providers canônicos), `check-docs-counts-sync` exit 0 e **300/300** nos testes que a leva toca.
O crescimento de arquivo que os PRs empilham uns sobre os outros foi rebaselinado num único registro datado (`_rebaseline_2026_09_03_houminxi_batch`), com a decomposição por arquivo: `providers/page.tsx` +18 (import CSV do #12504 + busca do #12495 no mesmo painel), `accountFallback.ts` +6 (o #12566 sobre o rebaseline que o #12590 já registrou — os dois tocam `checkFallbackError`) e `chatCore.ts` +3 (invalidez de cache de quota no 429 do #12325). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51`: `typecheck:core` limpo, `check:provider-consistency` OK (273 entradas REGISTRY, **356** providers canônicos), `check-docs-counts-sync` exit 0 e **300/300** nos testes que a leva toca.
O crescimento de arquivo que os PRs empilham uns sobre os outros foi rebaselinado num único registro datado (`_rebaseline_2026_09_03_houminxi_batch`), com a decomposição por arquivo: `providers/page.tsx` +18 (import CSV do #12504 + busca do #12495 no mesmo painel), `accountFallback.ts` +6 (o #12566 sobre o rebaseline que o #12590 já registrou — os dois tocam `checkFallbackError`) e `chatCore.ts` +3 (invalidez de cache de quota no 429 do #12325). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51`: `typecheck:core` limpo, `check:provider-consistency` OK (273 entradas REGISTRY, **356** providers canônicos), `check-docs-counts-sync` exit 0 e **300/300** nos testes que a leva toca.
O crescimento de arquivo que os PRs empilham uns sobre os outros foi rebaselinado num único registro datado (`_rebaseline_2026_09_03_houminxi_batch`), com a decomposição por arquivo: `providers/page.tsx` +18 (import CSV do #12504 + busca do #12495 no mesmo painel), `accountFallback.ts` +6 (o #12566 sobre o rebaseline que o #12590 já registrou — os dois tocam `checkFallbackError`) e `chatCore.ts` +3 (invalidez de cache de quota no 429 do #12325). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51`: `typecheck:core` limpo, `check:provider-consistency` OK (273 entradas REGISTRY, **356** providers canônicos), `check-docs-counts-sync` exit 0 e **300/300** nos testes que a leva toca.
O crescimento de arquivo que os PRs empilham uns sobre os outros foi rebaselinado num único registro datado (`_rebaseline_2026_09_03_houminxi_batch`), com a decomposição por arquivo: `providers/page.tsx` +18 (import CSV do #12504 + busca do #12495 no mesmo painel), `accountFallback.ts` +6 (o #12566 sobre o rebaseline que o #12590 já registrou — os dois tocam `checkFallbackError`) e `chatCore.ts` +3 (invalidez de cache de quota no 429 do #12325). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
Validado em lote numa worktree combinada com os 9 PRs desta leva sobre o tip de `release/v3.8.51`: `typecheck:core` limpo, `check:provider-consistency` OK (273 entradas REGISTRY, **356** providers canônicos), `check-docs-counts-sync` exit 0 e **300/300** nos testes que a leva toca.
O crescimento de arquivo que os PRs empilham uns sobre os outros foi rebaselinado num único registro datado (`_rebaseline_2026_09_03_houminxi_batch`), com a decomposição por arquivo: `providers/page.tsx` +18 (import CSV do #12504 + busca do #12495 no mesmo painel), `accountFallback.ts` +6 (o #12566 sobre o rebaseline que o #12590 já registrou — os dois tocam `checkFallbackError`) e `chatCore.ts` +3 (invalidez de cache de quota no 429 do #12325). As violações restantes (`codex.ts`, `stream.ts`) foram medidas também no tip puro e são drift da base, não desta leva.
`source.config.ts` feeds `docs/reference/**/*.md` to fumadocs-mdx, whose default
schema requires a `title`. #12478 added `docs/reference/REMOVED_PROVIDERS.md`
with no frontmatter block at all, so every production build died with:
[MDX] invalid frontmatter in docs/reference/REMOVED_PROVIDERS.md:
- title: Invalid input: expected string, received undefined
That single missing block is what turns three release-green gates red at once —
`Package artifact (npm pack policy)` fails on the build, and both
`Tarball boot-smoke` and the packaged CLI checks are skipped for lack of a
valid `dist/`.
Fixes:
- add the frontmatter block, matching the convention of its sibling reference
docs (`title` / `version` / `lastUpdated`).
- add `check:docs-frontmatter`, wired into `check:docs-all`, so the next doc
added without a title fails in milliseconds instead of costing a full Next
build and a red release branch. The gate reads its globs from
`source.config.ts` rather than duplicating them, so a new docs directory
cannot silently escape the check.
Verified: the gate reports OK across all 122 compiled docs, fails (exit 1) when
the frontmatter is removed, and `npm run check:docs-all` passes.
* feat(video): redact transcript fields in the in-memory pending-request snapshot (#12430 item 6)
trackPendingRequest (open-sse/handlers/chatCore.ts) stored the raw client
body (with video transcript/audioTranscript cues) under `clientRequest`,
live-exposed via /api/usage/call-logs (pendingDetails), /api/logs/[id] and
/api/conversations while a request is in-flight. P2a redacted the persisted
detailed-log snapshot but not this in-memory copy.
Add redactPendingBody() to videoBridgeSnapshotRedaction.ts (sibling to
logClientRawRequestRedacted from P2a): when videoBridgeObserved, returns the
redacted clone from redactVideoTranscriptFieldsForLog; otherwise returns the
exact same reference. Wire it into the trackPendingRequest call site
(chatCore.ts:934), keeping the file within its frozen 5976-line budget
(5971 -> 5974).
* feat(video): substring-redact transcript in derived-prompt dispatch logs (#12430 item 4)
Extend applyVideoBridgeLogRedaction with a string-content branch:
pipeline-strategy stages, smart-auto-pipeline, and context-handoff
summaries embed the transcript as a substring of a rendered prompt
string rather than an exact array part, so the existing exact
part-array match silently skipped them. Adds a mutually-exclusive
string branch (Array.isArray vs typeof === "string") that does a
replaceAll of the trusted fullText literal against a lazily cloned
message, reusing the existing rootClone/clonedContainers/clonedMessages
clone-on-write pattern so siblings keep original references and the
input is never mutated.
Aprovado pelo operador. Adiciona o OmniRouteTray (@zoispag) ao README — app de menu-bar para macOS, rotulado com honestidade como projeto da comunidade e não release oficial. Mudança só de markdown, sem tocar nada executável; inclui também dois ajustes de alinhamento na tabela de contatos. Obrigado, @ggiak.
* fix(authz): hard-gate every credential export and CLI-config write
GHSA-5926-2w35-7h4q: `POST /api/providers/{id}/claude-auth/export` and
`.../codex-auth/export` gate on `requireManagementAuth(request)` with no
`alwaysRequireAuth`, and neither path was in ALWAYS_PROTECTED_API_PATHS. Under
`requireLogin=false` — the local-first default — both fail open, so anyone who
knows a connection id downloads the operator's raw Claude/Codex OAuth
access_token / refresh_token (plus the Codex id_token).
This is the third recurrence of one class. GHSA-mghq-58h3-qcqj added
/api/db-backups; GHSA-v7g9-7f55-5g46 added the /api/settings/*-json siblings
mghq had missed; these two are the siblings both missed. So the fix is written
against the class, not the two reported routes.
Sweeping every route that hands out stored credentials, dumps captured traffic,
or writes the operator's CLI config turned up four more on the fail-open tier:
- GET /api/logs/export — dumps call_logs (prompts and responses) and proxy_logs
for up to 168h.
- /api/cli-tools/codex-profiles — GET leaks the operator's account label; PUT
writes attacker-supplied auth.json and config.toml straight into the host's
Codex CLI config. Its only guard is ensureCliConfigWriteAllowed() with no
targetPath, which checks CLI_ALLOW_CONFIG_WRITES — default true. Paired with
the POST that stores an arbitrary profile, that is: save a profile holding the
attacker's auth.json, apply it, and the operator's CLI now runs on attacker
credentials (or, via config.toml, an attacker base URL).
- {claude,codex}-auth/apply-local and providers/agy-auth/apply-local — write a
stored credential into ~/.codex/auth.json and
~/.gemini/antigravity-cli/antigravity-oauth-token.
The traffic-inspector HAR exports were already covered by LOCAL_ONLY.
Routes with a dynamic segment cannot be expressed in the exact/prefix list — a
`/api/providers/` prefix would hard-gate the whole provider surface and break
every keyless install — so this adds ALWAYS_PROTECTED_API_PATTERNS, mirroring
the existing LOCAL_ONLY_API_PATTERNS, and `isAlwaysProtectedPath` consults both.
The apply-local routes get ALWAYS_PROTECTED rather than LOCAL_ONLY on purpose:
it closes the anonymous hole without breaking an operator driving the dashboard
through a tunnel.
Deliberately NOT adding `{ alwaysRequireAuth: true }` at the handlers. Tier 2 is
the architecture's designated mechanism and the guard runs before the handler; a
second copy of the same decision inside each route is exactly the kind of
duplicate that drifts out of sync (cf. the dashboardCsrf prefix scan that had to
be unified in #11417).
tests/unit/authz/credential-export-always-protected.test.ts — 5 tests, red
before the fix. Written as an inventory of the whole class rather than two more
assertions, plus negative cases: the neighbouring provider routes must stay on
MANAGEMENT, and a connection id containing a slash must not slip past `[^/]+`.
openapi.yaml marks the seven newly-gated operations `x-always-protected`, and
openapi-security-tiers.test.ts now resolves `{param}` placeholders so it can
validate the pattern entries too.
Reported by @skeletonsec.
Closes GHSA-5926-2w35-7h4q
* chore(quality): register the credential-export authz test in stryker tap.testFiles
The new tests/unit/authz/credential-export-always-protected.test.ts covers
src/server/authz/routeGuard.ts, so check:mutation-test-coverage --strict fails
until it is listed — its mutant kills would not count otherwise.
Inserted in place (no re-serialization: a JSON round-trip on this file reorders
~10 curated entries that are already out of alphabetical order, cf. #11438).
Validado numa worktree sobre o tip de `release/v3.8.51`, medindo o gate dos dois lados: **red no tip** (dezenas de rotas `volcengine-plan`/`vnc-session` reportadas como "has x-loopback-only but is NOT covered") e **PASS com este PR**, exit 0.
Como é um gate de segurança, confirmei que o fix torna o checker *preciso* e não *frouxo*. A afirmação central do PR — que uma rota é coberta se casar com um prefixo resolvido **ou** com um pattern — bate exatamente com o runtime (`src/server/authz/routeGuard.ts:252-255`):
```ts
return (
LOCAL_ONLY_API_PREFIXES.some((p) => path === p || path.startsWith(p)) ||
LOCAL_ONLY_API_PATTERNS.some((re) => re.test(path))
);
```
O checker antigo enxergava só o primeiro braço, e nem isso por completo: a captura `[^\]]+` quebrava no `]` dentro de classes de regex, então `LOCAL_ONLY_API_PATTERNS` não era parseado, e `VNC_ROUTE_PREFIX` (const importada, não literal) não era resolvido. Resultado: rotas efetivamente protegidas em runtime apareciam como desprotegidas. Nenhum achado real foi silenciado — as 95 linhas de `WARN — missing x-loopback-only annotation` continuam saindo, são explicitamente não-fatais e pré-existentes.
Fecha um dos HARDs do base-red #12335. Obrigado, @ggiak.
Dependabot alerts #196–#199 — four HIGH advisories on fast-uri
(GHSA-jqff-g426-hqxp, GHSA-fph4-wmhf-6fwf, GHSA-f65p-4m7j-42xc,
GHSA-5jgf-p345-68v8), all patched in 3.1.6.
The root package-lock.json was already on a patched fast-uri (3.1.7) — those
alerts close on their own with the next scan. `electron/package-lock.json` is a
second lockfile and was still pinning 3.1.5, which is what these four alerts are
actually reporting.
Transitive, one copy, pulled by ajv (`^3.0.1`), so a package-lock-only update
lifts it without touching any manifest. The diff is three lines: version,
resolved and integrity for that single entry.
check:lockfile and check:tracked-artifacts pass.
Not fixed here: extract-zip (#191, HIGH, <= 2.0.1) has no published patch. It
comes in through @openai/codex-security and is dev-scope; it needs either an
upstream release or a decision to drop/replace the dependency, neither of which
belongs in a lockfile bump.
Validado numa worktree sobre o tip de `release/v3.8.51`. Verifiquei as premissas em vez de aceitar a justificativa:
**Exceções de licença** — todas as afirmações batem. Os três pacotes estão instalados exatamente nas versões travadas citadas (`@eloqnt/config@0.0.2`, `@eloqnt/format-json@0.0.3`, `@eloqnt/format-po@0.0.3`), os três **omitem `package.json#license` e não têm arquivo LICENSE**, e `npm ls` confirma que são transitivos de `next-intl@4.14.1` (MIT). No registry, o SPDX das três é MIT e o `@eloqnt/config@0.1.0` existe, como o texto diz. Uso de `exceptions` (com `risk`/`reviewAt`) em vez de `allowed: UNKNOWN` é o mecanismo certo.
`check:licenses` verde: **0 violações de política**, 948 permitidos, as três novas entradas aparecendo como exceções sinalizadas não bloqueantes ao lado das duas já existentes. Teste-guarda `tests/unit/build/check-licenses.test.ts`: **36/36**.
**Vitest do A2A lifecycle** — as duas seams usadas já existiam antes deste PR: `constructor(ttlMinutes = 5, persistence: A2APersistence = defaultPersistence)` e o 4º parâmetro opcional `deps?: MemoryHitsDeps` de `executeA2ATaskWithState`. O padrão é idêntico ao que `tests/unit/a2a-task-persistence.test.ts` já fazia. Nenhuma asserção foi removida ou enfraquecida — e como o comportamento de persistência tem suíte dedicada, injetar no-ops aqui tira acoplamento incidental, não cobertura.
Ganho medido nos dois lados: corpo dos testes **784ms no tip → 233ms com o PR**, sem nenhuma linha `[DB] SQLite database ready`. Registro honesto: **no tip o arquivo passa localmente** (4/4) — o red era do CI, sob o thread pool com as 167 migrações; localmente dá para comprovar o mecanismo e a aceleração, não a falha em si.
Suíte vitest completa: **51/51 arquivos, 465/465 testes**.
CI verde: 18 checks passando (4 shards de unit, Vitest, CodeQL, Fast Quality Gates, No new ESLint warnings, semgrep). Local: 8/8 no teste atualizado e 165/165 nos 16 arquivos tests/unit/electron-*.test.ts.
Validado sobre o tip de `release/v3.8.51` após reconciliar quatro arquivos que driftaram. Em parte o tip já tinha absorvido a intenção deste branch por abstrações melhores, então mantive a forma do tip e trouxe os ganhos que ainda eram reais:
- **`sseCollect.ts`** — o tip extraiu `stripObfuscationZeroWidth()` para `utils/zeroWidth.ts`, o que supera o `ZERO_WIDTH_RE` local (removido). A içada de `TEXTUAL_TOOL_CALL_RE` foi mantida: essa regex ainda estava inline num caminho quente.
- **`resultMemo.ts`** — o `memoStore()` do tip devolve o clone armazenado para que o idiom comum `memoStore(k, r); return memoLookup(k)!` evite um segundo deep-clone de vários MB. Esse contrato foi preservado (o branch o revertia para `void`), e o round-trip `JSON.parse(JSON.stringify())` virou `structuredClone()` nas duas pontas — que era o ponto de performance real do branch.
- **`browserPool.ts`** — o tip agora tem caminho headed e o engine Obscura (#12286). Ambos preservados, mais a varredura de TTL do `pendingContexts` deste branch, adaptada ao nome `poolKey` do tip.
- **`executeAttempt.ts`** — mantido o comentário explicativo do tip.
Também corrigi **quatro erros de typecheck que o branch introduzia**: `hasUnsupportedSignal` estava tipado `boolean` mas avaliava para `string | boolean`, e o fast-path de `extractUsage()` indexava `c.response`/`c.message` como `unknown`.
`typecheck:core` limpo e **482/482** nos testes de antigravity + compressão na própria branch. Obrigado, @opensource-elearning.
Validado sobre o tip de release/v3.8.51 após reconciliar o base-drift.
O pool de browsers ganhou um caminho **headed** (`headedBrowser`/`headedLaunching`, `resolvePlainBrowserLaunchOptions`, estado de launch por modo) depois que esta branch forkou. O PR reescrevia `launchBrowser()` no modelo de browser único contra o qual foi escrito, o que teria **removido o suporte headed**. Em vez disso, reapliquei a preferência pelo Obscura dentro do ramo headless de `launchBrowserInstance()`, à frente do cloakbrowser e do Chromium puro — um browser headed precisa ser um Chromium com janela real, então a preferência de engine é só do caminho headless. `state.engine` alimenta `isStealth` e `getBrowserPoolStatus()`, e o shutdown zera o engine sem matar o servidor Obscura compartilhado (dono: `./obscura.ts`).
typecheck:core limpo e 3/3 em tests/unit/obscura-integration.test.ts. Obrigado, @opensource-elearning.
Validado em lote sobre o tip de release/v3.8.51: boardou sem conflito, typecheck:core limpo e 17/17 em tests/unit/combo-context-window-filter.test.ts. Obrigado, @opensource-elearning.
* feat(video): redact raw client-snapshot transcript fields in the detailed log (#12150 P2)
clientRawRequest.body is captured before the guardrail chain runs and persisted
verbatim by reqLogger.logClientRawRequest, so it retained the client's raw
transcript/audioTranscript cue text on video parts even after P1's description
redaction. Add redactVideoTranscriptFieldsForLog (new, dependency-light module)
and wire it at the logClientRawRequest call site, gated on videoBridgeObserved:
redacts the structured transcript fields in the LOGGED copy only, never the
body sent to the provider or returned to the client.
* refactor(video): extract the guarded client-snapshot log call to keep chatCore within its size budget
Fast Quality Gates check:file-size flagged chatCore.ts growing past its
frozen ceiling (5985 > 5976) from the P2a wiring. Move the guarded
logClientRawRequest call into logClientRawRequestRedacted (new export
in videoBridgeSnapshotRedaction.ts, which already owns the redaction),
collapsing the inline if-block at the chatCore.ts call site to a single
call. Net -4 lines vs the pre-P2a base. Behavior unchanged: non-observed
still logs the exact same clientRawRequest.body reference; observed
still logs the redacted clone.
Validado em lote numa worktree combinada com #12524, #12538, #12277 e #12367 sobre o tip de release/v3.8.51: os quatro boardaram sem conflito (áreas disjuntas — zai-web, nvidia, clova, cursor/devin/fable). typecheck:core limpo, check:provider-consistency OK (272 entradas REGISTRY, 355 providers canônicos), check:known-symbols OK, e 305/305 nos testes tocados pelos quatro PRs. Os IDs de modelo adicionados foram conferidos individualmente. Obrigado, @backryun.
Validado em lote numa worktree combinada com #12524, #12538, #12277 e #12367 sobre o tip de release/v3.8.51: os quatro boardaram sem conflito (áreas disjuntas — zai-web, nvidia, clova, cursor/devin/fable). typecheck:core limpo, check:provider-consistency OK (272 entradas REGISTRY, 355 providers canônicos), check:known-symbols OK, e 305/305 nos testes tocados pelos quatro PRs. Os IDs de modelo adicionados foram conferidos individualmente. Obrigado, @backryun.
Validado em lote numa worktree combinada com #12524, #12538, #12277 e #12367 sobre o tip de release/v3.8.51: os quatro boardaram sem conflito (áreas disjuntas — zai-web, nvidia, clova, cursor/devin/fable). typecheck:core limpo, check:provider-consistency OK (272 entradas REGISTRY, 355 providers canônicos), check:known-symbols OK, e 305/305 nos testes tocados pelos quatro PRs. Os IDs de modelo adicionados foram conferidos individualmente. Obrigado, @backryun.
Validado em lote numa worktree combinada com #12524, #12538, #12277 e #12367 sobre o tip de release/v3.8.51: os quatro boardaram sem conflito (áreas disjuntas — zai-web, nvidia, clova, cursor/devin/fable). typecheck:core limpo, check:provider-consistency OK (272 entradas REGISTRY, 355 providers canônicos), check:known-symbols OK, e 305/305 nos testes tocados pelos quatro PRs. Os IDs de modelo adicionados foram conferidos individualmente. Obrigado, @backryun.
Bump MAJOR electron 43.4.1 → 44.0.0 (Chromium 152, Node 24.18.1, V8 15.2), aprovado pelo operador após análise dos breaking changes contra o código real:
- **Remoção dos builds 32-bit (Windows ia32, Linux armv7l)** — sem impacto: `electron/package.json` só declara alvos x64 e arm64.
- **libEGL/libGLESv2 deixam de ser distribuídos (ANGLE estático)** — sem impacto: nenhuma referência em `electron/`, nos scripts de build ou no afterPack.
- **`clipboard` deixa de ser exposto ao renderer** — sem impacto: sem uso no projeto.
- **`net.request` passa a rejeitar `Sec-Fetch-Dest` document/frame/iframe/fencedframe sem `Sec-Fetch-Mode: navigate`** — sem impacto: `net.request`/`net.fetch` não são usados.
- **`openAsHidden` e `wasOpenedAsHidden` removidos de `app.set/getLoginItemSettings()`** — usados em `electron/main.js:1116` e `electron/lib/windowLifecycle.js:7`, mas sem regressão em plataforma suportada: o caminho vivo do autostart oculto é `args: ["--hidden"]` combinado com a checagem `argv.includes("--hidden")`, que `shouldStartHidden()` avalia primeiro; Linux nem chega nessa API (usa `enableLinuxDesktopAutostart`). `openAsHidden` só funcionava em macOS ≤ 12, que esta própria versão deixa de suportar.
- **macOS 12 (Monterey) sai do suporte** — é o único efeito voltado ao usuário. Registrado no CHANGELOG em PR de acompanhamento.
O smoke de empacotamento (`electron-package-smoke`) não roda em PR para branch de release — `ci.yml` dispara em `main` — então a validação de empacotamento acontece no merge → main, que é o modelo do repositório. Um PR de acompanhamento remove o código morto de `openAsHidden`/`wasOpenedAsHidden`. Obrigado, Dependabot.
Validado em lote numa worktree combinada com os 7 bumps da raiz sobre o tip de release/v3.8.51: npm install reconciliou o lock sem diff (11/11 pacotes na versão alvo), typecheck:core e typecheck:noimplicit:core limpos, lint exit 0, e 149/149 testes nos 11 arquivos que exercitam zod diretamente. As falhas de CI do PR foram discriminadas como estado da base, não do bump. Obrigado, Dependabot.
Validado em lote numa worktree combinada com os 7 bumps da raiz sobre o tip de release/v3.8.51: npm install reconciliou o lock sem diff (11/11 pacotes na versão alvo), typecheck:core e typecheck:noimplicit:core limpos, lint exit 0, e 149/149 testes nos 11 arquivos que exercitam zod diretamente. As falhas de CI do PR foram discriminadas como estado da base, não do bump. Obrigado, Dependabot.
Validado em lote numa worktree combinada com os 7 bumps da raiz sobre o tip de release/v3.8.51: npm install reconciliou o lock sem diff (11/11 pacotes na versão alvo), typecheck:core e typecheck:noimplicit:core limpos, lint exit 0, e 149/149 testes nos 11 arquivos que exercitam zod diretamente. As falhas de CI do PR foram discriminadas como estado da base, não do bump. Obrigado, Dependabot.
Validado em lote numa worktree combinada com os 7 bumps da raiz sobre o tip de release/v3.8.51: npm install reconciliou o lock sem diff (11/11 pacotes na versão alvo), typecheck:core e typecheck:noimplicit:core limpos, lint exit 0, e 149/149 testes nos 11 arquivos que exercitam zod diretamente. As falhas de CI do PR foram discriminadas como estado da base, não do bump. Obrigado, Dependabot.
Validado em lote numa worktree combinada com os 7 bumps da raiz sobre o tip de release/v3.8.51: npm install reconciliou o lock sem diff (11/11 pacotes na versão alvo), typecheck:core e typecheck:noimplicit:core limpos, lint exit 0, e 149/149 testes nos 11 arquivos que exercitam zod diretamente. As falhas de CI do PR foram discriminadas como estado da base, não do bump. Obrigado, Dependabot.