<feed xmlns='http://www.w3.org/2005/Atom'>
<title>coinos-server.git/lib/safe-fetch.ts, branch master</title>
<subtitle>coinos server — legacy coinos.io backend (Lightning, on-chain, Liquid)</subtitle>
<id>https://git.coinos.io/coinos-server.git/atom?h=master</id>
<link rel='self' href='https://git.coinos.io/coinos-server.git/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/'/>
<updated>2026-09-25T22:15:19Z</updated>
<entry>
<title>safe-fetch: decode IPv6 hosts before checking for private addresses</title>
<updated>2026-09-25T22:15:19Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-25T22:15:19Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=cd5c18867a3b6764cd2b6108906b40b4578a65b4'/>
<id>urn:sha1:cd5c18867a3b6764cd2b6108906b40b4578a65b4</id>
<content type='text'>
URL parsing serializes IPv6 hosts in hex, so an IPv4-mapped address never
matched the dotted-quad pattern and passed as public. Expand the address and
judge every form that embeds an IPv4 address by that address, and block the
remaining non-public IPv6 ranges.

Co-Authored-By: Claude Opus 5.5 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>lnurl: pin SSRF-guard fetches to the validated IP (close DNS-rebinding)</title>
<updated>2026-08-27T18:19:10Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-27T18:19:10Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=900590a8479e802bce0c78605758ea582aa96a66'/>
<id>urn:sha1:900590a8479e802bce0c78605758ea582aa96a66</id>
<content type='text'>
The prior guard resolved the target host, checked the IP against the
private/loopback/link-local/CGNAT/metadata blocklist, then let got fetch the
URL — which re-resolves DNS independently when it connects, leaving a rebinding
window where a low-TTL host can pass the check as public and connect as
internal.

Reimplement safeGot on node http/https with a custom lookup that validates and
returns the exact IP the socket then connects to, so the validation and the
connection target are one and the same (no resolve/connect gap). Redirects are
followed manually and every hop is re-validated the same way; IP-literal hosts
(which bypass the lookup hook) are checked up front; the 2MB cap is enforced on
the streamed body rather than a spoofable content-length header. Scheme stays
restricted to http(s) with an 8s timeout. safeGot's contract is unchanged, so
the three lnurl callers are untouched.

Verified in-container: every internal target (loopback, docker-internal,
169.254 metadata, CGNAT, ULA, IP-literal, octal-literal) is blocked by the
guard, a server-side redirect into the metadata endpoint is blocked at the hop,
and legit external resolution (including http-&gt;https redirects) still works
with correct TLS against the real hostname over the pinned IP.

Co-Authored-By: Claude Opus 4.8 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01DKkev76U4YRnRAqM6jqbae
</content>
</entry>
<entry>
<title>lnurl: SSRF-guard user-supplied URL fetches + validate zap events</title>
<updated>2026-06-16T06:35:50Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-06-16T06:35:50Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinos-server.git/commit/?id=497277ef7b48c86aa2f44b613e0a35a0c3b0c238'/>
<id>urn:sha1:497277ef7b48c86aa2f44b613e0a35a0c3b0c238</id>
<content type='text'>
The /lnurl/proxy, /decode and /encode handlers fetched an arbitrary
user-supplied URL via got() and returned the body — an unauthenticated
SSRF reaching internal services. Route those fetches through a new
safeGot() that rejects hosts resolving to loopback/private/link-local/
CGNAT/metadata ranges, re-validates on each redirect hop, restricts the
scheme to http(s), and caps timeout + response size. Also validate the
NIP-57 zap request (kind 9734 + signature) before storing it, instead of
trusting the client-supplied event.

These endpoints can't be removed — they back lightning-address/LNURL
parsing and resolution in the wallet UI.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
