Skip to content

security(runtime): harden TLS provenance, lifecycle, and public error boundaries - #11742

Merged
diegosouzapw merged 9 commits into
release/v3.8.51from
fix/v3850-tls-client-license-provenance
Sep 17, 2026
Merged

diegosouzapw merged 9 commits into
release/v3.8.51from
fix/v3850-tls-client-license-provenance

Conversation

@diegosouzapw

@diegosouzapw diegosouzapw commented Aug 27, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

This draft hardens the frozen release/v3.8.50 line around the native
tls-client-node supply chain, runtime lifecycle, and public error/log boundaries. It is
independent of other PRs, but it does change provider executors and validation behavior;
the previous narrower scope statement is intentionally superseded.

Area What changed Evidence Disposition
Native dependency provenance Pins tls-client-node@0.2.0, records the exact wrapper/upstream licenses and NOTICE, and pins all six supported v1.15.1 native assets to official SHA-256 digests License/provenance and tamper regressions; manifest/source hash checks PASS for recorded provenance and policy
Build and packaging policy Verifies the native binary before runtime/build copies and carries THIRD_PARTY_NOTICES.md through npm, standalone, Docker, and Electron assembly paths Build/pack policy tests, MJS syntax checks, and exact-head production build/artifact evidence below Source/policy and exact-head production build PASS; real pack, Docker, and Electron artifacts remain HOLD
Runtime lifecycle and install lock Adds bounded lifecycle coordination, safe retries, handle/inode/device/nonce lock ownership, cross-process coverage, and fail-closed cleanup TLS serial matrix 47/47, including 100 callers and cross-process contention PASS on local filesystems
Error boundary Extracts a canonical sanitizer for credentials, stacks, recognized filesystem paths, hostile Proxies, Unicode/serialized separators, and public identifiers Canonical matrix 121/121; runtime/spec review CLEAR PASS
Provider consumers Hardens ChatGPT, Claude, Grok, Perplexity, Notion, LM Arena, moderation, OCR, and provider validation without exposing raw upstream errors Provider-specific matrices listed below PASS within tested boundaries
LM Arena public failures Projects HTTP, network, TLS, upstream-event, and stream-transport failures through a closed local union; rejects raw upstream bodies/stacks/causes and accepts only public error statuses 400..599 Iteration-4 RED 53/55 -> GREEN 55/55; final Arena matrix 124/124; direct CodeQL #968 fixed PASS locally and in final CodeQL analysis
Perplexity streaming Preserves early quota/upstream HTTP status through a bounded preflight, replays normal content without duplication, and releases readers/iterators 54/54 PASS with late-error limit below
Logs and usage Sanitizes call-log, pipeline, lifecycle, console, and request payload fields while preserving internal classification Semaphore SSE+JSON regression; request-log regressions PASS
Documentation Corrects privacy/local-processing language and documents the exact sanitizer boundary Docs gates PASS
Contributor ranking No new ranking rewrite was needed: the .50 base already contains a newer census than the supplied dossier snapshot README: 5,911 merged PR census, 535 distinct external contributors, and 639 normalized human Git identities Already present in base

Exact branch boundary

  • Base: 091589089cd134a94df9f6cdab9ba562b2cefd18 (release/v3.8.50).
  • Candidate: 29dc5427b6abd5a4ff5198759f8cfc2d500ce1aa.
  • Physical scope: 97 paths = 74 modified + 23 added, +18,409 / -1,941 against the exact base.
  • Commit count: 6 commits after the exact base.
  • Production/runtime scope: 32 paths under open-sse/ or src/; there are no production
    changes under electron/ or bin/.
  • This PR does not depend on refactor(stealth): replace tls-client-node with wreq-js #11753. That draft is an alternative wreq-js migration for
    release/v3.8.51; this PR deliberately keeps the exact .50 base and hardens the dependency
    shipped there.
  • The final scope intentionally uses the broader TLS + public error boundary hardening
    boundary. It therefore includes the five Moderation/OCR helper-and-test paths instead of
    presenting them as TLS-only work; the title, summary table, and changelog all name that expansion.

Runtime and security changes

Native dependency and lifecycle

  • Verify the selected native asset against the checked-in platform manifest before loading or
    copying it.
  • Reject version drift, digest mismatch, symlink/path substitution, and unsafe lock ownership.
  • Keep the exclusive install-lock handle open for the operation and verify nonce plus inode/device
    identity before cleanup, so an older owner does not delete a newer owner's lock.
  • Coordinate shared-client start/stop, retries, stream leases, timeout/abort settlement, and cleanup
    without retaining native stack/cause data.
  • Project TLS stream error names through a closed allowlist:
    Error, AbortError, TimeoutError, BodyTimeoutError, TlsClientHangError, and
    NativeTlsError; every other, hostile, or credential-shaped value falls back to Error.

Public errors and persistent logs

  • Route HTTP, JSON, SSE, executor, validation, moderation, OCR, and call-log messages through the
    canonical error sanitizer.
  • Defensively snapshot hostile rejection fields with protected single reads; accessors and
    getPrototypeOf traps fail closed.
  • Redact strong tokens, labeled credentials, stack frames, recognized POSIX/Windows/UNC/file-URL
    paths, raw and serialized line separators, and sensitive nested request-log payloads.
  • Preserve safe status codes, Retry-After, SSE framing, public error codes, and internal cooldown /
    usage classification.
  • Sanitize the semaphore-capacity message before it reaches SSE, JSON, call-log, pipeline, or
    lifecycle events while preserving HTTP 429 and SEMAPHORE_TIMEOUT.

Provider-specific behavior

  • Perplexity: bounded preflight (up to 32 semantic chunks or 250 ms) promotes early quota/upstream
    failures before SSE headers commit, replays normal thinking/content once, and closes readers and
    iterators.
  • Grok: sanitizes chunk, reader, fetch, and TLS failures in streaming and non-streaming paths.
  • LM Arena: maps every public failure through a closed discriminated union and
    buildErrorBody(). Non-2xx TLS bodies are never reflected; network/TLS exceptions, upstream SSE
    errors, and stream-reader failures become fresh locally constructed errors without upstream
    cause or stack. Only integer status codes in the error range 400..599 survive projection;
    success/redirect, invalid, coercive, and hostile values fall back to 502.
  • ChatGPT/Claude/Notion: protect TLS and provider-specific error classification against hostile
    Proxies and sanitize transport metadata.
  • Provider validation: fail closed when proxy-fallback metadata or TLS/header metadata is hostile;
    the route logs only the canonical sanitized message and imports the specific DB modules rather
    than the restricted barrel.

Legal and provenance disposition

Primary sources:

The Commons Clause makes tls-client-node source-available and commercially restrictive.
Distributing notices does not grant a right to sell a product or service covered by that
restriction. The exception remains medium risk and temporary, owned by @diegosouzapw, with review
due 2026-09-30 / v3.9.0. Legal review, separate permission, or replacement with a permissive
transport is still required before commercial deployment. This PR records obligations and reduces
provenance risk; it is not legal advice and does not cure the restriction.

Current shared transport consumers: chatgpt-web, claude-web, perplexity-web, grok-web,
notion-web, and lmarena.

TDD and focused verification

RED -> GREEN was captured for the native manifest/license/NOTICE contract, tampered binaries,
runtime version drift, unsafe install-lock replacement, lifecycle contention, hostile
instanceof/properties/coercion, credential-shaped Error.name, Perplexity early quota and replay,
public error/log sinks, provider validation fallback metadata, semaphore call-log persistence, the
CodeQL polynomial data-URL regex, omitted-media-type data URLs, LM Arena upstream stack/body
reflection, closed stream-transport projection, and rejection of public 2xx/3xx statuses.

Focused results on the final candidate:

Matrix Result
LM Arena iteration-4 status regressions RED 53/55, then GREEN 55/55
Arena / LM Arena serial aggregate 124/124 PASS
TLS lifecycle/download/install-lock/timeout, serial 47/47 PASS
Perplexity 54/54 PASS
Grok 78/78 PASS
Canonical sanitizer / ChatCore helpers 121/121 PASS
Provider-validation error sanitizer 7/7 PASS
Final TLS/header validation slice 7/7 PASS
Moderation + OCR 24/24 PASS
Notion 41/41 PASS
Semaphore SSE + JSON + fresh call-log/pipeline 1/1 PASS
CodeQL data-URL scanner + canonical sanitizer regression slice 85/85 PASS

The TLS parallel aggregate produced 46/47 LOAD-RED because the 100-caller stress exceeded its
75-second limit under CPU contention; the same case passed alone in 48.3 seconds and in the serial
47/47 matrix. A later broad ChatCore slice was 4/11 INFRA-RED: seven cases were rejected by the
real resource-pressure guard while the cgroup was at 96.7%+ usage; the changed case passed alone.
Neither result is represented as a full-suite PASS.

Repository gates

PASS on exact candidate 29dc5427b6abd5a4ff5198759f8cfc2d500ce1aa:

  • npm run typecheck:core;
  • npm run check:cycles;
  • scoped ESLint with the repository suppressions file (0 errors);
  • Prettier on the final changed implementation/test paths;
  • git diff --check;
  • npm run check:error-helper;
  • npm run test:vitest -- --maxWorkers=1 --no-file-parallelism: the first run reached 404/405
    because one auto/glm case timed out; that isolated slice then passed 11/11, and the clean retry
    completed 405/405.

PASS recorded earlier in the branch and unaffected by the final LM Arena-only commits:

  • MJS syntax (node --check, 8/8);
  • npm run check:file-size (4,408 production and 5,086 test files checked);
  • npm run check:secrets -- --ratchet (0 findings);
  • npm run check:any-budget:t11;
  • npm run check:tracked-artifacts;
  • npm run check:docs-all and npm run check:docs-counts (only the two existing soft
    executor-count notices);
  • npm run check:route-validation:t06;
  • npm run check:build-scope.

Exact-head full-suite and artifact evidence:

  • PASS: full npm run test:vitest:ui -- --maxWorkers=1 --no-file-parallelism on 29dc5427 —
    285/285 files and 1,816/1,816 tests in 1,417.66s, with an isolated temporary DATA_DIR.
  • PASS: remote Fast Production Build / npm run build:release on 29dc5427 in 18m21s;
    the omniroute-build artifact was uploaded (4,996,056,168 bytes).
  • PASS: DAST smoke in 8m34s, Semgrep in 3m07s, and Semgrep Cloud on 29dc5427.
  • PASS: Change Classification in 49s. Seven Quality Gates fast-path jobs were SKIPPED by
    the classifier and are not counted as PASS.
  • PASS: remote CodeQL JavaScript/TypeScript on 29dc5427 in 48m30s; Actions, C/C++, Python,
    and the Advanced Security aggregate also passed.
  • FIXED: direct alert OmniRoute favicon shows other logo instead of OmniRoute logo #967 (js/polynomial-redos), with no dismissal or suppression. The
    final PR ref has no open code-scanning alert.
  • FIXED: direct alert [BUG] NPM-installed CLI tools still fail healthcheck on Windows — locateCommand ignores where output #968 (js/stack-trace-exposure), with no dismissal or suppression. Its
    stored most-recent instance changed from open to fixed after the final analysis.

Bounded CodeQL remediation history

Iteration Candidate Observation Action / result
1 b40a9fc3 Language analyses passed, but the outer Advanced Security verdict failed with #967 js/polynomial-redos and #968 js/stack-trace-exposure Started bounded remediation; no alert was dismissed or suppressed
2 e5d1bbc9 The deterministic data-URL scanner fixed direct alert #967. Build, DAST, Semgrep, all CodeQL language jobs, and the outer aggregator passed, but direct alert #968 remained open Demonstrated that the green aggregate alone was insufficient; continued from the direct alert
3 d66748406f Closed LM Arena's public-failure union and routed stream transport through the canonical error builder; static review still found that projected HTTP status accepted public 2xx/3xx values Added a narrower regression instead of declaring completion
4 29dc5427 Normalizes public LM Arena statuses to integer 400..599 and rejects success/redirect, invalid, coercive, and hostile values TDD RED 53/55 -> GREEN 55/55; Arena matrix 124/124; all CodeQL language jobs and the aggregate PASS; direct #967/#968 FIXED

Iteration-1 remote evidence on b40a9fc3 included Fast Production Build PASS in 18m56s, DAST
PASS in 8m39s, Semgrep and Semgrep Cloud PASS, and all four CodeQL language jobs PASS. Iteration-2
remote evidence on e5d1bbc9 included Fast Production Build PASS in 19m01s, DAST/Semgrep PASS,
all CodeQL language jobs PASS, and the outer aggregator PASS. Quality Gates fast-path jobs skipped
by workflow classification are never counted as PASS.

Exact-base / non-candidate reds

  • npm run typecheck:noimplicit:core reports 36 errors only in unchanged
    open-sse/translator/response/openai-responses/pureHelpers.ts; the file is byte-identical to the
    exact base, so this is BASE-RED.
  • docs/i18n/it/README.md, docs/i18n/tr/README.md, and
    tests/unit/grok-web.test.ts are Prettier-red on both the candidate and exact base; no broad
    reformat is included here.
  • The live npm run check:changelog-integrity compares this frozen .50 tree with the newer
    origin/release/v3.8.51 and reports ten .51 bullets as missing. CHANGELOG.md on this candidate
    is byte-identical to exact base 091589...; only the owned changelog fragment is added, so this is
    branch drift / BASE-RED, not a candidate regression.
  • The Claude native test reaches its material assertions and then terminates in the same koffi
    fatal on the candidate and exact base: BASE-RED.
  • npm run check:circular-deps invokes the shell shim node_modules/.bin/dpdm through Node and
    currently fails before graph analysis; this is distinct from the passing npm run check:cycles
    result above and is not called a candidate PASS.
  • npm run check:open-sse-typecheck reports a new-baseline mismatch only in unchanged
    src/app/api/v1/models/catalog.ts; the relevant source/config blobs are identical to the exact
    base, so this remains BASE-RED, not a candidate PASS.

HOLD / not claimed

  • Full production dependency-license scan from a clean lockfile install: the shared node_modules
    is contaminated and cannot prove the exact dependency tree.
  • Commercial authorization for the Commons Clause dependency.
  • Real npm pack/boot artifact, Docker Node/Bun images, and Electron Linux/Windows/macOS artifacts.
  • npm run check:pack-policy / its real npm pack --dry-run did not finish within more than ten
    minutes and was interrupted without a verdict.
  • Artifact homologation/deployment.
  • Full npm run test:unit and coverage.

These HOLDs keep the PR in draft. They are not presented as PASS and do not authorize merge,
release, or deployment.

Known runtime boundaries

  • Install-lock guarantees are for local filesystems; NFS/CIFS equivalence is not claimed.
  • Node provides no atomic compare-and-unlink, so a minimal TOCTOU window remains despite the live
    handle, inode/device, and nonce checks.
  • A native synchronous call cannot be preempted by JavaScript; process-exit cleanup is best effort.
  • A never-settling native start/cleanup can retain a discarded lifecycle lease; there is no safe
    JavaScript TTL that can interrupt the native library.
  • A Perplexity error after the bounded preflight and after SSE 200 is committed cannot be promoted
    to an HTTP 429/502.

Maintainer boundary

  • Leave this PR open and draft.
  • No merge or auto-merge.
  • No tag, release, publish, deployment, or homologation action is part of this PR.

@diegosouzapw

Copy link
Copy Markdown
Collaborator Author

Segurando esta PR por ora — não vou mergeá-la nesta rodada.

Motivo: #11753 ("refactor(stealth): replace tls-client-node with wreq-js") remove tls-client-node completamente do package.json, substituindo-o por wreq-js. Se ambas mergearem, o trabalho desta PR (pin + verificação de proveniência dos artefatos nativos do tls-client-node) fica pinando uma dependência que não existe mais no release/v3.8.51.

O dono decidiu segurar as duas PRs por enquanto para avaliar manualmente qual caminho seguir (manter tls-client-node pinado/verificado, ou migrar para wreq-js). Nenhuma ação necessária da sua parte — só um aviso da sobreposição.

@diegosouzapw

Copy link
Copy Markdown
Collaborator Author

Obrigado pelo trabalho de hardening de proveniência aqui! Ao processar esta PR junto com sua companheira #11753 (que acabou de ser retargetada para release/v3.8.51 e validada), encontrei que o escopo desta PR ficou obsoleto: ela pina e verifica a proveniência do binário nativo do tls-client-node (tlsClientNativeManifest.json, tlsClientDownloadDir.ts, fixTlsClientNodeBinary.mjs, testes de docker-binary), mas #11753 remove o tls-client-node inteiramente do projeto, migrando todos os provedores web-cookie (ChatGPT, Claude, Perplexity, Grok, Notion, LMArena) para wreq-js.

Depois que #11753 for mesclada, a dependência que esta PR protege deixa de existir no código — não há mais binário do tls-client-node para pinar ou verificar. Vou fechar esta PR. Se fizer sentido aplicar o mesmo espírito de pin/verificação de proveniência ao wreq-js (que #11753 introduz com pin exato 3.0.0), isso pode virar uma issue nova e separada — me avise se quiser que eu abra uma. Obrigado novamente pelo cuidado com a cadeia de suprimentos aqui.

@diegosouzapw diegosouzapw changed the title security(deps): pin and verify tls-client native artifacts security(runtime): harden TLS provenance, lifecycle, and public error boundaries Sep 1, 2026
Comment thread open-sse/utils/errorSanitization.ts Fixed
@diegosouzapw

Copy link
Copy Markdown
Collaborator Author

Babysit — auditoria do candidato 29dc5427

Auditoria preparada contra a base exata
091589089cd134a94df9f6cdab9ba562b2cefd18 (release/v3.8.50). O branch local, o upstream
remoto e o headRefOid da PR apontam para
29dc5427b6abd5a4ff5198759f8cfc2d500ce1aa.

O fechamento abaixo usa os resultados remotos exatos e os endpoints diretos dos alertas; nenhum
verde agregado foi usado como substituto para a verificação de #967/#968.

Estado, identidade e escopo

Item Resultado
PR #11742 — OPEN + DRAFT
Auto-merge desativado (null)
Base exata 091589089cd134a94df9f6cdab9ba562b2cefd18 (release/v3.8.50)
Candidato exato 29dc5427b6abd5a4ff5198759f8cfc2d500ce1aa
Merge state atual estruturalmente MERGEABLE; política BLOCKED enquanto draft/sem aprovação
Diff exato 97 caminhos, +18.409 / -1.941, em 6 commits
Escopo TLS nativo: proveniência, empacotamento, lifecycle e lock; fronteiras públicas de erro/log em executors/handlers/validation, incluindo LM Arena e Moderations/OCR
Autoridade da evidência Git físico no worktree isolado e GitHub da PR. A geração 2026-09-01T21:14:36Z do grafo cobre o checkout principal .51, não este WIP .50, e não é usada como prova negativa deste candidato
PR #11753 alternativa independente em wreq-js para .51; não é dependência desta PR e não foi mesclada aqui

Loop de correção do CodeQL — 4/5 iterações

Nenhum alerta foi descartado, suprimido ou reclassificado para obter verde; o limite do skill
babysit não foi excedido.

Iteração SHA Evidência Providência
1 b40a9fc3 Todos os jobs de linguagem do CodeQL passaram, mas o verdict externo falhou com #967 js/polynomial-redos e #968 js/stack-trace-exposure Iniciada correção delimitada, com regressões; nenhum dismiss/suppress
2 e5d1bbc9 Scanner determinístico corrigiu diretamente #967. Build, DAST, Semgrep, todos os jobs de linguagem e o agregador ficaram verdes, mas o endpoint direto ainda mostrava #968 open O verde agregado não foi usado como prova de encerramento; a investigação continuou
3 d66748406f A união fechada de falhas públicas e o stream transport passaram pelo buildErrorBody(); revisão estática encontrou que o status ainda admitia 2xx/3xx Adicionados casos negativos específicos; a iteração não foi declarada final
4 29dc5427 Status público LM Arena limitado a inteiro 400..599; sucessos, redirects, valores inválidos/coercivos/hostis caem para 502 TDD RED 53/55 -> GREEN 55/55; matriz Arena 124/124; todos os jobs CodeQL e o agregador PASS; #967/#968 diretamente FIXED

Evidência local no SHA exato

PASS confirmado em 29dc5427:

  • TDD da iteração 4: RED 53/55 nos dois novos casos; depois GREEN 55/55.
  • Matriz serial Arena / LM Arena: 124/124.
  • npm run typecheck:core.
  • ESLint escopado com suppressions do repositório: 0 erros.
  • Prettier nos caminhos finais alterados.
  • npm run check:error-helper.
  • npm run check:cycles.
  • git diff --check.
  • Vitest MCP, execução final: 44 arquivos / 405/405.
    • A primeira execução atingiu 404/405 por timeout isolado no caso auto/glm.
    • O recorte isolado passou 11/11.
    • A repetição integral e serial passou 405/405; o primeiro timeout fica registrado como
      flake, não apagado do histórico.

Evidência local anterior, sem alteração material pelos dois commits finais de LM Arena:

  • TLS serial 47/47; stress isolado 1/1 em 48,3s.
  • Perplexity 54/54; Grok 78/78; sanitização canônica/ChatCore 121/121.
  • Validação de providers 7/7 + 7/7; Moderations/OCR 24/24; Notion 41/41.
  • Regressão de semaphore/call-log: 1/1.
  • Scanner de data URL + recorte canônico: 85/85.
  • Ratchets de tamanho (prod=4408, tests=5086), secrets (0), any-budget,
    tracked-artifacts, docs, route-validation, build-scope e sintaxe MJS (8/8).

Não são promovidos a PASS:

  • O agregado TLS paralelo foi 46/47 LOAD-RED sob contenção; o caso de 100 callers passou
    isolado e na matriz serial.
  • O recorte ChatCore amplo foi 4/11 INFRA-RED com o guard real de pressão de recursos acima de
    96,7%; o caso alterado passou isolado.

CI e segurança no SHA exato

Verificação Resultado final
Change Classification PASS em 49s
Fast Production Build / build:release / upload do artefato PASS em 18m21s; artefato omniroute-build enviado (4.996.056.168 bytes)
DAST smoke PASS em 8m34s
Semgrep PASS em 3m07s
Semgrep Cloud PASS
CodeQL Actions PASS em 55s
CodeQL C/C++ PASS em 1m00s
CodeQL Python PASS em 59s
CodeQL JavaScript/TypeScript PASS em 48m30s
Advanced Security aggregate PASS
Quality Gates fast-path SKIPPED — sete jobs pulados pelo classificador; não contabilizados como PASS
Vitest UI, 285 arquivos / 1.816 testes PASS — 285/285 arquivos e 1.816/1.816 testes em 1.417,66s, no SHA 29dc5427 e DATA_DIR temporário
Reviews e threads acionáveis CLEAR — nenhuma thread acionável; uma thread CodeQL antiga está resolved e outdated; nenhum review request

Estado direto dos alertas — a fonte obrigatória para encerrar este loop, independentemente do
verde do agregador:

Alerta Estado no candidato final
#967 — js/polynomial-redos FIXED; nenhum dismiss/suppress; a ref final não possui alerta aberto
#968 — js/stack-trace-exposure FIXED; o endpoint direto mudou de open em e5d1bbc9 para fixed após a análise final; nenhum dismiss/suppress

Consulta adicional por alertas abertos na ref refs/pull/11742/head: lista vazia.

BASE-RED / branch drift — não atribuídos ao candidato

  • npm run typecheck:noimplicit:core: 36 erros apenas em
    open-sse/translator/response/openai-responses/pureHelpers.ts, inalterado e byte-idêntico à base.
  • npm run check:open-sse-typecheck: mismatch do baseline em
    src/app/api/v1/models/catalog.ts, com os blobs envolvidos idênticos à base.
  • npm run check:circular-deps: o script chama node node_modules/.bin/dpdm, mas dpdm é um
    shell shim; isso é distinto do npm run check:cycles que passou acima.
  • Prettier em docs/i18n/it/README.md, docs/i18n/tr/README.md e
    tests/unit/grok-web.test.ts: mesmos hunks vermelhos na base exata.
  • Teste Claude Web: fatal nativo em koffi após as asserções, reproduzido igualmente na base.
  • npm run check:changelog-integrity: o checker vivo compara esta árvore congelada .50 com a
    linha .51 atual e acusa dez bullets da .51; CHANGELOG.md permanece byte-idêntico à base e
    esta PR adiciona somente o fragmento próprio.

HOLD / não alegado como PASS

  • Autorização comercial para a dependência Commons Clause: exige autorização separada, revisão
    jurídica ou substituição por transporte permissivo. NOTICE/proveniência não concedem licença.
  • Scan de licenças de produção a partir de instalação/lockfile limpos; o node_modules
    compartilhado não prova a árvore exata.
  • npm run check:pack-policy: npm pack --dry-run permaneceu ativo por mais de dez minutos e foi
    interrompido, sem veredito.
  • Pacote e boot reais, Docker Node/Bun e artefatos Electron Linux/Windows/macOS.
  • Homologação e deploy.
  • Matriz integral npm run test:unit e coverage.

Disposição para o mantenedor

CI material verde e pronto para revisão humana. Os alertas diretos #967/#968 estão corrigidos,
não há thread/review acionável e os checks materiais executados no candidato final passaram. Isso
não libera merge: os HOLDs acima permanecem e esta PR deve continuar aberta e em draft até
decisão humana.

Nenhum merge, auto-merge, tag, release, publicação, homologação ou deploy foi executado ou
autorizado por esta auditoria.

@diegosouzapw
diegosouzapw changed the base branch from release/v3.8.50 to release/v3.8.51 September 17, 2026 16:35
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).
… hardening

open-sse/handlers/chatCore.ts 6219 -> 6287. This is the one part of #11742 that
survived the rebase: sanitizeErrorMessage on the plugin onError hook, on the
semaphore-timeout path and on failureMessage before it reaches console.log and
the call log, sanitizeUpstreamDetails on the malformed-response log, and
getSafeErrorMetadata + try/catch where hostile (Proxy) metadata could throw.

That is the LOG boundary, which is broader than Hard Rule #12 (responses). The
rest of the PR was dropped as already landed on the tip.
@diegosouzapw

Copy link
Copy Markdown
Collaborator Author

Rebaseada de release/v3.8.50 para release/v3.8.51 e mergeada. O resultado mudou bastante o escopo, então registro o que aconteceu.

A PR encolheu de 97 para 27 arquivos. Nas três semanas em que ficou aberta, o tip absorveu quase tudo por três caminhos independentes:

já no tip supera
#12506 → #12945 → #13635 (public error boundaries) todo o stream de fronteiras de erro
#12429 (web-cookie TLS → wreq-js) o commit inteiro de provenance/pinning do tls-client-node
#11754 (aposentadoria do ChatGPT Web) as 3214 linhas de chatgpt-web.ts

A evidência mais direta: errorPathRedaction.ts, errorSanitization.ts e upstreamErrorResponse.ts conflitaram como add/add — os dois lados criaram o mesmo arquivo do zero.

Na sanitização, o tip é mais restritivo que esta PR, então ficou o tip. O isSafePublicErrorIdentifier daqui é denylist (o que não bate num marcador de credencial passa — falha aberta); o do tip é allowlist pura de ~380 identificadores mais ^[1-5]\d{2}$ (o que não está listado vira "error" — falha fechada). Mesmo padrão em upstreamErrorPassthrough.ts, onde o tip soma containsSensitiveErrorCredential() à elegibilidade e embrulha a projeção em try/catch → null.

Resultado: todo o open-sse/utils/ difere do tip por uma única linha — o registro de lmarena_stream_error no allowlist, necessário porque a fronteira do Arena emite esse identificador. É literal fixo, emitido junto de ARENA_PUBLIC_MESSAGES.upstreamStream, sem dado do upstream: registra um token, não afrouxa o mecanismo.

O que sobreviveu e é o valor real desta PR é o endurecimento da fronteira de log, reaplicado à mão sobre a estrutura nova do chatCore.ts (o tip o reescreveu em 42 commits): sanitizeErrorMessage no hook onError de plugin, no caminho de timeout de semáforo e na failureMessage antes de ela chegar ao console.log e ao call-log; sanitizeUpstreamDetails no log de resposta malformada; getSafeErrorMetadata + try/catch onde metadata hostil (Proxy) podia lançar. Isso é mais amplo que a Hard Rule #12, que cobre respostas — e é a razão dos +68 em chatCore.ts, rebaselinados com justificativa nominal.

Também foi reaplicado o que o tip não tinha: sanitizadores do Perplexity sobre a arquitetura nova (a via não-streaming de quota mandava chunk.error e chunk.errorCode crus para o corpo da resposta), sanitizeNotionTransportError na via fail-closed do Notion, e o vocabulário estruturado ArenaPublicFailure do LMArena, que não ecoa error.message.

Validação: 61/61 nos testes de sanitização, 200/200 nos de executor — incluindo tls-client-wreq-residue, o guard que falha se o tls-client-node reaparecer, o que prova que o descarte daquele stream está completo. check:file-size e check:docs-counts verdes.

Três resíduos que merecem PR própria:

  1. toPublicPerplexityErrorCode virou quase no-op — contra a allowlist do tip, um código real como RATE_LIMIT colapsa sempre para PPLX_ERROR. Mais restritivo, então ficou, mas a intenção original não vale mais.
  2. scripts/check/check-licenses.mjs com exceções versionadas + expiração reviewBy foi descartado: é melhoria real e não específica de TLS.
  3. A seção da ERROR_SANITIZATION.md que explicava respostas e logs como fronteiras separadas se perdeu — justamente o conceito que sobreviveu no código.

@diegosouzapw
diegosouzapw marked this pull request as ready for review September 17, 2026 18:35
@diegosouzapw
diegosouzapw merged commit 2fa6ef0 into release/v3.8.51 Sep 17, 2026
9 of 12 checks passed
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
… boundaries (cheaper-inference#11742)

* security(deps): pin and verify tls-client native artifacts

* docs(changelog): link tls-client provenance PR

* security(runtime): harden TLS and public error boundaries

* security(runtime): resolve CodeQL error-boundary findings

* security(lmarena): close public stream error boundary

* fix(lmarena): normalize public error statuses

* chore(quality): rebaseline chatCore.ts for the surviving log-boundary hardening

open-sse/handlers/chatCore.ts 6219 -> 6287. This is the one part of cheaper-inference#11742 that
survived the rebase: sanitizeErrorMessage on the plugin onError hook, on the
semaphore-timeout path and on failureMessage before it reaches console.log and
the call log, sanitizeUpstreamDetails on the malformed-response log, and
getSafeErrorMetadata + try/catch where hostile (Proxy) metadata could throw.

That is the LOG boundary, which is broader than Hard Rule cheaper-inference#12 (responses). The
rest of the PR was dropped as already landed on the tip.
@diegosouzapw
diegosouzapw deleted the fix/v3850-tls-client-license-provenance branch October 18, 2026 23:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants