Skip to content

v0.6.2 — guard the guard

Choose a tag to compare

@siaginw siaginw released this 04 Sep 23:53
· 1 commit to main since this release

SheetDiff v0.6.2 — guard the guard

The latest audit pass attacked v0.6.1's own fixes. It won. Everything it found is fixed and regression-tested.

CRITICAL: the SSRF guard was bypassable

The WHATWG URL parser canonicalizes dotted IPv4-mapped IPv6 literals to hexnew URL("http://[::ffff:127.0.0.1]/").hostname is [::ffff:7f00:1] — so v0.6.1's dotted-only unwrap never fired on a URL-derived hostname. The auditor demonstrated end-to-end, with a live listener, that ::ffff:7f00:1 reached loopback with the guard passing, and that ::ffff:a9fe:a9fe (cloud metadata) sailed through too.

Mapped addresses are now parsed in both spellings; unparseable ::ffff: forms are refused outright; and NAT64 (64:ff9b::/96), 6to4 (2002::/16), and CGNAT (100.64.0.0/10 — Tailscale ranges) joined the block list.

The rebinding window, closed

The guard resolved the hostname, then fetch() resolved it again independently — a rebinding DNS server could pass the check publicly and answer the fetch privately. Plain-http push URLs are now refused unless NOTIFY_ALLOW_PRIVATE_URLS=1 is set: a rebound connection can't complete TLS without a hostname-valid certificate, and LAN-ntfy users already have the opt-in. Response bodies are released so sockets pool promptly.

"Line List FIRST" no longer flips the billing basis

Compilation tabs re-list the working tabs with fewer columns (the real Line List carries 20 vs the PE tabs' 23). With raw position order, a compilation that precedes the tabs it copies owned everything — and the working tabs got classified as its copies. Measured on the real tracker: 208,961 ft placed-since and zero open holes instead of the true 55,683 ft / 28,299.

Ownership now runs richest-tab-first (column count, then position). Same-width sheets behave exactly as before. Verified on the real file: Line List first, last, or absent now all produce identical billing packets.

Also

  • Import-quiet push suppression is per-tab — an import between one tab's base and now silences only that tab's echoes; genuine changes on clean tabs still notify.
  • The 0.6.1 "honesty" fixes actually render now (they didn't): failed test pushes and invalid URLs show their messages on /settings, and an invalid entry never clears a previously saved topic.
  • Pino redaction restores the req.headers paths 0.6.1 dropped, censors err.response.data / err.config.headers, and its comment now tells the truth about wildcard depth.
  • .env.example documents LOG_LEVEL and NOTIFY_ALLOW_PRIVATE_URLS.

394 tests + 7 E2E, all green in CI. Node 22+.