GitHub Security Advisory: GHSA-c2rm-g55x-8hr5
Package: nuxt-og-image (NPM)
Severity: LOW (CVSS 3.7)
CWE: CWE-918: Server-Side Request Forgery (SSRF)
Vulnerable versions: >= 6.2.5, < 6.4.9
Patched in: 6.4.9

Summary: nuxt-og-image SSRF — bypass of GHSA-pqhr-mp3f-hrpp / v6.2.5 fix (IPv6 + redirect)

Details:
## Summary

The `isBlockedUrl()` denylist introduced in `nuxt-og-image@6.2.5` to remediate **GHSA-pqhr-mp3f-hrpp** (Dmitry Prokhorov / Positive Technologies, March 2026) is incomplete. The patch advisory states "Decimal/hexadecimal IP encoding bypasses are also handled" — that part is true (Node's WHATWG URL parser canonicalizes those forms before validation), but the v6.2.5 implementation misses two independent surfaces in the latest release `6.4.8`:

1. **IPv6 prefix list is incomplete.** The IPv6 branch checks only `bare === "::1" || startsWith("fc") || startsWith("fd") || startsWith("fe80")`. It misses:
   - `[::ffff:7f00:1]` — IPv6-mapped IPv4 loopback in pure-hex form (RE_MAPPED_V4 regex requires dotted-quad). **Reaches 127.0.0.1 on a single-stack-IPv4 host with no other primitive needed.**
   - `[fec0::/10]` (RFC 3879 site-local — deprecated but still routable on legacy networks)
   - `[5f00::/16]` (RFC 9602 SRv6 SIDs)
   - `[3fff::/20]` (RFC 9637 IPv6 documentation v2)
   - `[64:ff9b:1::/48]` (RFC 8215 NAT64 local-use, including embedded IPv4 loopback `[64:ff9b:1::7f00:1]`)

2. **No redirect re-validation.** `isBlockedUrl` runs once on the initial `<img src>`. The subsequent `$fetch(decodedSrc, ...)` (ofetch, default redirect-follow) follows 30x responses with no second-pass validation. Any allowed origin that returns a 302 to an internal IP — S3 redirect rules, GCS, Azure, CloudFront, any user-content CDN where the attacker can place a single redirect — completes the SSRF.

The net result is that the v6.2.5 SSRF advisory is bypassable in two distinct ways. The same root family as #29 / #38 (ipx) but in a **different code path with different gaps** — `nuxt-og-image` does not delegate to `ipx`, it ships its own validator, and that validator has fresh issues that survived the prior fix.

## Affected

| Package          | Version           | Role                                                |
|------------------|-------------------|-----------------------------------------------------|
| `nuxt-og-image`  | `6.4.8` (latest)  | default OG-image generator for Nuxt apps            |
| `@nuxtjs/og-image` (alias) | same          | re-export, same code path                            |

The vulnerable code lives in `dist/runtime/server/og-image/core/plugins/imageSrc.js` and is enforced for every `<img src>` (and `style="background-image: url(...)"`) inside an OG image component, on production builds (`!import.meta.dev`).

## Vulnerable code (`imageSrc.js`, verbatim)

```js
function isPrivateIPv4(a, b) {
  if (a === 127) return true;
  if (a === 10) return true;
  if (a === 172 && b >= 16 && b <= 31) return true;
  if (a === 192 && b === 168) return true;
  if (a === 169 && b === 254) return true;
  if (a === 0) return true;
  return false;
}
function isBlockedUrl(url) {
  let parsed;
  try { parsed = new URL(url); } catch { return true; }
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return true;
  const hostname = parsed.hos