Skip to content

[Bug] isPrivateIPv6 misses uncompressed and alternate loopback forms #407

@samzong

Description

@samzong

Problem

isPrivateIPv6 only detects loopback through two literal string comparisons: lower === '::' and lower === '::1'. IPv6 allows multiple equivalent representations of the same address, and two common forms slip through the current check:

  1. Uncompressed: 0000:0000:0000:0000:0000:0000:0000:0001 — valid per RFC 5952, recognized by node:net's isIP as v6, but the literal string comparison fails. The subsequent first & 0xfe00 / first & 0xffc0 masks produce 0, which does not match the ULA (fc00::/7) or link-local (fe80::/10) ranges. Result: returns false → guard bypassed.
  2. Alternate compression: 0::1 — equivalent to ::1, valid, also slips through the literal comparison and fails the mask checks.

Both forms can reach the renderer-triggered safeFetch path via the image URL, meaning a crafted URL like https://[0000:0000:0000:0000:0000:0000:0000:0001]/... bypasses SSRF protection.

Location

File: packages/desktop/src/main/net/ssrf-guard.ts:21-36

function isPrivateIPv6(ip: string): boolean {
  const lower = ip.toLowerCase();
  if (lower === '::' || lower === '::1') return true;
  const first = parseInt(lower.split(':')[0], 16);
  if ((first & 0xfe00) === 0xfc00) return true;
  if ((first & 0xffc0) === 0xfe80) return true;
  // ...
  return false;
}

Fix Approach

Normalize the address to canonical form before comparison. Two options:

  1. Simple: expand the input yourself — split on :, handle the :: zero-run, pad each hextet to 4 hex digits, join. Then compare against 0000:0000:0000:0000:0000:0000:0000:0000 and 0000:0000:0000:0000:0000:0000:0000:0001.
  2. Pragmatic: use Buffer.from(...) with the ipaddr.js library (already transitively available via many packages) to parse into a 16-byte array, then check all-zero or all-zero-except-last-byte-equals-1.

After normalization, extend the check to also catch any address whose upper 112 bits are zero and lower 16 bits are 0 or 1.

Verification

  1. Run pnpm check — must pass.
  2. Unit tests for each of these inputs — all must return true:
    • ::
    • ::1
    • 0:0:0:0:0:0:0:1
    • 0000:0000:0000:0000:0000:0000:0000:0001
    • 0::1
    • 0000::1

Context

  • WG: Observability & DX (security)
  • Priority: Low (good first issue — contained, test-driven fix)
  • Estimated effort: 30-45 minutes

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/dxObservability & DX WGgood first issueGood for newcomerskind/bugCategorizes issue or PR as related to a bugsecuritySecurity related issues

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions