Back to Blog
high SEVERITY6 min read

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.

O
By Orbis AppSec
•Published August 11, 2026•Reviewed August 11, 2026

Answer Summary

CVE-2026-69192 is a high-severity SSRF vulnerability in the Node.js `ip-address` package (versions before 10.3.1) caused by inconsistent parsing of leading-zero IP octets—the library interprets them as decimal while system resolvers treat them as octal (CWE-918). This mismatch allows attackers to craft IP addresses that appear safe to validation but resolve to internal/blocked addresses. The fix involves upgrading `ip-address` to 10.3.1 via npm overrides to ensure consistent octal-aware parsing.

Vulnerability at a Glance

cweCWE-918
fixUpgrade ip-address from 10.2.0 to 10.3.1 using npm overrides
riskAttackers can bypass IP blocklists to access internal services or restricted endpoints
languageJavaScript/Node.js
root causeip-address library decoded leading-zero octets as decimal while resolvers decode them as octal
vulnerabilityServer-Side Request Forgery (SSRF) via IP Address Parsing Inconsistency

Introduction

In a Node.js application's dependency tree, we discovered a high-severity SSRF vulnerability lurking in the ip-address package at version 10.2.0. The package-lock.json file locked this vulnerable version, which handles IP address parsing and validation—a critical security boundary for any application that makes outbound requests based on user input.

The vulnerability, tracked as CVE-2026-69192, exploits a subtle but dangerous inconsistency: when you write an IP address like 0177.0.0.1, the ip-address library's Address4 class interprets those leading zeros as decimal notation, seeing it as 177.0.0.1. But when your operating system's resolver processes that same address, it interprets the leading zero as octal notation—meaning 0177 becomes 127 in decimal. The result? Your validation says "safe external IP," but the actual request goes to 127.0.0.1—localhost.

This matters for any developer using IP validation to protect against SSRF attacks, which is essentially everyone building applications that fetch URLs or connect to user-specified addresses.

The Vulnerability Explained

How Octal IP Parsing Works (And Doesn't)

IP addresses have a lesser-known feature: octets with leading zeros can be interpreted as octal numbers. This is a POSIX standard behavior that most system resolvers follow:

0177.0.0.1  →  Octal: 127.0.0.1 (localhost!)
0300.0.0.1  →  Octal: 192.0.0.1
010.0.0.1   →  Octal: 8.0.0.1

The vulnerable ip-address version 10.2.0 ignored this convention entirely. Its Address4 class parsed all octets as decimal, regardless of leading zeros:

// How ip-address 10.2.0 parsed addresses (simplified)
// Input: "0177.0.0.1"
// Library sees: 177.0.0.1 (decimal interpretation)
// System resolver sees: 127.0.0.1 (octal interpretation)

The Attack Scenario

Imagine your application has SSRF protection that blocks requests to internal IP ranges:

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

function isBlockedIP(ipString) {
  const addr = new Address4(ipString);
  // Block localhost, private ranges, etc.
  if (addr.isInSubnet(new Address4('127.0.0.0/8'))) return true;
  if (addr.isInSubnet(new Address4('10.0.0.0/8'))) return true;
  if (addr.isInSubnet(new Address4('192.168.0.0/16'))) return true;
  return false;
}

// Attacker submits: "0177.0.0.1"
isBlockedIP("0177.0.0.1");  // Returns FALSE (sees 177.0.0.1)
// But when the request is made...
fetch("http://0177.0.0.1/admin/secrets");  // Actually hits 127.0.0.1!

An attacker could use this to:
- Access internal admin panels on localhost
- Reach cloud metadata endpoints (like AWS's 169.254.169.254 via 0251.0376.0251.0376)
- Probe internal network services that should be unreachable
- Exfiltrate data from internal APIs

Real-World Impact

This application uses Firebase Admin SDK and Stripe—both of which handle sensitive data. If any component validates user-supplied URLs or IP addresses before making requests (common in webhook validation, proxy functionality, or URL preview features), this parsing inconsistency could allow attackers to bypass those protections and access internal services or sensitive endpoints.

The Fix

The fix implemented in this PR is elegant in its simplicity: upgrade the ip-address package from 10.2.0 to 10.3.1, where the maintainers corrected the octal parsing behavior.

What Changed in package.json

// Before
{
  "dependencies": {
    "firebase-admin": "^13.10.0",
    "stripe": "^22.3.0",
    "supercompress-proxy": "^0.5.17"
  }
}

// After
{
  "dependencies": {
    "firebase-admin": "^13.10.0",
    "stripe": "^22.3.0",
    "supercompress-proxy": "^0.5.17"
  },
  "overrides": {
    "ip-address": "10.3.1"
  }
}

Why Use npm Overrides?

The ip-address package isn't a direct dependency—it's a transitive dependency somewhere in the dependency tree (likely through supercompress-proxy or another package). The overrides field in package.json forces npm to use version 10.3.1 regardless of what version the parent packages request.

What Changed in package-lock.json

// Before
"node_modules/ip-address": {
  "version": "10.2.0",
  "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
  "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA=="
}

// After
"node_modules/ip-address": {
  "version": "10.3.1",
  "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.3.1.tgz",
  "integrity": "sha512-1e9d3kb97NHJTIJDZW9rKqW2h6+dFa50Dy0fpPSMQp2ADje5gvKsXmdiK6dwY5t76TaTt5+P5N1Y/LoToIxP6g=="
}

How Version 10.3.1 Fixes the Issue

The patched version now correctly interprets leading-zero octets as octal, matching the behavior of system resolvers:

// ip-address 10.3.1 behavior
const { Address4 } = require('ip-address');

// Input: "0177.0.0.1"
// Now correctly parsed as: 127.0.0.1 (octal interpretation)
// Validation and resolution are now CONSISTENT

This means your SSRF blocklist will correctly identify 0177.0.0.1 as localhost and block the request before it's made.

Key Takeaways

  • Octal IP notation is a real attack vector: The obscure 0177.0.0.1 syntax can bypass naive IP validation in any language where the validation library and system resolver disagree on interpretation.

  • Transitive dependencies carry risk: The ip-address vulnerability wasn't in direct dependencies but hidden in the dependency tree—npm overrides is the correct mechanism to force upgrades.

  • SSRF protection requires consistency: Your validation logic must interpret IP addresses exactly as the component making the actual request will interpret them.

  • The supercompress-proxy dependency path (or similar) pulled in the vulnerable ip-address version—always audit your full dependency tree, not just direct dependencies.

  • Version 10.3.1 specifically addresses octal parsing: This isn't just a general security update; it's a targeted fix for the decimal-vs-octal interpretation mismatch.

How Orbis AppSec Detected This

  • Source: User-controlled URL or IP address input flowing through the application's request handling
  • Sink: The ip-address library's Address4 class used for IP validation before outbound requests
  • Missing control: Consistent octal-aware IP parsing that matches system resolver behavior
  • CWE: CWE-918 (Server-Side Request Forgery)
  • Fix: Upgraded ip-address from 10.2.0 to 10.3.1 via npm overrides to ensure consistent IP address interpretation

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 can completely undermine security controls. The ip-address library's decimal interpretation of leading-zero octets, while technically valid in isolation, created a dangerous mismatch with how operating systems actually resolve these addresses.

The fix was straightforward—a version upgrade via npm overrides—but the vulnerability itself highlights the importance of understanding the full data flow in your applications. When validating IP addresses for security purposes, ensure your validation library interprets addresses exactly as the downstream components will.

For Node.js developers: audit your dependencies for ip-address versions below 10.3.1, and consider implementing defense-in-depth SSRF protections that don't rely solely on pre-request IP validation.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #52

Related Articles

high

fetch() with Automatic Redirects Leaks HTTPS Trust to api.tomys.top

The `withProxy()` fetch wrapper allowed automatic HTTP redirects from `api.tomys.top`, trusting the external API's domain integrity without validating final destinations. A compromised or malicious redirect response could send users to attacker-controlled phishing sites while maintaining HTTPS protocol and hostname checks that falsely appear secure.

high

Meta.get() Honored @uploadURL From Script Headers: SSRF Fix

A userscript manager's metadata parser accepted the `@uploadURL` directive from a script's own header block, so any installed or auto-updated script could silently redirect the options-page `uploadScript()` fetch to an attacker-chosen destination — including loopback, link-local metadata endpoints, and LAN addresses. The fix makes `Meta.get()` ignore `@uploadURL` when it appears in a script header, leaving the User Metadata field (validated in `Meta.getUserMeta()`) as the only way to set it, and

high

Unvalidated thumbnailUrl Passed to axios.get(): SSRF Risk

A server-side image helper passes a `thumbnailUrl` value — sourced from 3D-print file metadata returned by an OctoPrint instance — straight into `axios.get()` with no scheme, host, or IP validation. Anyone who can write that metadata (a crafted G-code file, or a compromised/spoofed OctoPrint endpoint) turns the application into an HTTP proxy for internal networks and cloud metadata services. The accompanying pull request bumps `react-router-dom` from 7.9.1 to 7.18.4 to pick up the CVE-2026-21884

high

esearch() SSRF: requests.get() Trusted Any Host in the URL

A citation format-conversion script used by an AI research skill built HTTP URLs from user-supplied PMIDs, DOIs, arXiv IDs, and free-text queries, then passed the resulting string straight to `requests.get()` with no check that it still pointed at an intended API host. The fix introduces an `ALLOWED_HOSTS` set containing the three real upstream APIs and an `_is_allowed_url()` helper that compares `urlparse(url).hostname` against it before the request is issued. This closes a CWE-918 server-side

high

updateCardBg() Follows Unvalidated 302 Location Headers

A background-image updater fetched a configured image URL with manual redirect handling and then re-issued the request to whatever `Location` header came back, with no scheme or host checks. A redirect to `http://169.254.169.254/` or `http://127.0.0.1:<port>/` would have been followed with the original fetch options attached, and the response body written to disk as an image asset. The fix resolves the redirect target against `imgDownloadUrl` and rejects anything that is not HTTPS on the same ho

high

stream_media_file SSRF: src Parameter Reaches requests.get()

A media-download helper accepted a fully attacker-controlled URL from the `src` query parameter and passed it straight to `requests.get()`, turning the service into an open HTTP proxy for internal networks and cloud metadata endpoints. The fix introduces an `assert_safe_url()` guard that resolves the hostname with `getaddrinfo()` and rejects private, loopback, link-local, reserved, and multicast addresses before any request is issued. The guard is now called at the top of both `download_media_fi