Repository navigation
fix(sse): add first-byte watchdog to the TLS-fingerprint transport (#12656) - #13272
Merged
Merged
Conversation
…12656) The wreq-js TLS-fingerprint transport resolved the Response as soon as upstream headers arrived, with zero guard around how long the caller then waited for the body's first byte. TlsClient's only timing control, a flat timeout, defaults to 600_000ms, matching the reported 90-600s stall window exactly. Add guardTlsFirstByte() (open-sse/utils/tlsFirstByteWatchdog.ts), which races the body's first read() against a short, env-overridable watchdog (TLS_FIRST_BYTE_WATCHDOG_MS, default 10s). A healthy body is unaffected (bytes already buffered are replayed through a passthrough stream); a stalled body cancels the wreq reader and throws, letting proxyFetch's existing TLS-fallback catch blocks fall through to the direct/proxy dispatcher. A non-replay-safe request (e.g. a POST with a body) still throws instead of being silently retried, reusing isTlsFallbackReplaySafe.
4 of 5 tasks
7 tasks done
Githab-capibara
added a commit
to Githab-capibara/OmniRoute
that referenced
this pull request
Sep 17, 2026
…heaper-inference#12656) (cheaper-inference#13272) Merged as part of the 39-PR owner batch of 2026-09-11, validated as a unit. Boarded into one consolidated worktree cut from `release/v3.8.51` with the other 38 — zero conflicts between them. - ESLint over every changed file: no errors (the only finding was one suppression entry the batch emptied, pruned on cheaper-inference#13243) - `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, within baseline); `check:changelog-integrity` OK - complexity 2821 / baseline 3218 and cognitive-complexity 1272 / baseline 1437 — both under baseline - 256 assertions green: 246 under node:test and 10 under vitest, which is where `tests/unit/**/*.test.tsx` actually runs - `check-file-size`: `chatCore.ts` rebaselined 6144 → 6146 for cheaper-inference#13278 and cheaper-inference#13276, annotated and landed on cheaper-inference#13243⚠️ base-red inherited: cheaper-inference#12732 — the provider count (356 in the docs vs the 358 the modules define) and `open-sse/utils/stream.ts` at 3115 > frozen 3098 both reproduce on the pure tip with zero contribution from this batch.
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…heaper-inference#12656) (cheaper-inference#13272) Merged as part of the 39-PR owner batch of 2026-09-11, validated as a unit. Boarded into one consolidated worktree cut from `release/v3.8.51` with the other 38 — zero conflicts between them. - ESLint over every changed file: no errors (the only finding was one suppression entry the batch emptied, pruned on cheaper-inference#13243) - `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, within baseline); `check:changelog-integrity` OK - complexity 2821 / baseline 3218 and cognitive-complexity 1272 / baseline 1437 — both under baseline - 256 assertions green: 246 under node:test and 10 under vitest, which is where `tests/unit/**/*.test.tsx` actually runs - `check-file-size`: `chatCore.ts` rebaselined 6144 → 6146 for cheaper-inference#13278 and cheaper-inference#13276, annotated and landed on cheaper-inference#13243⚠️ base-red inherited: cheaper-inference#12732 — the provider count (356 in the docs vs the 358 the modules define) and `open-sse/utils/stream.ts` at 3115 > frozen 3098 both reproduce on the pure tip with zero contribution from this batch.
5 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #12656
Root cause
When
ENABLE_TLS_FINGERPRINT=true, the wreq-js (TLS fingerprint) branch ofproxyFetch()/TlsClient.fetch()returned the upstreamResponseas soon as headers resolved, with zeroprotection around how long the caller then waited for the body's first byte. The only timing
guard on that path was
TlsClient's single overalltimeoutoption, which defaults to600_000ms(10 min) — matching the reported 90-600s stall range exactly. A stalled wreq body wasreturned to the caller and simply hung; the only generic body-phase guard elsewhere
(
ensureStreamReadinessinchatCore.ts) is transport-agnostic and deliberately loose (up to600_000msin agent/long-running-goal mode) so it can't distinguish "upstream genuinely warmingup" from "the wreq transport itself is wedged."
Fix
open-sse/utils/tlsFirstByteWatchdog.ts(new):guardTlsFirstByte()races the response body'sfirst
read()against a short, env-overridable watchdog (TLS_FIRST_BYTE_WATCHDOG_MS, default10000,0disables it). A healthy/fast body is completely unaffected — the already-read firstchunk is replayed through a passthrough
ReadableStreamso no bytes are lost. A body that neveryields within the deadline cancels the wreq reader and throws.
open-sse/utils/proxyFetch.ts: both TLS-fingerprint branches (direct and proxied) now awaitguardTlsFirstByte(response)instead of returning the raw response. The watchdog's timeouterror is caught by the existing TLS-fallback
catchblocks, so replay-safety(
isTlsFallbackReplaySafe) is reused, not reimplemented — a non-idempotent request (e.g. POSTwith a body) still throws instead of being silently retried on the direct/proxy dispatcher.
src/shared/utils/runtimeTimeouts.ts:getTlsFirstByteWatchdogMs()resolves the new env var,following the existing
readTimeoutMs()/allowZeropattern.open-sse/utils/tlsClient.tsre-exportsguardTlsFirstBytesoproxyFetch.ts(frozen at itsfile-size cap, see
config/quality/file-size-baseline.json) can import it without adding aline to a file that cannot grow.
docs/security/STEALTH_GUIDE.md,docs/reference/ENVIRONMENT.md,.env.example.Regression test
tests/unit/tls-first-byte-watchdog-12656.test.ts— promotes the plan-file's RED probe into apermanent suite:
RED → GREEN evidence (test file run against the code before/after the fix,
TLS_FIRST_BYTE_WATCHDOG_MS=80):Existing tls/proxy regression suites re-run clean (121/122 pass, 1 pre-existing skip):
proxy-fetch.test.ts,proxy-fetch-dns-retry-10443.test.ts,proxyfetch-bun.test.ts,proxyfetch-caller-abort-log-suppression.test.ts,proxyfetch-direct-response-start-timeout-10214.test.ts,proxyfetch-retry-fresh-socket-4252.test.ts,proxyfetch-undici-retry.test.ts,proxyfetch-vercel-relay-2743.test.ts,9551-proxyfetch-no-proxy-context-bypass.test.ts,proxy-nested-context-skip.test.ts,tls-proxy-context.test.ts,tlsClient-circuit-breaker.test.ts,tls-client-wreq-migration.test.ts,tls-client-wreq-residue.test.ts,tls-options.test.ts.Gates run
node scripts/check/check-file-size.mjs— OK (proxyFetch.ts stays at its frozen 1271 LOC cap;the new watchdog logic was extracted into its own module rather than growing the frozen file)
node scripts/check/check-complexity.mjs— OK (2798 vs baseline 3218)node scripts/check/check-cognitive-complexity.mjs— OK (1265 vs baseline 1437)npm run typecheck:core— exit 0npx eslint --suppressions-location config/quality/eslint-suppressions.json <changed files>— exit 0npm run check:docs-all— exit 0 (env/doc contract in sync, no fabricated claims)node scripts/check/check-changelog-integrity.mjs— OK