Repository navigation
fix(analytics): make Umami opt-in, drop visitor-IP event (OWASP A06) - #50
Merged
Merged
Conversation
The middleware posted every page view, plus a custom "Network Log" event carrying the visitor's IP, to a hardcoded http://umami-umami-1:3000 with the upstream author's website id as a default. - Off by default: nothing is sent unless UMAMI_URL and UMAMI_WEBSITE_ID are both set; otherwise return NextResponse.next() with no fetch. - No hardcoded host or default website id; invalid/non-http URL disables. - Remove the "Network Log" IP event. The client IP is forwarded only as X-Forwarded-For so Umami can do its own geo lookup. - 2s timeout on the analytics fetch; failures are swallowed, still via event.waitUntil so page loads never wait on analytics. - docker-compose: drop the external umami_default network, which made `docker compose up` fail on hosts without upstream's Umami stack. - Document the variables and what is sent in .env.example; update SECURITY.md A06. Add middleware unit tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
carbon-evolution
added a commit
that referenced
this pull request
Oct 18, 2026
- A01 Broken Access Control: Medium -> Low. Recon scanner closed by default (token / explicit opt-in), client IP from forwarding headers only with TRUST_PROXY_HEADERS (#51). - A06 Insecure Design: Medium -> Low. Analytics opt-in, visitor-IP event removed (#50); recon no longer anonymous by default. - A02 Security Misconfiguration: stays Medium (improved). Hardening headers and a tighter CSP without unsafe-eval (#52); ZAP on main now 0 High / 3 Medium / 1 Low / 4 Info (was 0/5/5/5). Remaining Medium items are script/style 'unsafe-inline' and wildcard media sources. - A07: scanner access token compared in constant time. - Overall: 2 Medium, 8 Low (was 4 Medium, 6 Low); Scorecard 8.0. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the OWASP A06 finding in
SECURITY.md:src/middleware.tssent every page view, plus a custom "Network Log" event containing the visitor's IP, to a hardcodedhttp://umami-umami-1:3000(upstream's Umami container) with upstream's website ID as the default.What changed
UMAMI_URL(base URL, posts to<UMAMI_URL>/api/send) andUMAMI_WEBSITE_ID. If either is missing, orUMAMI_URLisn't a valid http(s) URL, the middleware returnsNextResponse.next()right away and makes no outbound request. There is no hardcoded host and no default site ID.UMAMI_WEBSITE_IDwas already the name in use; no other Umami variables existed in the README, DOCKER.md or .env.example.cf-connecting-ip/x-forwarded-for) is still forwarded, but only as theX-Forwarded-Forheader and never in the payload. Without it, Umami would record every visitor as the OSIRIS server, so geo stats and visitor counts would be meaningless.User-Agentis forwarded for the same reason.event.waitUntil(...). The fetch now has a 2sAbortSignal.timeout. Rejections are swallowed, and a synchronous throw is caught too, so analytics can't break a page load.external: trueumami_defaultnetwork. With it,docker compose upfails on any host that doesn't already have upstream's Umami stack. Operators who run Umami in another Compose project can attach its network with an override (noted in.env.example).Tests
New file
src/middleware.test.ts(7 tests). It stubs globalfetch, uses a realNextRequestand a fakeNextFetchEvent, and covers:waitUntilUMAMI_TRACK_IP=trueAll 7 fail against the old middleware. On the rejection test, the old code also swallowed the error; that test fails there only because the old code made 2 calls instead of 1.
Verification
tsc --noEmit0 ·eslint .0 errors · vitest 73/73 ·npm run buildOKnext start -p 3020, a listener on :3029 logging requests):GET /returned 200 and the listener got 0 requestsUMAMI_URL=http://127.0.0.1:3029 UMAMI_WEBSITE_ID=smoke-site-1:GET /returned 200 and the listener got exactly 1POST /api/sendwith payload{hostname,language,referrer,screen,title,url:"/",website:"smoke-site-1"}and headerx-forwarded-for: 203.0.113.7GET /still returned 200🤖 Generated with Claude Code