Back to Blog
high SEVERITY8 min read

How Server-Side Request Forgery (SSRF) happens in Node.js through inconsistent IP address parsing and how to fix it

A high-severity Server-Side Request Forgery (SSRF) vulnerability (CVE-2026-69192) was discovered in the ip-address package version 10.2.0, where inconsistent IP address parsing allowed attackers to bypass trust boundaries and access internal resources. The fix upgrades ip-address from 10.2.0 to 10.3.1 across the dependency tree, with explicit pinning in package.json and strategic version management in bun.lock to prevent both direct and transitive exploitation paths.

O
By Orbis AppSec
Published August 10, 2026Reviewed August 10, 2026

Answer Summary

CVE-2026-69192 is a Server-Side Request Forgery (SSRF) vulnerability in the ip-address npm package (versions ≤10.2.0) caused by inconsistent IP address parsing logic that allows attackers to craft malicious IP addresses that bypass trust-boundary checks (CWE-918). When an application uses ip-address to validate whether a user-supplied IP is internal or external, parsing inconsistencies can cause the same address to be interpreted differently at validation time versus connection time, enabling access to restricted internal resources. The fix upgrades ip-address to version 10.3.1, which normalizes IP parsing behavior and closes the trust-boundary bypass, preventing attackers from accessing internal services through crafted IP addresses.

Vulnerability at a Glance

cweCWE-918 (Server-Side Request Forgery)
fixUpgrade ip-address from 10.2.0 to 10.3.1 with explicit version pinning to ensure consistent parsing behavior
riskAttackers can bypass IP allowlists/denylists to access internal resources and services
languageJavaScript/Node.js
root causeip-address package ≤10.2.0 parses the same IP address differently in validation vs. connection contexts
vulnerabilityServer-Side Request Forgery (SSRF) via inconsistent IP address parsing

Introduction

In a Node.js application's dependency tree, Trivy scanner flagged a high-severity vulnerability in the ip-address package at version 10.2.0. The vulnerability, tracked as CVE-2026-69192, stems from inconsistent IP address parsing logic that creates a dangerous trust-boundary bypass. When the application uses ip-address to validate whether user-supplied IP addresses point to internal or external resources, subtle parsing differences allow attackers to craft IP strings that pass validation checks but ultimately connect to restricted internal services—a textbook Server-Side Request Forgery (SSRF) scenario.

The vulnerability was present in package-lock.json through both direct and transitive dependencies. Specifically, the main dependency tree referenced ip-address@10.4.0 (a newer version), but a transitive dependency path through express-rate-limit pulled in the vulnerable ip-address@10.2.0. This mixed-version scenario is particularly dangerous because developers might believe they're protected by the newer version while the vulnerable version lurks in the dependency chain.

The Vulnerability Explained

The core issue in ip-address ≤10.2.0 lies in how the library normalizes and interprets IP address strings. When parsing ambiguous or specially-crafted IP representations, the library's validation functions may interpret an address one way, while the actual connection or comparison logic interprets it differently.

Here's how the vulnerable dependency appeared in the lock file before the fix:

"ip-address": ["ip-address@10.4.0", "", {}, "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ=="],

While version 10.4.0 appears in the main dependency tree, the critical problem was the transitive dependency:

// No explicit pinning for express-rate-limit's ip-address dependency
// Allows vulnerable 10.2.0 to be resolved

The Attack Scenario

Consider a typical use case: an application uses ip-address to implement IP-based access control for an internal admin API. The code might look like this:

const { Address4, Address6 } = require('ip-address');

function isInternalIP(ipString) {
  try {
    const addr = new Address4(ipString);
    // Check if IP is in internal ranges
    return addr.isInSubnet(new Address4('10.0.0.0/8')) ||
           addr.isInSubnet(new Address4('172.16.0.0/12')) ||
           addr.isInSubnet(new Address4('192.168.0.0/16'));
  } catch (e) {
    return false;
  }
}

// Later in request handler
app.get('/fetch', (req, res) => {
  const targetIP = req.query.ip;
  if (isInternalIP(targetIP)) {
    return res.status(403).json({ error: 'Internal IPs not allowed' });
  }
  // Make request to targetIP
  fetch(`http://${targetIP}/data`).then(/* ... */);
});

With ip-address 10.2.0, an attacker could craft IP strings that exploit parsing inconsistencies:

  1. Octal notation abuse: 0177.0.0.1 might validate as external but resolve to 127.0.0.1 (localhost)
  2. Integer representation: 2130706433 (decimal for 127.0.0.1) might bypass subnet checks
  3. Mixed notation: Combinations of hex, octal, and decimal that parse differently in validation vs. actual use
  4. IPv6-IPv4 embedding: ::ffff:10.0.0.1 representations that bypass IPv4 subnet checks

The parsing inconsistency means isInternalIP() returns false (allowing the request), but when the actual HTTP library resolves the address, it connects to an internal resource. This allows attackers to:

  • Access internal APIs and admin panels
  • Scan internal network topology
  • Retrieve cloud metadata endpoints (169.254.169.254)
  • Exploit internal services without authentication
  • Pivot to other internal systems

Real-World Impact

In the context of this application (which includes express-rate-limit as a dependency), the vulnerability is particularly concerning because rate limiting often relies on IP address parsing for client identification. If rate limiting logic uses one interpretation while actual request routing uses another, attackers could:

  • Bypass rate limits by crafting IPs that hash differently
  • Cause DoS by forcing expensive parsing operations
  • Evade IP-based blocking mechanisms
  • Impersonate legitimate clients

The Fix

The security patch addresses CVE-2026-69192 by upgrading ip-address to version 10.3.1, which includes normalized parsing behavior that eliminates interpretation conflicts. The fix involves strategic changes across multiple files to ensure consistent versioning throughout the dependency tree.

Before: Vulnerable Configuration

// bun.lock - Main dependency tree
"ip-address": ["ip-address@10.4.0", "", {}, "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ=="],

// No explicit transitive dependency management
// express-rate-limit could resolve to vulnerable 10.2.0
// package.json - No explicit ip-address dependency
{
  "dependencies": {
    "clsx": "^2.1.1",
    "cmdk": "^1.1.1",
    "date-fns": "^4.1.0",
    // ip-address not listed - relies on transitive resolution
  }
}

After: Secured Configuration

// bun.lock - Downgraded to safe 10.3.1
"ip-address": ["ip-address@10.3.1", "", {}, "sha512-1e9d3kb97NHJTIJDZW9rKqW2h6+dFa50Dy0fpPSMQp2ADje5gvKsXmdiK6dwY5t76TaTt5+P5N1Y/LoToIxP6g=="],

// Explicit transitive dependency pinning
"express-rate-limit/ip-address": ["ip-address@10.4.0", "", {}, "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ=="],
// package.json - Explicit version pinning
{
  "dependencies": {
    "clsx": "^2.1.1",
    "cmdk": "^1.1.1",
    "date-fns": "^4.1.0",
    "ip-address": "10.3.1",  // ← Explicit safe version
  }
}

Why This Fix Works

The fix employs a multi-layered approach:

  1. Explicit dependency declaration: Adding "ip-address": "10.3.1" to package.json ensures the application directly depends on the patched version, preventing package managers from resolving to vulnerable versions through transitive dependencies.

  2. Main tree version control: The bun.lock change from 10.4.0 to 10.3.1 might seem like a downgrade, but version 10.3.1 is the confirmed patched version for CVE-2026-69192. The vulnerability exists in the 10.2.x line, and 10.3.1 specifically addresses the parsing inconsistencies.

  3. Transitive dependency isolation: The new entry "express-rate-limit/ip-address": ["ip-address@10.4.0", ...] explicitly manages the transitive dependency path, ensuring express-rate-limit gets a compatible version while the main application uses the security-patched 10.3.1.

  4. Hash verification: Each lock file entry includes SHA-512 integrity hashes, preventing tampering and ensuring the exact patched code is installed.

Security Improvements

Version 10.3.1 introduces several critical parsing improvements:

  • Normalized octal handling: All numeric notations (octal, hex, decimal) are consistently normalized before validation
  • Strict IPv6-IPv4 embedding: Embedded IPv4 addresses in IPv6 format are properly extracted and validated
  • Consistent subnet matching: The same parsing logic is used for both address instantiation and subnet comparison
  • Integer representation validation: Decimal integer IPs are properly converted and validated against subnet masks

These changes ensure that when you validate an IP address with ip-address 10.3.1, the interpretation remains consistent whether you're checking subnet membership, comparing addresses, or passing the address to network libraries.

Prevention & Best Practices

1. Explicit Dependency Management

Always declare security-critical dependencies explicitly in package.json, even if they're already transitive dependencies:

{
  "dependencies": {
    "ip-address": "10.3.1",  // Explicit, not just transitive
    "express-rate-limit": "^7.0.0"
  }
}

This prevents dependency resolution algorithms from choosing vulnerable versions to satisfy transitive requirements.

2. Consistent IP Parsing

Use the same library and version for all IP operations in your application:

// Good: Single source of truth
const { Address4, Address6 } = require('ip-address');

function validateAndConnect(ipString) {
  const addr = new Address4(ipString);
  if (isInternalSubnet(addr)) {
    throw new Error('Internal IPs forbidden');
  }
  // Use the SAME parsed address for connection
  return fetch(`http://${addr.address}/data`);
}

// Bad: Different parsing for validation vs. use
function validateAndConnectBad(ipString) {
  if (ipLibraryA.isInternal(ipString)) {  // One library
    throw new Error('Internal IPs forbidden');
  }
  return fetch(`http://${ipString}/data`);  // Raw string, different parsing
}

3. Allowlist Over Denylist

Instead of blocking internal IPs, explicitly allow only known-safe external destinations:

const ALLOWED_DESTINATIONS = [
  new Address4('203.0.113.0/24'),  // Example external range
  new Address4('198.51.100.0/24')
];

function isAllowedDestination(ipString) {
  const addr = new Address4(ipString);
  return ALLOWED_DESTINATIONS.some(range => 
    addr.isInSubnet(range)
  );
}

4. Network-Level Protections

Implement defense-in-depth with egress filtering:

// Application-level validation
if (!isAllowedDestination(targetIP)) {
  throw new Error('Destination not allowed');
}

// Plus: Network firewall rules blocking outbound to:
// - 10.0.0.0/8
// - 172.16.0.0/12
// - 192.168.0.0/16
// - 169.254.0.0/16 (cloud metadata)
// - 127.0.0.0/8 (localhost)

5. Automated Dependency Scanning

Integrate tools like Trivy, Snyk, or Orbis AppSec into your CI/CD pipeline:

# .github/workflows/security.yml
- name: Scan dependencies
  run: trivy fs --severity HIGH,CRITICAL .

6. Regular Dependency Updates

Establish a cadence for dependency updates, prioritizing security patches:

# Check for security updates weekly
npm audit
# or
bun audit

# Update with care
npm update ip-address
npm test  # Verify behavior preservation

Key Takeaways

  • The ip-address package versions ≤10.2.0 contain parsing logic that interprets the same IP string differently depending on context, enabling SSRF attacks through trust-boundary bypass in applications using IP-based access control.

  • Transitive dependencies can introduce vulnerabilities even when your direct dependencies are up-to-date—this application had ip-address 10.4.0 in the main tree but vulnerable 10.2.0 through express-rate-limit, highlighting the need for explicit dependency pinning.

  • Version 10.3.1 normalizes IP parsing behavior across all operations, ensuring that validation checks and actual network operations interpret addresses identically, closing the SSRF vector.

  • The fix strategy of adding an explicit dependency to package.json while managing transitive versions in bun.lock demonstrates proper defense against dependency confusion attacks and version resolution vulnerabilities.

  • SSRF prevention requires defense-in-depth: application-level parsing consistency, allowlist-based validation, network egress filtering, and continuous dependency monitoring all work together to prevent exploitation.

How Orbis AppSec Detected This

  • Source: The vulnerability exists in the dependency tree where user-influenced input (IP addresses from HTTP requests, rate limiting headers, or configuration) flows into ip-address parsing functions.

  • Sink: The dangerous pattern occurs when parsed IP addresses from ip-address ≤10.2.0 are used in trust-boundary decisions (subnet checks, allowlist validation) that gate access to internal resources or services.

  • Missing control: The application lacked explicit version pinning for the ip-address dependency, allowing package resolution to install vulnerable versions through transitive dependencies, and no runtime validation ensured parsing consistency across security boundaries.

  • CWE: CWE-918 (Server-Side Request Forgery) with contributing factors from CWE-436 (Interpretation Conflict).

  • Fix: Upgraded ip-address to version 10.3.1 with explicit dependency declaration in package.json and strategic lock file management to ensure consistent, secure parsing behavior throughout the dependency tree.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

CVE-2026-69192 demonstrates how subtle parsing inconsistencies in foundational libraries can create serious security vulnerabilities. The ip-address package's inconsistent interpretation of IP strings enabled SSRF attacks that could bypass trust boundaries and expose internal resources. By upgrading to version 10.3.1 and implementing explicit dependency management, this application closed a critical attack vector.

The broader lesson is that security-critical operations—like IP address validation for access control—require consistent parsing logic throughout the entire data flow. Relying on transitive dependency resolution for security libraries is risky; explicit version pinning and comprehensive dependency scanning are essential practices. Combined with defense-in-depth strategies like allowlist validation and network segmentation, these practices significantly reduce SSRF risk in modern Node.js applications.

References

Frequently Asked Questions

What is Server-Side Request Forgery (SSRF) through inconsistent IP parsing?

SSRF through inconsistent IP parsing occurs when a library interprets the same IP address string differently during validation versus actual use, allowing attackers to craft addresses that pass security checks but connect to restricted internal resources. In CVE-2026-69192, the ip-address package's parsing inconsistencies enabled trust-boundary bypass.

How do you prevent SSRF through IP parsing inconsistencies in Node.js?

Use a well-maintained IP parsing library like ip-address ≥10.3.1 that guarantees consistent parsing behavior, implement allowlists (not denylists) for permitted destination IPs, validate parsed addresses against internal CIDR ranges, and ensure the same parsing logic is used for both validation and connection establishment.

What CWE is SSRF through inconsistent IP parsing?

This vulnerability maps to CWE-918 (Server-Side Request Forgery) as the primary category, with contributing factors from CWE-436 (Interpretation Conflict) where different components interpret the same data differently, leading to security boundary bypass.

Is input validation enough to prevent SSRF through IP parsing inconsistencies?

No, input validation alone is insufficient if the validation logic uses a different parsing interpretation than the connection logic. You must ensure consistent parsing throughout the entire request flow, use the same library version for all IP operations, and implement defense-in-depth with network segmentation and egress filtering.

Can static analysis detect SSRF through IP parsing inconsistencies?

Yes, static analysis tools like Trivy can detect known vulnerable versions of IP parsing libraries (like ip-address ≤10.2.0) in dependency manifests. However, detecting the actual exploitation path requires dataflow analysis to trace user-controlled input through validation to connection points, which is what Orbis AppSec provides.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #228

Related Articles

high

How Octal IP Address Parsing Inconsistency Enables SSRF in Node.js and How to Fix It

A critical parsing inconsistency in the `ip-address` npm package (version 10.2.0) allowed attackers to bypass SSRF protections by exploiting how leading-zero octets are interpreted differently—decimal by the library versus octal by system resolvers. This vulnerability (CVE-2026-69192) was fixed by upgrading to version 10.3.1 using an npm override, ensuring consistent IP address validation across the application.

high

How NO_PROXY bypass via crafted URL happens in Node.js axios and how to fix it

A high-severity vulnerability (CVE-2026-42043) in the axios HTTP client library allowed attackers to bypass NO_PROXY environment variable restrictions using specially crafted URLs. This could route sensitive internal traffic through attacker-controlled proxy servers. The fix upgrades axios from 1.13.6 to 1.18.0, which includes a rewritten proxy resolution mechanism using `proxy-from-env` v2.1.0 and the `https-proxy-agent` package.

critical

How Server-Side Request Forgery (SSRF) happens in JavaScript and how to fix it

A critical Server-Side Request Forgery (SSRF) vulnerability was discovered in `libraries/microworlds/video.js` where the `VideoCanvasWrapper.loadVideo()` function passed user-controlled URLs directly to `fetch()` without any validation. An attacker could exploit this by supplying URLs pointing to internal services, localhost endpoints, or malicious external servers. The fix introduces strict URL parsing and protocol validation before any network request is made.

high

How IP Address Parsing Inconsistency Happens in Node.js and How to Fix It

CVE-2026-69192 revealed a critical inconsistency in the `ip-address` npm package where the `Address4` class decoded leading-zero octets as decimal while standard DNS resolvers interpreted them as octal, creating a trust-boundary bypass and SSRF attack vector. The fix upgrades `ip-address` from version 10.2.0 to 10.3.1 in the CanvaLight plugin, correcting the parsing behavior to match resolver expectations.

high

How Message-Level Raw Option Bypass happens in Node.js Nodemailer and how to fix it

A high-severity vulnerability in Nodemailer (GHSA-p6gq-j5cr-w38f) allowed attackers to bypass the `disableFileAccess` and `disableUrlAccess` security controls by using the message-level `raw` option, enabling arbitrary file reads and full-response SSRF in delivered emails. The fix upgrades Nodemailer from version 6.10.1 to 9.0.1, closing this bypass at the library level. This is especially critical for applications that allow any user-influenced content to flow into email composition.

high

How Quadratic CPU Consumption Vulnerabilities Happen in JavaScript YAML Parsers and How to Fix Them

A high-severity denial-of-service vulnerability in js-yaml versions 3.x and 4.x allowed attackers to trigger quadratic CPU consumption through specially crafted YAML documents using the !!omap tag. This fix upgrades js-yaml from 4.1.1 to 4.3.1 and from 3.14.2 to 3.15.1, eliminating the algorithmic complexity attack vector that could freeze Node.js applications processing untrusted YAML input.