Back to Blog
high SEVERITY7 min read

How Octal/Decimal IP Parsing Ambiguity happens in JavaScript and how to fix it

CVE-2026-69192 is a high-severity vulnerability in the `ip-address` npm package (versions before 10.3.1) where IPv4 addresses with leading-zero octets — like `010.0.0.1` — are parsed as decimal by the library but interpreted as octal by OS-level resolvers, creating a dangerous mismatch. This discrepancy can allow attackers to bypass IP-based access controls and trust boundaries, potentially enabling Server-Side Request Forgery (SSRF) attacks. Upgrading to `ip-address@10.3.1` in the SAP BW Query

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

Answer Summary

CVE-2026-69192 is a high-severity SSRF and trust-boundary bypass vulnerability (CWE-918) in the `ip-address` npm package before version 10.3.1. The flaw exists because `Address4` parses IPv4 octets with leading zeros (e.g., `010.0.0.1`) as decimal (value: 10), while OS resolvers and many HTTP stacks interpret them as octal (value: 8). An attacker can craft an address that passes an allowlist check in application code but resolves to a different, restricted IP at the network layer. The fix is to upgrade `ip-address` to 10.3.1, which normalizes or rejects leading-zero octets so that parsed and resolved addresses always agree.

Vulnerability at a Glance

cweCWE-918 (Server-Side Request Forgery)
fixUpgrade ip-address from 10.2.0 to 10.3.1 and pin via package.json overrides
riskAttackers bypass IP allowlists to reach internal services
languageJavaScript / Node.js
root causeip-address Address4 treats leading-zero octets as decimal; resolvers treat them as octal
vulnerabilityOctal/Decimal IPv4 Parsing Ambiguity leading to SSRF

How Octal/Decimal IP Parsing Ambiguity Happens in JavaScript and How to Fix It

Introduction

The plugins/sap-bw-query/mcp/ component handles MCP (Model Context Protocol) server logic for SAP BW Query integration — a context where outbound connections are made based on configuration and potentially user-influenced input. Buried in its dependency tree, ip-address@10.2.0 contained a subtle but dangerous flaw: its Address4 class decoded IPv4 octets with leading zeros as decimal, while every major OS resolver and many HTTP stacks decode them as octal. The result is that the string 010.0.0.1 means two completely different things depending on who is reading it — and attackers can exploit that gap to slip past IP-based access controls entirely.

This post walks through exactly what went wrong, how the exploit works, and what the upgrade to ip-address@10.3.1 actually fixes.


The Vulnerability Explained

A Tale of Two Parsers

IPv4 addresses like 010.0.0.1 look innocuous, but the leading zero carries a loaded meaning in C-style numeric literals: it signals octal notation. So 010 in octal is 8 in decimal, making 010.0.0.1 resolve to 8.0.0.1 at the OS level — not 10.0.0.1.

The vulnerable ip-address@10.2.0 library's Address4 class did not apply this octal rule. When application code called:

// ip-address 10.2.0 — VULNERABLE behavior
const { Address4 } = require('ip-address');
const addr = new Address4('010.168.1.1');
console.log(addr.toArray()); // [10, 168, 1, 1]  ← decimal interpretation

The library returned 10.168.1.1. But when that same string was handed to Node's dns.lookup(), http.request(), or the underlying libc resolver, the OS parsed 010 as octal and connected to 8.168.1.1 instead.

The Dangerous Mismatch

Consider a typical SSRF-prevention pattern:

// Simplified allowlist check using ip-address 10.2.0
const { Address4 } = require('ip-address');

function isSafeDestination(ipString) {
  const addr = new Address4(ipString);
  const numeric = addr.bigInteger(); // computed from decimal-parsed octets
  // Check: is this address in the public internet range?
  return !isInternalRange(numeric); // passes for "010.168.1.1" → treats as 10.168.1.1
}

// Later, the application actually connects:
fetch(`http://${ipString}/api/data`); // OS resolves "010.168.1.1" → 8.168.1.1

The validation says "safe" because it computed 10.168.1.1. The actual TCP connection goes to 8.168.1.1. If 8.168.1.1 is an internal service (or a metadata endpoint like 169.254.169.254 via a crafted octal address), the attacker has achieved SSRF.

Constructing a Metadata-Service Bypass

AWS EC2's instance metadata service lives at 169.254.169.254. In octal, that address can be written as 0251.0376.0251.0376. An attacker submitting this string would find that:

  1. ip-address@10.2.0 parses each octet as decimal → 251.376.251.376 (invalid, gets rejected) — but with mixed leading-zero/non-leading-zero octets, more nuanced bypasses become possible.
  2. For simpler cases like 0127.0.0.1 (octal for 87.0.0.1 vs. loopback 127.0.0.1), the library sees 127.0.0.1 (loopback — blocked), while the resolver sees 87.0.0.1 (public — allowed). The direction of the bypass depends on the allowlist logic, but the mismatch is always exploitable.

Real-World Impact for This Component

The SAP BW Query MCP plugin makes outbound calls as part of its query-routing logic. If any part of that pipeline validates a destination address using Address4 before passing it to a network call, an attacker who can influence the destination string can route requests to internal infrastructure — SAP BW backend servers, internal APIs, or cloud metadata endpoints — while the validation layer remains unaware.


The Fix

What Changed

The fix is a two-part change in the plugins/sap-bw-query/mcp/ directory:

1. package-lock.json — version pin updated

 "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==",

2. package.jsonoverrides field added

+  "overrides": {
+    "ip-address": "10.3.1"
+  }

The overrides field is critical. Without it, npm could still resolve a transitive dependency to the vulnerable 10.2.0 even if the direct dependency was updated. By declaring the override, the fix ensures that every copy of ip-address anywhere in the dependency tree is pinned to 10.3.1.

What 10.3.1 Actually Fixes

In ip-address@10.3.1, the Address4 parser was updated to treat leading-zero octets consistently with resolver behavior — either by rejecting them outright as ambiguous or by normalizing them to their octal values before any arithmetic. This means:

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

// Ambiguous leading-zero input is now handled safely:
// Either throws an AddressError, or correctly interprets 010 as 8
const addr = new Address4('010.168.1.1');
// Result is now consistent with what the OS resolver will do

The library and the resolver now agree on what a given string means, eliminating the validation-bypass window entirely.


Prevention & Best Practices

1. Validate After Resolution, Not Before

The most robust SSRF defense resolves the hostname/IP first and then checks the resulting numeric address:

const dns = require('dns').promises;

async function isSafeDestination(host) {
  const { address } = await dns.lookup(host); // get what the OS will actually connect to
  const addr = new Address4(address);         // parse the resolved canonical form
  return !isInternalRange(addr.bigInteger()); // check the real destination
}

This approach is immune to parser-resolver mismatches because validation happens on the resolved address, not the raw input string.

2. Use npm overrides for Transitive Dependency Control

As demonstrated in this fix, package.json overrides (npm v8.3+) let you enforce a minimum version across the entire dependency tree:

{
  "overrides": {
    "ip-address": ">=10.3.1"
  }
}

This is especially important for security fixes in widely-used utility libraries that appear as transitive dependencies.

3. Reject Non-Standard IP Formats at Input

Consider rejecting any IP address string that contains leading zeros before it ever reaches your parsing or networking code:

function rejectAmbiguousOctets(ipString) {
  if (/\b0\d/.test(ipString)) {
    throw new Error('Ambiguous leading-zero octet rejected');
  }
}

4. Run Dependency Scanners in CI

Trivy, Snyk, and npm audit all flag known-vulnerable package versions. Integrate them into your CI pipeline so that vulnerabilities like this are caught before they reach production.

5. OWASP & CWE Guidance

  • OWASP SSRF Prevention Cheat Sheet: always validate the resolved address, not the user-supplied string.
  • CWE-918: Server-Side Request Forgery — the canonical classification for vulnerabilities where an attacker causes a server to make unintended network requests.

Key Takeaways

  • Leading-zero IPv4 octets are a parser trap: 010 means 8 to your OS but 10 to ip-address@10.2.0 — never assume two parsers agree on ambiguous input.
  • String-level IP allowlists are not enough: The mismatch in Address4's decimal interpretation vs. resolver octal interpretation means a validated string can still reach a forbidden destination.
  • The overrides field in package.json is a security tool: Without it, transitive dependency trees can silently pull in the vulnerable 10.2.0 even after a direct-dependency upgrade.
  • SSRF defenses must happen post-resolution: Validate the IP address after DNS/resolver normalization, not on the raw user-supplied string.
  • The SAP BW Query MCP plugin's outbound-connection context makes this high-priority: Any component that routes network requests based on configuration or user input is a prime SSRF target.

How Orbis AppSec Detected This

  • Source: User-influenced or configuration-supplied IP address strings entering the SAP BW Query MCP plugin's connection-routing logic.
  • Sink: Address4 constructor in ip-address@10.2.0 (resolved via node_modules/ip-address in plugins/sap-bw-query/mcp/package-lock.json) used to validate or parse destination addresses before outbound network calls.
  • Missing control: No normalization or rejection of leading-zero octets prior to parsing; no post-resolution re-validation to confirm the parsed and resolved addresses match.
  • CWE: CWE-918 — Server-Side Request Forgery (SSRF).
  • Fix: Upgraded ip-address from 10.2.0 to 10.3.1 and added an overrides entry in package.json to enforce the fix across the full 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 is a sharp reminder that IP address validation is harder than it looks. A string like 010.168.1.1 is not self-evidently dangerous, but it carries a hidden ambiguity that splits application-layer parsers and OS resolvers onto different interpretive paths — and attackers can walk right through that gap. The fix in the SAP BW Query MCP plugin is surgical: upgrading ip-address to 10.3.1 and locking it with overrides ensures that the library and the resolver agree on every address, closing the SSRF window without touching any valid input paths.

For developers working with IP-based access controls in Node.js, the lesson is clear: never trust a pre-resolution string check. Resolve first, validate second, and keep your parsing libraries up to date.


References

Frequently Asked Questions

What is an octal/decimal IP parsing ambiguity vulnerability?

It occurs when one layer of a system (e.g., a validation library) interprets a leading-zero octet in an IPv4 address as decimal while another layer (e.g., the OS resolver) interprets it as octal, so the "same" string resolves to two different IP addresses depending on who is reading it.

How do you prevent octal/decimal IP parsing ambiguity in Node.js?

Use an IP-parsing library that explicitly rejects or normalizes leading-zero octets, pin its version in package.json overrides, and validate addresses after resolution rather than before.

What CWE is octal/decimal IP parsing ambiguity?

CWE-918 (Server-Side Request Forgery) is the primary CWE, because the practical exploit is tricking a server into making requests to unintended internal destinations.

Is checking an IP string against an allowlist enough to prevent SSRF?

No. String-level allowlist checks are insufficient if the parser and the resolver disagree on what the string means. You must either resolve the address first and then check, or use a parser that rejects ambiguous forms.

Can static analysis detect octal/decimal IP parsing ambiguity?

Yes. Dependency scanners like Trivy can flag known-vulnerable versions of ip-address. SAST tools can also flag untrusted input flowing into Address4 without post-resolution validation.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #104

Related Articles

critical

How unvalidated URL input handling happens in SvelteKit with Tauri and how to fix it

A critical vulnerability in `src/routes/+page.svelte` allowed attackers to supply arbitrary URLs—including `http://` and local file paths—through query parameters and drag-drop events, which were then fetched without validation. The fix restricts input to HTTPS-only URLs and removes the dangerous local file fetch path entirely, eliminating both SSRF and local file disclosure attack vectors.

critical

How Server-Side Request Forgery happens in Node.js server.js and how to fix it

A Server-Side Request Forgery (SSRF) vulnerability was discovered in `server.js` and `worker.js`, where user-supplied `config` URL parameters were passed directly to `fetchWithAuth()` without any validation. This allowed attackers to force the application to make requests to internal network addresses, cloud metadata endpoints like `169.254.169.254`, or `file://` URIs. The fix introduces an `isAllowedUrl()` allowlist function that rejects private IP ranges, loopback addresses, and non-HTTP(S) pr

high

How Nodemailer raw option bypass happens in Node.js and how to fix it

A high-severity vulnerability in Nodemailer versions prior to 9.0.1 allowed attackers to bypass the `disableFileAccess` and `disableUrlAccess` security controls using the message-level `raw` option. This bypass enabled arbitrary file reads from the server and full-response Server-Side Request Forgery (SSRF) attacks, potentially exposing sensitive configuration files and internal network resources. The fix involves upgrading Nodemailer from version 8.0.7 to 9.0.1.

critical

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

A critical Server-Side Request Forgery vulnerability in `popup.js` allowed attackers to inject malicious URLs from scraped webpages directly into fetch() calls, potentially accessing internal network resources and AWS metadata endpoints. The fix adds URL validation to ensure only HTTP/HTTPS protocols are used and blocks requests to private IP ranges and localhost addresses.

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 axios versions prior to 1.15.1 allowed attackers to bypass NO_PROXY environment variable restrictions using specially crafted URLs. This meant HTTP requests intended to stay internal could be routed through an attacker-controlled proxy, potentially exposing sensitive data. The fix upgrades axios to version 1.15.1, which correctly validates URLs against NO_PROXY rules.

high

How Denial of Service via Exponential-Time Complexity Happens in Node.js Dependencies and How to Fix It

A high-severity denial of service vulnerability (CVE-2026-14257) was discovered in the brace-expansion package within the zeroshot-oecp Docker container's dependency tree. The vulnerability allows attackers to craft malicious input patterns that trigger exponential-time processing, potentially freezing or crashing Node.js applications. This fix upgrades the nested brace-expansion dependency to version 5.0.9 using a targeted Dockerfile modification.