Skip to content

fix(ssrf): isolate pinned connections from pooled sockets; bump dependencies - #37

Merged
frontegg-david merged 4 commits into
mainfrom
fix/ssrf-pooled-socket-pinning
Sep 26, 2026
Merged

frontegg-david merged 4 commits into
mainfrom
fix/ssrf-pooled-socket-pinning

Conversation

@frontegg-david

@frontegg-david frontegg-david commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

SSRF hardening: pinned transport never reuses pooled sockets

Since 2.5.1, safeFetch pins every Node connection to the address assertUrlSafe validated, through a custom lookup. That closes the DNS-rebinding window between the check and the connect. There was one remaining gap: Node ≥ 19's http.globalAgent keeps sockets alive and pools them by host:port, ignoring lookup. A pinned request could reuse a keep-alive socket that another client in the process had already connected to a different (internal) address.

nodePinnedTransport now passes agent: false, so every request and every redirect hop opens a fresh socket through the pinned lookup.

Regression tests

  • Pooled socket (ssrf.spec.ts). First, warm http.globalAgent with a socket to 127.0.0.1. Then a request to the same host:port pinned to a public address must not reach the loopback server. This test failed before the fix.
  • DNS rebinding (ssrf.spec.ts, generator.spec.ts). Validation answers a public address and a connect-time dns.lookup would answer loopback. safeFetch, fromURL and external $ref resolution must never reach the loopback server, and must never call the connect-time lookup. I checked that all three fail when the pinned lookup is removed.

Docs

docs/ssrf-prevention.md "How It Works" now covers DNS validation, connection pinning, fresh sockets and per-hop redirect checks, plus the limits of fetchImpl and edge runtimes.

Dependency updates (supersedes #34, #35, #36)

⚠️ Breaking change

json-schema-ref-parser 16 requires Node.js ≥ 22.19.0 (Node 20 reached end-of-life in April 2026). engines.node is now >=22.19.0, and the CI matrix drops 20.x.

Verification

  • yarn build
  • tsc --noEmit
  • yarn test:coverage: 1050 tests, 100% statements/branches/functions/lines
  • yarn test:unit and yarn test:integration
  • yarn test:e2e: 56 tests
  • All run locally on Node 24.

Summary by CodeRabbit

  • Security

    • Node.js requests now connect to the address validated during URL checks and use a fresh connection for each request. Redirects are revalidated, and redirects are not followed for external reference fetches.
    • On Web or edge runtimes, or when using a custom fetch implementation, connection pinning is unavailable; use host allowlists and network egress controls with untrusted input.
  • Bug Fixes

    • Multipart requests now preserve byte data when serializing Uint8Array values.
  • Compatibility

    • The minimum supported Node.js version is now 22.19.0.

Node's global http agent keeps sockets alive and pools them by host:port,
ignoring the request's lookup. A pinned request could therefore reuse a
socket another client in the process had connected to a different
(internal) address, skipping the pin. The pinned transport now uses a
one-off agent per request so every hop dials through the pinned lookup.

Adds regression tests for DNS rebinding through safeFetch, fromURL and
external $ref resolution (validation answers public, a connect-time lookup
would answer loopback), and for the pooled-socket case. Documents the
pinning guarantees in docs/ssrf-prevention.md.
…ire Node >= 22.19

json-schema-ref-parser 16 drops Node.js versions before 22.19.0 (Node 20
reached end-of-life in April 2026) and ships upstream reference-handling
hardening. Raise engines.node to match and drop 20.x from the CI matrix.

BREAKING CHANGE: Node.js >= 22.19.0 is now required.
…ode 26, @types/jest 30, zod 4.6

- @types/node 26 types Uint8Array views as Uint8Array<ArrayBufferLike>,
  which is not a BlobPart; copy multipart binary parts into a plain
  ArrayBuffer-backed view before wrapping them in a Blob.
- Jest 30 renamed --testPathPattern to --testPathPatterns.
@coderabbitai

coderabbitai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Essentials

Run ID: 4ae70e4d-6789-4612-b373-5b66425c765f

📥 Commits

Reviewing files that changed from the base of the PR and between 525862a and 1946fe9.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (8)
  • .github/workflows/push.yml
  • CLAUDE.md
  • docs/ssrf-prevention.md
  • package.json
  • src/__tests__/generator.spec.ts
  • src/__tests__/ssrf.spec.ts
  • src/request-builder.ts
  • src/ssrf.ts

Included review availability: This review used your included allowance. 1 included review remains after this review. Your included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.


Walkthrough

Node requests now use fresh sockets to preserve pinned-address behavior. The changes add DNS-rebinding and socket-pooling tests, update Node and test-tool versions, document SSRF safeguards, and copy multipart byte arrays before creating a Blob.

Changes

Pinned request transport

Layer / File(s) Summary
Node runtime baseline
.github/workflows/push.yml, package.json
The CI matrix tests Node 22.x and 24.x. The minimum supported Node.js version is 22.19.0.
Socket isolation and regression coverage
src/ssrf.ts, src/__tests__/ssrf.spec.ts, src/__tests__/generator.spec.ts, docs/ssrf-prevention.md
nodePinnedTransport disables agent pooling. Tests cover DNS rebinding and pooled socket reuse. Documentation describes request pinning, redirects, and runtime limitations.

Test and parser toolchain

Layer / File(s) Summary
Test and parser versions
package.json, CLAUDE.md
The integration-test script uses Jest’s --testPathPatterns option. Dependency ranges and documented Jest and JSON Schema reference parser versions are updated.

Multipart byte serialization

Layer / File(s) Summary
Copy multipart byte arrays
src/request-builder.ts
Multipart serialization copies Uint8Array values before passing them to Blob.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to 1946f

No merge-blocking issue is established; the change is ready for normal checks.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary SSRF connection-isolation fix and also identifies the dependency updates.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 4 files. (4 skipped: 4 …
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

A rabbit checks the pinned address with care.
A fresh socket carries each request through air.
No pooled path can change the route.
Byte copies keep multipart values stout.
New test versions hop along the way.
The rabbit thumps, then bounds away.

Comment @coderabbitai help to get the list of available commands.

@frontegg-david
frontegg-david merged commit b371c7a into main Sep 26, 2026
7 checks passed
@frontegg-david
frontegg-david deleted the fix/ssrf-pooled-socket-pinning branch September 26, 2026 00:00
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.

1 participant