sigil

module
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 11, 2026 License: Apache-2.0

README

Sigil

A small, self-hosted favicon resolver. Given a domain, Sigil fetches that site's real favicon server-side, normalizes it to a PNG at the size you ask for, and caches the result. It exists to replace third-party favicon proxies (e.g. google.com/s2/favicons) so that resolving an icon never leaks which domains your users look up to an outside party.

Apache-2.0. Sigil fetches URLs derived from untrusted domains — an SSRF surface by design — so a hardened fetch boundary is its headline feature (see the Security model and THREAT-MODEL.md). Self-reviewed, not independently audited.

Security model

Sigil's whole reason to be a separate service is containment: the code that fetches attacker-influenceable URLs must not run in a process that holds database credentials or an internal-network position. The running instance is designed to have zero internal secrets and egress-only reach — if its defenses were ever bypassed, the blast radius is a box that can talk to the public internet and nothing else.

The fetch boundary (pkg/favicon/fetch.go) enforces, for every request and every redirect hop:

  • Dial-time IP validation (TOCTOU-safe). The resolved IP is checked in net.Dialer.Control — after DNS resolution, immediately before connect, once per candidate address — using code.dny.dev/ssrf (an IANA-registry-derived denylist). This closes the resolve-then-connect gap and defeats DNS rebinding: a name that rebinds between check and dial is still validated at the dial. Validation is on netip.Addr, so IPv4-mapped IPv6 (::ffff:169.254.169.254) cannot slip past as global unicast.
  • A default-deny address policy. Loopback, RFC 1918 private space, CGNAT (100.64.0.0/10), link-local 169.254.0.0/16 (the cloud metadata endpoint), multicast, reserved ranges, IPv6 ULA/link-local, and NAT64 are all rejected. Only TCP to ports 80/443 is permitted.
  • Bounded redirects. At most 3 hops, and every hop must be http/https — a file://, gopher://, etc. redirect is refused.
  • Bounded responses. The body is read through a hard byte cap, the content type is decided by sniffing (never the response header), only raster image types are accepted, and the decoded pixel dimensions are bounded before full decode to defeat decompression bombs.
  • SVG only through a sandboxed rasterizer. An SVG is XML that can carry script (stored-XSS risk) or external entities (XXE / second-order SSRF), so SVG bytes are never served or stored: a structurally-detected SVG is bounded (bytes, XML tokens, nesting depth) and rendered to a fixed-size pixel buffer by a pure-Go renderer (oksvg + rasterx) built on encoding/xml, which has no code path that loads external entities, DTDs, or URLs — the XXE class cannot be expressed, and a no-network regression test keeps it that way. Renderer panics are contained per candidate. The output, like every icon Sigil serves, is a freshly encoded PNG.

The SSRF test suite (pkg/favicon/fetch_test.go) asserts each of these independently of the guard library, so a dependency bump that regressed a range would fail our tests rather than silently ship.

What it is (and is not)

  • Is: a hardened favicon fetcher + normalizer, usable as a Go library (github.com/ciphera-net/sigil/pkg/favicon) or as a tiny HTTP service (cmd/server).
  • Is not: a public favicon API for the internet, a general URL fetcher, or a proxy. The published instance stays internal; publishing the code is the open-source act.

Install (library)

go get github.com/ciphera-net/sigil/pkg/favicon

Requires Go 1.25+.

API (service)

Route Response
GET /icon?domain=<hostname>&sz=<16|32|64|128> 200 image/png resolved icon · 404 no icon (negatively cached) · 400 bad domain/size · 502 upstream fetch failure (not cached)
GET /healthz liveness
GET /metrics Prometheus

domain must be a bare hostname (no scheme, port, path, or IP literal).

Self-host

docker build -t sigil .
docker run -p 8085:8085 sigil

The container is a distroless static image holding only the server binary.

CI

.woodpecker/ runs, as separate checks: test (gofmt, go vet, go test -race, including the SSRF suite), govulncheck (dependency-vuln scan), and — once the service lands — build / push / deploy.

Directories

Path Synopsis
cmd
server command
Command server is the Sigil favicon-resolution HTTP service.
Command server is the Sigil favicon-resolution HTTP service.
pkg
favicon
Package favicon resolves and normalizes the favicon for a domain.
Package favicon resolves and normalizes the favicon for a domain.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL