Back to Blog
high SEVERITY6 min read

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

The `ip-address` npm package (version 10.2.0) parsed IPv4 addresses with leading-zero octets as decimal numbers, while operating system resolvers interpret them as octal. This inconsistency (CVE-2026-69192) allows attackers to bypass SSRF protections and trust-boundary checks by crafting IP addresses that appear safe to the library but resolve to internal network addresses. The fix upgrades `ip-address` to version 10.3.1, which correctly rejects or normalizes ambiguous octal notation.

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

Answer Summary

CVE-2026-69192 is a high-severity SSRF and trust-boundary bypass vulnerability in the Node.js `ip-address` npm package (versions prior to 10.3.1). The `Address4` class decodes leading-zero octets as decimal integers while OS-level DNS resolvers decode them as octal, creating a parsing differential (CWE-918). The fix is to upgrade `ip-address` from 10.2.0 to 10.3.1, which eliminates the ambiguous parsing behavior by rejecting or correctly interpreting octal-notation octets.

Vulnerability at a Glance

cweCWE-918
fixUpgrade ip-address from 10.2.0 to 10.3.1
riskAttackers can bypass allow/deny lists to reach internal services
languageJavaScript/Node.js
root causeAddress4 treats leading-zero octets as decimal while resolvers treat them as octal
vulnerabilityServer-Side Request Forgery (SSRF) via IP address parsing inconsistency

Introduction

In the Argo project—a Next.js-based AI SaaS platform—the dependency ip-address at version 10.2.0 introduced a subtle but dangerous parsing inconsistency. The library's Address4 class decoded IPv4 octets with leading zeros (e.g., 0177) as decimal values, while the underlying operating system resolver and most network stacks interpret them as octal. This mismatch, tracked as CVE-2026-69192, means an attacker could craft an IP address like 0177.0.0.01 that the library validates as the harmless address 177.0.0.1, but the actual network request resolves to 127.0.0.1—the loopback interface.

This vulnerability was flagged by Trivy in package-lock.json where ip-address was pinned at version 10.2.0 as a peer dependency. Although the project's PR notes the dependency as "not confirmed reachable," the presence of this library in any request-validation or URL-filtering pipeline creates a high-risk attack surface for SSRF.

The Vulnerability Explained

The Octal Parsing Differential

In most POSIX systems and network stacks, an IPv4 octet with a leading zero is interpreted as an octal number:

0177 (octal) = 127 (decimal)
0300 (octal) = 192 (decimal)
0250 (octal) = 168 (decimal)

However, ip-address version 10.2.0's Address4 class used standard JavaScript parseInt() or equivalent decimal parsing, treating 0177 as simply 177. This creates a parsing oracle:

Input ip-address 10.2.0 sees OS Resolver sees
0177.0.0.01 177.0.0.1 (public) 127.0.0.1 (loopback)
0300.0250.0.01 300.250.0.1 (invalid) 192.168.0.1 (private)
010.0.0.01 10.0.0.1 (private ✓ blocked) 8.0.0.1 (public)

Attack Scenario

Consider a typical SSRF protection pattern in a Node.js application:

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

function isInternalIP(ipString) {
  const addr = new Address4(ipString);
  // Check if the IP is in private ranges
  return addr.isInSubnet(new Address4('127.0.0.0/8')) ||
         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'));
}

// Attacker supplies: "0177.0.0.01"
if (!isInternalIP(userSuppliedIP)) {
  // Library says "177.0.0.1" — not internal, allow the request!
  fetch(`http://${userSuppliedIP}/admin/secrets`);
  // But the OS resolves 0177.0.0.01 → 127.0.0.1 — SSRF to localhost!
}

An attacker targeting the Argo platform could use this to:
1. Access internal metadata endpoints (e.g., cloud provider metadata at 0251.0250.0251.0376169.254.169.254)
2. Reach internal microservices behind the firewall
3. Exfiltrate secrets from the local environment

Why This Is High Severity

The Argo project handles AI agent orchestration and likely makes outbound HTTP requests as part of its MCP (Model Context Protocol) SDK integration (@modelcontextprotocol/sdk). Any URL or IP validation using ip-address 10.2.0 could be bypassed, giving attackers access to internal infrastructure.

The Fix

The fix upgrades ip-address from 10.2.0 to 10.3.1, which correctly handles leading-zero octets by either rejecting them as ambiguous or interpreting them consistently with OS resolver behavior.

Changes Made

1. package.json — Pin the fixed version as a direct dependency:

-    "sharp": "^0.35.0"
+    "sharp": "^0.35.0",
+    "ip-address": "10.3.1"

By adding ip-address as a direct dependency pinned to exactly 10.3.1 (no caret or tilde), the project ensures that regardless of what peer dependencies request, the resolved version will always be the patched one.

2. package-lock.json — Lock the resolved version:

 "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==",
+  "version": "10.3.1",
+  "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.3.1.tgz",
+  "integrity": "sha512-1e9d3kb97NHJTIJDZW9rKqW2h6+dFa50Dy0fpPSMQp2ADje5gvKsXmdiK6dwY5t76TaTt5+P5N1Y/LoToIxP6g==",

How 10.3.1 Fixes the Issue

Version 10.3.1 of ip-address modifies the Address4 parsing logic to:
1. Reject octets with leading zeros as invalid input, or
2. Parse them as octal to match OS resolver behavior

Either approach eliminates the parsing differential. The PR notes that "it only tightens handling of untrusted input and leaves valid inputs unaffected"—meaning standard decimal IPv4 addresses like 192.168.1.1 continue to work identically.

Why a Direct Dependency Pin?

Notice in the original package-lock.json, ip-address was marked as "peer": true—it was pulled in transitively. By adding it as a direct dependency in package.json with an exact version pin ("ip-address": "10.3.1" without ^), the project takes explicit control over which version is resolved, preventing future regressions from transitive dependency updates.

Prevention & Best Practices

1. Defense in Depth for SSRF

Never rely solely on pre-request IP validation. Implement a multi-layer approach:

// Layer 1: Reject ambiguous formats before parsing
if (/^0\d/.test(octet)) {
  throw new Error('Leading zeros in IP octets are not permitted');
}

// Layer 2: Validate the resolved address, not just the input
const resolved = await dns.resolve4(hostname);
if (isPrivateIP(resolved[0])) {
  throw new Error('Resolved to internal address');
}

// Layer 3: Network-level controls (firewall egress rules)

2. Audit Transitive Dependencies

Use npm audit, Trivy, or Snyk to continuously scan your lockfile. The vulnerability existed in a peer dependency—not something directly imported—making it easy to miss in code review.

3. Pin Critical Security Dependencies

For libraries that handle security-sensitive parsing (IP addresses, URLs, certificates), use exact version pins rather than semver ranges to prevent unexpected changes.

4. Relevant Standards

  • OWASP SSRF Prevention Cheat Sheet: Recommends validating resolved IPs, not input strings
  • CWE-918: Server-Side Request Forgery
  • CWE-1389: Incorrect Parsing of Numbers with Different Radixes

Key Takeaways

  • Leading zeros in IPv4 octets are ambiguous: 0177 means 177 in some parsers and 127 in others—never trust a single parser's interpretation for security decisions.
  • The ip-address npm package at 10.2.0 had a critical parsing differential in its Address4 class that made SSRF bypass trivial with crafted octal-notation addresses.
  • Peer dependencies can introduce high-severity vulnerabilities silently—the vulnerable ip-address version was pulled transitively, not directly imported by Argo.
  • Pinning "ip-address": "10.3.1" as a direct dependency overrides the peer dependency resolution and ensures the patched version is always used.
  • Post-resolution validation is essential: Even with a fixed parser, always validate the actual resolved IP address against deny lists before making outbound requests.

How Orbis AppSec Detected This

  • Source: User-influenced input (URLs or IP addresses) entering the application through API endpoints, potentially processed via the @modelcontextprotocol/sdk or @hono/node-server request handlers.
  • Sink: The Address4 constructor in ip-address 10.2.0 (node_modules/ip-address), which parses IPv4 addresses with leading-zero octets as decimal, creating a trust-boundary bypass before outbound HTTP requests.
  • Missing control: No rejection or consistent octal interpretation of leading-zero IPv4 octets; no post-resolution IP validation.
  • CWE: CWE-918 (Server-Side Request Forgery)
  • Fix: Upgraded ip-address from 10.2.0 to 10.3.1, which eliminates the decimal/octal parsing inconsistency in Address4, and pinned it as a direct dependency to prevent transitive regression.

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 is a textbook example of how parsing differentials create security vulnerabilities. The ip-address library and the OS resolver disagreed on what 0177.0.0.01 means, and that disagreement is all an attacker needs to bypass SSRF protections. The fix—upgrading to 10.3.1 and pinning the dependency—is minimal in code changes but critical in security impact.

For any Node.js application that validates IP addresses before making outbound requests, this vulnerability is a reminder: your validator and your resolver must agree on the interpretation of every input, or your security boundary doesn't exist.

References

Frequently Asked Questions

What is an IP address parsing inconsistency vulnerability?

It occurs when two components in a system interpret the same IP address string differently—one may see "0177.0.0.1" as 177.0.0.1 (decimal) while another interprets it as 127.0.0.1 (octal), allowing security checks to be bypassed.

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

Use libraries that strictly reject ambiguous IP formats (like leading zeros), validate resolved addresses against deny lists after DNS resolution, and keep IP-parsing dependencies updated.

What CWE is SSRF?

CWE-918: Server-Side Request Forgery (SSRF). Related CWEs include CWE-1389 (Incorrect Parsing of Numbers with Different Radixes).

Is URL validation enough to prevent SSRF?

No. URL validation alone is insufficient because parsing differentials between validators and resolvers can allow crafted addresses to bypass checks. You must also validate the resolved IP address.

Can static analysis detect IP parsing SSRF?

Yes. Tools like Trivy, Semgrep, and Snyk can detect known vulnerable library versions and flag patterns where user-supplied URLs are fetched without post-resolution validation.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #308

Related Articles

high

How SSRF and Credential Leakage happens in Node.js axios and how to fix it

CVE-2025-27152 is a high-severity vulnerability in axios versions prior to 1.8.2 that allows Server-Side Request Forgery (SSRF) and credential leakage when absolute URLs are passed in requests. By upgrading from the vulnerable `^1.7.4` range (which resolved to `1.7.9`) to the pinned `1.8.2`, the attack surface for intercepting or redirecting authenticated HTTP requests is eliminated. Any Node.js application that passes user-influenced URLs to axios is potentially affected.

critical

How Server-Side Request Forgery happens in Browser Extensions and how to fix it

A Server-Side Request Forgery (SSRF) vulnerability in `offscreen.js` allowed attackers to supply malicious feed URLs that the browser extension would fetch without validation, potentially exposing internal network services including cloud metadata endpoints. The fix introduces a dedicated `validateFeedUrl` utility and disables automatic redirect following, closing the attack vector before requests leave the extension. This kind of vulnerability is especially dangerous in browser extensions becau

critical

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

A critical SSRF vulnerability in `js/cd-player/playlist-importer.js` allowed attacker-controlled URLs from third-party Meting APIs to be stored and later fetched by users' browsers, potentially exposing internal network resources. The fix introduces an `isSafeUrl()` validation function that enforces HTTPS-only URLs before any track audio or cover art URL is accepted into the application. This change closes the attack path without altering the normal playlist import workflow.

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 `functions/stream/createProxyResponse.js`, where the `location` parameter was passed directly to `fetch()` without any URL validation. This allowed attackers to weaponize the proxy function to reach internal network resources, cloud metadata endpoints, and arbitrary external services. The fix adds protocol validation using the `URL` constructor before any fetch operation is performed.

critical

How Unvalidated Update URLs Happen in Node.js Agent Updaters and How to Fix Them

A critical vulnerability in `agent/src/updater.js` allowed an attacker who could modify the agent's configuration to redirect software update downloads to an attacker-controlled server, enabling remote code execution via a crafted tarball. The fix introduces strict hostname validation — including private network awareness — so the updater only fetches from trusted origins. This kind of supply-chain attack vector is easy to overlook but catastrophic in production agent deployments.

high

How Command Injection Happens in Node.js Child Process Calls and How to Fix It

A high-severity command injection vulnerability was discovered in Vite's `shared.js` file where the `gitExec()` function used `execSync()` with string concatenation, allowing potential shell metacharacter injection. The fix replaces `execSync()` with `spawnSync()` and passes Git arguments as an array instead of a shell string, eliminating the injection vector entirely.