release / govulncheck (push) Successful in 17s
release / web (push) Successful in 1m33s
release / go (push) Successful in 1m49s
release / integration (push) Successful in 5m42s
release / android (push) Successful in 5m59s
release / Build signed APK (releases and dev) (push) Successful in 6m9s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 16s
release / Verify release artifacts (tag releases only) (push) Skipped
Applies rule 32's migration exception: each header still says what the schema was, what changes and what it prevents, but task, milestone, decision and rule ids are replaced by the practice they stood for, the operator quotes by the decision, single-install counts are dropped, and pointers to a deleted package and an external design doc are gone. golang-migrate keeps no checksum, so editing applied migrations' comments is safe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
35 lines
1.8 KiB
SQL
35 lines
1.8 KiB
SQL
-- Trusted reverse-proxy depth for client-IP extraction.
|
|
--
|
|
-- X-Forwarded-For grows left-to-right: each proxy APPENDS the peer it
|
|
-- received the request from. For client -> CDN -> own-proxy -> Minstrel the
|
|
-- app sees XFF = [client, CDN] with RemoteAddr = own-proxy. So the real
|
|
-- client sits at XFF[len - hops], where hops counts the proxies you trust:
|
|
--
|
|
-- 0 no proxy in front — use the socket peer, ignore XFF entirely
|
|
-- 1 one reverse proxy (nginx / Caddy / Traefik terminating TLS)
|
|
-- 2 a CDN in front of your own proxy (Cloudflare -> nginx -> Minstrel)
|
|
--
|
|
-- Default 1: a publicly reachable Minstrel needs a TLS terminator in front of
|
|
-- it, and recording that terminator's own address for every session makes the
|
|
-- active-sessions surface useless — created_ip and last_ip would both
|
|
-- be the proxy, so the "address changed" signal could never fire.
|
|
--
|
|
-- The cost, stated on the admin card rather than buried: hops >= 1 DECLARES
|
|
-- that a proxy exists. If one doesn't, a client can forge X-Forwarded-For and
|
|
-- choose what its own session row shows, which defeats exactly the compromise
|
|
-- detection the active-sessions surface exists for. That is inherent to the
|
|
-- trusted-hop model, which is why 0 is a first-class setting and not a hidden
|
|
-- escape hatch.
|
|
--
|
|
-- Upper bound 10 guards a typo turning into "trust the whole header"; no real
|
|
-- deployment chains ten proxies.
|
|
CREATE TABLE network_settings (
|
|
id boolean PRIMARY KEY DEFAULT true,
|
|
trusted_proxy_hops int NOT NULL DEFAULT 1,
|
|
CONSTRAINT network_settings_singleton CHECK (id = true),
|
|
CONSTRAINT network_settings_hops_range
|
|
CHECK (trusted_proxy_hops >= 0 AND trusted_proxy_hops <= 10)
|
|
);
|
|
|
|
INSERT INTO network_settings (id) VALUES (true) ON CONFLICT (id) DO NOTHING;
|