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.0376 → 169.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:
0177means 177 in some parsers and 127 in others—never trust a single parser's interpretation for security decisions. - The
ip-addressnpm package at 10.2.0 had a critical parsing differential in itsAddress4class that made SSRF bypass trivial with crafted octal-notation addresses. - Peer dependencies can introduce high-severity vulnerabilities silently—the vulnerable
ip-addressversion 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/sdkor@hono/node-serverrequest handlers. - Sink: The
Address4constructor inip-address10.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-addressfrom 10.2.0 to 10.3.1, which eliminates the decimal/octal parsing inconsistency inAddress4, 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.