Skip to content

fix(security): block SSRF in remote connection endpoints#27

Open
dirkwa wants to merge 2 commits into
masterfrom
fix-ssrf-remote-connection-endpoints
Open

fix(security): block SSRF in remote connection endpoints#27
dirkwa wants to merge 2 commits into
masterfrom
fix-ssrf-remote-connection-endpoints

Conversation

@dirkwa

@dirkwa dirkwa commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Motivation

The testSignalKConnection, requestAccess and checkAccessRequest admin endpoints passed an attacker-controlled host straight into http(s).request, letting the server be driven to make requests to loopback, the cloud metadata service (169.254.169.254) and other non-routable destinations (SSRF, GHSA-q59x-jc9f-gfqf). requestId was also interpolated into the remote request path, enabling traversal.

Approach

Validate the destination before connecting, in a new src/ssrfGuard.ts:

  • Reject special-use addresses — loopback, link-local/cloud-metadata, unspecified, multicast, reserved — for both IP literals (checked synchronously, because Node skips a custom lookup for literal hosts) and resolved hostnames (via a validating lookup that pins the socket to the checked address, preventing DNS rebinding).
  • Private LAN ranges (RFC1918, IPv6 ULA) stay reachable on purpose: connecting to another Signal K server on the boat's own network is the intended use of these endpoints.
  • Require requestId to be a UUID, stopping path traversal in checkAccessRequest.

The selfsignedcert toggle is intentionally retained (self-signed certs are common on boat LANs) and is now only reachable for non-special-use hosts. The auth model is unchanged: these endpoints must work during initial setup before security is enabled, and addAdminMiddleware already gates them once security is on.

Tested

  • npm run build, eslint, and prettier --check all pass
  • 41 unit tests in test/ssrfGuard.ts covering address classification, the validating lookup, the literal-host guard, and requestId validation
  • End-to-end checks confirming requests to 127.0.0.1, localhost, 169.254.169.254 (HTTP and HTTPS) and the ::ffff: IPv4-mapped form are blocked, while a 192.168.x LAN host still reaches the network layer

SSRF Protection for Remote Connection Endpoints

This PR adds server-side request forgery (SSRF) protection to the testSignalKConnection, requestAccess, and checkAccessRequest admin endpoints, which previously accepted attacker-controlled hosts and passed them directly to HTTP(S) request logic.

Core SSRF Guard Module

A new src/ssrfGuard.ts module provides SSRF protection utilities:

  • Address Classification: isBlockedAddress() and assertAllowedHost() reject special-use addresses including loopback (127.0.0.0/8, ::1), link-local/cloud metadata (169.254.0.0/16, fe80::/10), multicast, unspecified, and reserved ranges
  • DNS Rebinding Prevention: ssrfSafeLookup replaces the standard DNS lookup during HTTP requests, validating all resolved addresses before connection and pinning the socket to the checked address
  • Private Network Access: Allows private LAN ranges (RFC1918, IPv6 ULA) so local Signal K servers remain reachable
  • Additional Protections: Blocks NAT64 prefix (64:ff9b::/96) and deprecated IPv4-compatible IPv6 forms (::/96)

Request Validation & Response Filtering

A new src/remoteConnectionSchemas.ts defines TypeBox validation schemas for request bodies:

  • Enforces host as non-empty string, port as numeric value in range 1–65535
  • Requires requestId to be a valid UUID (preventing path traversal)
  • Validates TLS and self-signed certificate toggle flags

Updated src/serverroutes.ts endpoints integrate validation and implement safer response handling:

  • Validates all incoming request bodies against the schemas, returning structured 400 errors on failure
  • Parses remote discovery responses with pickServerInfo() to expose only server.id and server.version
  • Normalizes access-request responses with pickAccessRequestReply() to expose only relevant fields (state, optional requestId, and access permission/token data)
  • Standardizes transport-level failures to a generic CONNECTION_FAILED error (detailed errors logged server-side)
  • Adds ssrfSafeLookup and assertAllowedHost() checks to remote HTTP fetching logic

Testing

Comprehensive test coverage validates the security improvements:

  • 41 unit tests in test/ssrfGuard.ts covering address classification, validating lookup behavior, literal-host guards, and requestId validation
  • Tests in test/remoteConnectionSchemas.ts verify schema validation for request bodies, UUID patterns, and port ranges
  • End-to-end checks confirm blocking of loopback, link-local, metadata addresses (HTTP/HTTPS) and IPv4-mapped IPv6 forms while allowing private LAN ranges to reach the network layer

The testSignalKConnection, requestAccess and checkAccessRequest endpoints
passed an attacker-controlled host straight to http(s).request, letting the
server be driven to loopback, the cloud metadata service (169.254.169.254)
and other non-routable destinations (GHSA-q59x-jc9f-gfqf).

Validate the destination before connecting: reject special-use addresses
(loopback, link-local/metadata, unspecified, multicast, reserved) for both
IP literals and resolved hostnames, pinning the connection to a checked
address to prevent DNS rebinding. Private LAN ranges stay reachable so the
intended use - connecting to another Signal K server on the boat network -
keeps working. Also require requestId to be a UUID to stop path traversal
in checkAccessRequest.
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6e007c65-ae47-4de2-bf5b-4e435b38382f

📥 Commits

Reviewing files that changed from the base of the PR and between 27787e2 and ed1feab.

📒 Files selected for processing (5)
  • src/remoteConnectionSchemas.ts
  • src/serverroutes.ts
  • src/ssrfGuard.ts
  • test/remoteConnectionSchemas.ts
  • test/ssrfGuard.ts

📝 Walkthrough

Walkthrough

This PR introduces SSRF (Server-Side Request Forgery) protection and request validation for remote-connection admin endpoints. It adds a new SSRF guard module that blocks outbound connections to special-use IP ranges, defines TypeBox schemas for validating remote requests, and integrates both into existing endpoints with standardized error handling and safe DNS resolution.

Changes

Remote Connection SSRF Protection and Validation

Layer / File(s) Summary
SSRF Guard: Blocked Addresses and Safe Lookup
src/ssrfGuard.ts, test/ssrfGuard.ts
BlockedHostError exception and isBlockedAddress() classify IP literals against IPv4/IPv6 special-use ranges (loopback, link-local, metadata, multicast). assertAllowedHost() synchronously rejects blocked IPs. ssrfSafeLookup resolves all addresses, fails fast if any is blocked, and returns validated addresses to pin HTTP connections and mitigate DNS rebinding. Tests verify blocked/allowed classification, sync rejection, and integration with http.request.
Request Validation Schemas
src/remoteConnectionSchemas.ts, test/remoteConnectionSchemas.ts
TypeBox schemas define validation for test connection (useTLS, selfsignedcert, optional token), request access (clientId, description), and check access request (UUID requestId). Patterns enforce UUID format and 1–65535 port range. Tests verify schema acceptance/rejection of valid/invalid inputs, port formats, and missing required fields.
Endpoint Integration with Validation and SSRF
src/serverroutes.ts
Routes import SSRF guards and schemas; /testSignalKConnection, /requestAccess, and /checkAccessRequest validate payloads via validateAgainst, returning 400 on schema failure. Response helpers pickServerInfo and pickAccessRequestReply normalize remote responses. makeRemoteRequest adds lookup: ssrfSafeLookup and assertAllowedHost() checks, accepts port and TLS flags as flexible types, and standardizes connection failures to CONNECTION_FAILED constant.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A rabbit hops through network gates,
Blocking what the guard deems irate—
No loopback, no metadata to taste,
Just safe DNS, no rebinding haste!
Schemas validate what comes through the door,
SSRF protection forevermore! 🔒

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary security fix: blocking SSRF vulnerabilities in remote connection endpoints.
Description check ✅ Passed The description is comprehensive, covering motivation (the SSRF/path-traversal vulnerability), approach (validation strategy and implementation), and testing evidence (build checks and 41 unit tests).
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-ssrf-remote-connection-endpoints

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

Follow-up hardening of the SSRF fix for the remote-connection endpoints,
addressing maintainer review:

- Validate request bodies with TypeBox (the existing serverroutes pattern)
  instead of ad-hoc checks: host must be a non-empty string, port a number
  or numeric string in range, and requestId a UUID - which subsumes the
  earlier requestId path-traversal guard.
- Return a single generic connection error instead of echoing the raw
  transport error, so the endpoints can no longer be used to map internal
  network topology by error differentiation. The real cause is still logged
  server-side via debug; post-authentication states stay distinct.
- Reflect only the fields the admin UI consumes from remote responses
  (server id/version, access-request state/requestId/permission/token)
  rather than passing the remote body through verbatim.
- Block the NAT64 well-known prefix (64:ff9b::/96) and deprecated
  IPv4-compatible (::/96) IPv6 forms, which otherwise encode loopback and
  metadata addresses past the guard on translating networks.
@dirkwa

dirkwa commented Jun 13, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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