Skip to content

Serve only the root path - #28

Merged
jdtw merged 1 commit into
mainfrom
fix/root-only-route
Aug 1, 2026
Merged

Serve only the root path#28
jdtw merged 1 commit into
mainfrom
fix/root-only-route

Conversation

@jdtw

@jdtw jdtw commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Why

The mux registered "/", which in net/http is a catch-all. Every request that didn't match /favicon.ico ran the full handler.

In a sampled four hours of production logs, 81 of 97 requests were vulnerability scanners/wp-login.php, /666.php, /backup/, /wp-content/plugins/core-plugin/include.php and friends. Only 16 were for /.

Each of those probes fetched the King County road alert feed, and each was a candidate to spend a Gemini call once the analyzer's 15-minute cache expired.

What

Go 1.22 added the {$} pattern, which matches the root path and nothing else. Unmatched paths now get a 404 straight from the mux, never reaching the handler.

s.HandleFunc("/{$}", logged(s.flood()))

Safe here because every asset the page references is an absolute external URL — nothing was ever served from a subpath. /favicon.ico keeps its own exact-match route.

Testing

The tests assert the absence of work, not just the status code — a counting feed server verifies the probes fetch the road alert feed zero times, and that / still fetches it exactly once. That's the property that actually matters.

Verified against a real server too:

/                    → 200
/favicon.ico         → 200
/wp-login.php        → 404
/666.php             → 404
/admin/config.php    → 404
/backup/             → 404
/a/b/c               → 404
HEAD /               → 200

Exactly 3 requests reached the handler across that run (GET / ×2 and HEAD /); all five scanner paths were rejected by the mux. HEAD is checked explicitly since bots use it. Full suite and -race both pass.

Not included

Two other things worth fixing that aren't in scope here:

  • No negative caching in the analyzer. Every error return in IsRoadOpen leaves lastCheck untouched, so if the cameras start 403ing or the API errors, the 15-minute cache never engages and every request retries. That's the uncapped-cost path.
  • Analysis runs year-round. The Snoqualmie floods roughly Nov–Apr, but the cameras are analyzed 96×/day, 365 days/year.

🤖 Generated with Claude Code

The mux registered "/", which in net/http is a catch-all: every request
that did not match /favicon.ico ran the full handler. In a sampled four
hours of production logs, 81 of 97 requests were vulnerability scanners
probing for PHP and WordPress -- /wp-login.php, /666.php, /backup/ and
similar. Each one fetched the King County road alert feed, and each was a
candidate to spend a Gemini call once the analyzer cache expired.

Go 1.22 added the {$} pattern, which matches the root path and nothing
else, so unmatched paths now get a 404 from the mux without reaching the
handler. Every asset the page references is an absolute external URL, so
nothing was being served from a subpath.

The tests assert the absence of work rather than just the status code: a
counting feed server confirms the probes fetch the feed zero times, and
that the root path still fetches it exactly once.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jdtw
jdtw merged commit 76c332c into main Aug 1, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant