Skip to content

fix(security): unbiased maxai X-Random nonce + stricter URL/regex test assertions (destrava o gate codeql-ratchet) - #12502

Merged
diegosouzapw merged 1 commit into
release/v3.8.51from
fix/codeql-maxai-crypto
Sep 2, 2026
Merged

diegosouzapw merged 1 commit into
release/v3.8.51from
fix/codeql-maxai-crypto

Conversation

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Contexto

O gate codeql-ratchet está em REGRESSÃO e reprova toda PR aberta do repositório:

[codeql-ratchet] Alertas CodeQL abertos (não-dismissed): 13
[codeql-ratchet] REGRESSÃO — 13 alertas CodeQL abertos > baseline 11

Os 13 alertas não vieram do trabalho que eles estão bloqueando — chegaram com merges recentes de provider/mídia de outros autores:

Commit PR Alertas
cabbbe41 feat(providers): add MaxAI — signed OpenAI-compatible provider … #11461 5
530096a3 feat(providers): add UC (uncensored.com) — persona + direct #11513 4 (2 preservados por 752aac65 / #12423)
3a564183 fix(sse): name the shadowed custom provider node … #12365 1
84504e3f chore(providers): retire Felo Web on provenance hold #11698 1

Esta PR corrige em código os 7 alertas que são defeito real e documenta os 6 restantes para dispensa do operador com justificativa (Hard Rule #14 — nenhum alerta foi dispensado via API por esta sessão).

Projeção: 13 → 6 alertas abertos, abaixo do baseline 11. O rebaseline (--update) fica a cargo do operador, depois que a contagem assentar.

Triagem completa — 13 alertas

# Regra Arquivo:linha Veredito Justificativa
975 js/biased-cryptographic-random open-sse/executors/maxai/signing.ts:128 corrigido randomBytes(4).readUInt32BE(0) % 900000 enviesa o slot X-Random (2^32 não divide 900000). Trocado por crypto.randomInt(100000, 1000000), que faz rejection sampling internamente. Forma na rede idêntica: sempre 6 dígitos.
971 js/incomplete-sanitization tests/unit/maxai-image.test.ts:115 corrigido new RegExp(MAXAI_IMAGE_PATH.replace(/\//g, "\\/")) escapava só / (que nem precisa de escape em RegExp) e aceitava o path em qualquer posição de uma URL errada. Trocado por igualdade exata assert.equal(capturedUrl, MAXAI_BASE_URL + MAXAI_IMAGE_PATH).
980 js/incomplete-sanitization tests/unit/custom-provider-prefix-shadowing-11943.test.ts:100 corrigido RegExp montada com NAME.replace(/[()]/g, "\\$&") — só parênteses escapados; qualquer outro metacaractere num nome futuro seria interpretado em vez de casado literalmente. Trocado por message.includes(...) com a string exata.
976 js/incomplete-url-substring-sanitization tests/unit/uc-image.test.ts:147 corrigido O router do fetch mockado despachava por url.includes("clerk.uncensored.com") — qualquer host que apenas contivesse o nome (ex.: https://evil.example/?next=clerk.uncensored.com) recebia a resposta do mint, ou seja, o teste não conseguia falhar se o executor montasse a URL errada. Trocado pelo helper novo isUcClerkMintUrl(): new URL(url).origin === UC_CLERK_FAPI + shape do path /v1/client/sessions/{sid}/tokens.
977 js/incomplete-url-substring-sanitization tests/unit/uc-image.test.ts:268 corrigido idem #976
978 js/incomplete-url-substring-sanitization tests/unit/uc-video.test.ts:151 corrigido idem #976
979 js/incomplete-url-substring-sanitization tests/unit/uc-video.test.ts:342 corrigido idem #976
973 js/weak-cryptographic-algorithm open-sse/executors/maxai/signing.ts:43 exigência do protocolo HMAC-SHA1 é o MAC que o servidor da MaxAI verifica (sha1 = HMAC_SHA1(sign_str, key=reqTime:hmacKey)). Trocar o algoritmo invalida toda assinatura e derruba o provider. HMAC-SHA1 também não é afetado pelos ataques de colisão contra SHA-1.
972 js/weak-cryptographic-algorithm open-sse/executors/maxai/constants.ts:279 exigência do protocolo reproduceProof() é a reimplementação pura do mesmo cálculo (HMAC-SHA1 + SM3), usada para validar constantes extraídas. Mesma justificativa de #973.
974 js/weak-cryptographic-algorithm tests/unit/helpers/maxaiMockConstants.ts:52 exigência do protocolo referenceProof() é a implementação de referência independente do proof (deliberadamente não importada da produção) para provar que a matemática bate com a spec externa. Arquivo de teste, chaves MOCK, nenhum segredo real.
969 js/insufficient-password-hash open-sse/executors/maxai/signing.ts:43 falso positivo O CodeQL classifica o hmacKey como "password" e o HMAC como hash de senha. Não é: é a chave de um MAC de assinatura de requisição. Nada é armazenado nem comparado como hash de senha, e o hmacKey é uma constante pública que o próprio bundle JS da MaxAI publica (ver cabeçalho de signing.ts). Um KDF com custo (bcrypt/scrypt/argon2) é semanticamente inaplicável aqui e quebraria o protocolo.
970 js/insufficient-password-hash open-sse/executors/maxai/signing.ts:61 exigência do protocolo evpBytesToKey() é o EVP_BytesToKey(MD5) do OpenSSL — exatamente a derivação que CryptoJS.AES.encrypt(text, passphrase) usa. É obrigatória para produzir um envelope Salted__ que a MaxAI consiga decifrar. A "passphrase" é o aesKey público extraído do bundle, não uma senha de usuário.
984 js/stack-trace-exposure open-sse/utils/error.ts:749 falso positivo Precedente documentado (Hard Rule #14 + docs/security/ERROR_SANITIZATION.md): o CodeQL não reconhece sanitizadores customizados. A linha é JSON.stringify({ error: { message: sanitizeErrorMessage(message), … } }) e a única entrada tainted passa por sanitizeErrorMessage() (open-sse/utils/error.ts:64), que corta a string no primeiro \n (descartando os frames at …), substitui paths absolutos por <path>, trunca em MAX_ERROR_LEN e roda redactSensitiveErrorText().

Resumo: 7 corrigidos · 2 falsos positivos (#969, #984) · 4 exigências de protocolo (#970, #972, #973, #974).

Precisa de ação do operador

Os 6 alertas abaixo não foram dispensados por esta sessão (Hard Rule #14 reserva o dismissal ao operador). Sugestão de motivo para cada um:

# Regra Motivo sugerido
969 js/insufficient-password-hash false positive — chave de MAC de assinatura de requisição (constante pública do bundle MaxAI), não hash de senha.
970 js/insufficient-password-hash won't fix — EVP_BytesToKey(MD5) exigido pelo formato Salted__ do CryptoJS que a MaxAI decifra.
972 js/weak-cryptographic-algorithm won't fix — HMAC-SHA1 + SM3 é o esquema de assinatura da MaxAI; mudar quebra o provider.
973 js/weak-cryptographic-algorithm won't fix — idem #972 (caminho de produção).
974 js/weak-cryptographic-algorithm used in tests — implementação de referência do proof, com chaves MOCK.
984 js/stack-trace-exposure false positive — já sanitizado por sanitizeErrorMessage(); ver docs/security/ERROR_SANITIZATION.md.

Depois que a contagem assentar, o rebaseline é node scripts/check/check-codeql-ratchet.mjs --update (não rodei — é decisão do operador).

Mudanças

Arquivo O quê
open-sse/executors/maxai/signing.ts única mudança de produção — maxaiRandomSlot() sobre crypto.randomInt
tests/unit/helpers/ucClerkUrl.ts novo — isUcClerkMintUrl(), recognizer estrito do mint do Clerk
tests/unit/uc-image.test.ts, tests/unit/uc-video.test.ts router do mock com match estrito de origin + path
tests/unit/maxai-image.test.ts igualdade exata de URL no lugar da RegExp semi-escapada
tests/unit/custom-provider-prefix-shadowing-11943.test.ts substring exata no lugar da RegExp semi-escapada
tests/unit/maxai.test.ts teste novo: maxaiRandomSlot emits an unbiased 6-digit X-Random slot

Nenhuma asserção existente foi enfraquecida — todas as trocas em teste são estritamente mais restritivas do que o que estava lá. O prettier do lint-staged reformatou algumas linhas pré-existentes de signing.ts / maxai.test.ts / maxai-image.test.ts (esses arquivos entraram no repo não formatados); é ruído de formatação, não mudança de comportamento.

Verificação (foreground)

Comando Exit Resultado
npm run typecheck:core 0 limpo
node --import tsx/esm --test tests/unit/maxai.test.ts tests/unit/maxai-image.test.ts tests/unit/uc-image.test.ts tests/unit/uc-video.test.ts tests/unit/custom-provider-prefix-shadowing-11943.test.ts 0 tests 99 · pass 99 · fail 0
node --import tsx/esm --test tests/unit/maxai-documents.test.ts 0 tests 14 · pass 14 · fail 0
npx eslint <7 arquivos alterados> 0 0 problemas

Base release/v3.8.51 verificada verde na abertura (sem issue Release branch not green aberta) e sem release-freeze ativo.

…ertions

Drains 7 of the 13 open CodeQL alerts that put the `codeql-ratchet` gate into
regression (13 > baseline 11) on every open PR. The alerts arrived with the
recent provider/media merges (#11461 MaxAI, #11513 UC, #12365 prefix shadowing),
not with the work they are currently blocking.

Production fix (js/biased-cryptographic-random):
- open-sse/executors/maxai/signing.ts: the 6-digit `X-Random` wire slot was
  drawn as `randomBytes(4).readUInt32BE(0) % 900000`. 2^32 does not divide
  evenly by 900000, so the low ~4772 values of the range came out marginally
  more often. Extracted as `maxaiRandomSlot()` over `crypto.randomInt`, which
  rejection-samples internally. The emitted shape is unchanged (6 digits).

Test assertions strengthened (never weakened):
- tests/unit/helpers/ucClerkUrl.ts (new): `isUcClerkMintUrl()` matches the Clerk
  mint call by parsed origin (against `UC_CLERK_FAPI`) plus the
  `/v1/client/sessions/{sid}/tokens` path shape.
- tests/unit/uc-image.test.ts, tests/unit/uc-video.test.ts: the mock fetch
  routers dispatched on `url.includes("clerk.uncensored.com")`, so any host
  merely embedding the name was served the mint response — a malformed URL
  built by the executor could not fail the test
  (js/incomplete-url-substring-sanitization x4).
- tests/unit/maxai-image.test.ts: `new RegExp(PATH.replace(/\//g, "\\/"))`
  escaped only slashes (which need no escaping) and matched the path anywhere in
  a wrong URL; replaced by exact URL equality (js/incomplete-sanitization).
- tests/unit/custom-provider-prefix-shadowing-11943.test.ts: the expected node
  mention was a RegExp with only `()` hand-escaped; replaced by an exact
  substring check (js/incomplete-sanitization).
- tests/unit/maxai.test.ts: regression guard for the X-Random slot (6 digits,
  in range, spread across both halves of the range).

The remaining 6 alerts are not defects and are left for an operator dismissal
with justification (Hard Rule #14): the MaxAI HMAC-SHA1/SM3 signature and the
CryptoJS `EVP_BytesToKey(MD5)` derivation are wire-protocol requirements —
changing either breaks the provider — and `open-sse/utils/error.ts:749` already
routes through `sanitizeErrorMessage()` (documented CodeQL sanitizer blind spot,
docs/security/ERROR_SANITIZATION.md).
Comment thread open-sse/executors/maxai/signing.ts Dismissed
Comment thread open-sse/executors/maxai/signing.ts Dismissed
Comment thread open-sse/executors/maxai/signing.ts Dismissed
@diegosouzapw

Copy link
Copy Markdown
Collaborator Author

Mergeando com 2 checks vermelhos que não podem ficar verdes antes do merge — ambos são estado do repositório, não conteúdo desta PR (15 checks verdes):

  1. CodeQL — acusa "3 alertas novos", mas as anotações apontam open-sse/executors/maxai/signing.ts:56 e :76: são os alertas pré-existentes de hash fraco / derivação de chave, reatribuídos a esta PR apenas porque o conserto do nonce deslocou as linhas do arquivo. Nenhum alerta novo foi introduzido.
  2. codeql-ratchet (13 > baseline 11) — a contagem vem da API de code scanning do repositório, que só é reavaliada depois que as correções entram na branch. Nenhuma PR de correção de alerta consegue derrubar esse número antes de mergear.

O que esta PR de fato faz: corrige 7 alertas reais — nonce enviesado no assinador MaxAI (randomBytes % 900000 → crypto.randomInt, mesmo formato de 6 dígitos na rede), 2 sanitizações incompletas e 4 roteadores de fetch mockado em uc-image/uc-video que casavam qualquer host contendo o nome do domínio (defeito real de teste: não conseguiam falhar se o executor montasse a URL errada). Projeção após o próximo scan: 13 → 6, abaixo do baseline 11, o que devolve o gate ao verde para todas as PRs abertas.

Os 6 alertas restantes seguem abertos, com justificativa técnica por alerta no corpo desta PR, aguardando dispensa do operador (Hard Rule #14 — não dispenso alerta de segurança por conta própria).

@diegosouzapw
diegosouzapw merged commit e243b04 into release/v3.8.51 Sep 2, 2026
19 of 21 checks passed
@diegosouzapw
diegosouzapw deleted the fix/codeql-maxai-crypto branch September 2, 2026 18:56
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…ertions (cheaper-inference#12502)

Drains 7 of the 13 open CodeQL alerts that put the `codeql-ratchet` gate into
regression (13 > baseline 11) on every open PR. The alerts arrived with the
recent provider/media merges (cheaper-inference#11461 MaxAI, cheaper-inference#11513 UC, cheaper-inference#12365 prefix shadowing),
not with the work they are currently blocking.

Production fix (js/biased-cryptographic-random):
- open-sse/executors/maxai/signing.ts: the 6-digit `X-Random` wire slot was
  drawn as `randomBytes(4).readUInt32BE(0) % 900000`. 2^32 does not divide
  evenly by 900000, so the low ~4772 values of the range came out marginally
  more often. Extracted as `maxaiRandomSlot()` over `crypto.randomInt`, which
  rejection-samples internally. The emitted shape is unchanged (6 digits).

Test assertions strengthened (never weakened):
- tests/unit/helpers/ucClerkUrl.ts (new): `isUcClerkMintUrl()` matches the Clerk
  mint call by parsed origin (against `UC_CLERK_FAPI`) plus the
  `/v1/client/sessions/{sid}/tokens` path shape.
- tests/unit/uc-image.test.ts, tests/unit/uc-video.test.ts: the mock fetch
  routers dispatched on `url.includes("clerk.uncensored.com")`, so any host
  merely embedding the name was served the mint response — a malformed URL
  built by the executor could not fail the test
  (js/incomplete-url-substring-sanitization x4).
- tests/unit/maxai-image.test.ts: `new RegExp(PATH.replace(/\//g, "\\/"))`
  escaped only slashes (which need no escaping) and matched the path anywhere in
  a wrong URL; replaced by exact URL equality (js/incomplete-sanitization).
- tests/unit/custom-provider-prefix-shadowing-11943.test.ts: the expected node
  mention was a RegExp with only `()` hand-escaped; replaced by an exact
  substring check (js/incomplete-sanitization).
- tests/unit/maxai.test.ts: regression guard for the X-Random slot (6 digits,
  in range, spread across both halves of the range).

The remaining 6 alerts are not defects and are left for an operator dismissal
with justification (Hard Rule cheaper-inference#14): the MaxAI HMAC-SHA1/SM3 signature and the
CryptoJS `EVP_BytesToKey(MD5)` derivation are wire-protocol requirements —
changing either breaks the provider — and `open-sse/utils/error.ts:749` already
routes through `sanitizeErrorMessage()` (documented CodeQL sanitizer blind spot,
docs/security/ERROR_SANITIZATION.md).

Co-authored-by: Markus Hartung <diegosouzapw@users.noreply.github.com>
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