feat: geofilter customizer tab + PUT /api/config/geo-filter#736
feat: geofilter customizer tab + PUT /api/config/geo-filter#736efiten wants to merge 3 commits intoKpa-clawbot:masterfrom
Conversation
Kpa-clawbot
left a comment
There was a problem hiding this comment.
Security Review — PUT /api/config/geo-filter
Reviewed with a security-focused lens on the config write endpoint. The overall design is solid — API key gating, atomic file writes, input shape validation, clear separation of read-only vs write-enabled UI. Good work. But there are a few issues that need addressing before merge.
Must-fix
1. Data race on s.cfg.GeoFilter (routes.go)
The PUT handler writes s.cfg.GeoFilter = gf with no synchronization. The GET handler and any ingest-time filtering read s.cfg.GeoFilter concurrently from other goroutines. This is a textbook data race — Go's race detector will flag it. Wrap config mutations in a mutex (or use atomic.Pointer if you want lock-free reads).
2. No request body size limit (routes.go:~L440)
json.NewDecoder(r.Body).Decode(&body) reads unbounded input. An attacker with a valid API key (or a compromised key) can send a multi-GB JSON body and exhaust server memory. Wrap with http.MaxBytesReader:
r.Body = http.MaxBytesReader(w, r.Body, 1<<20) // 1 MB3. No coordinate range validation (routes.go)
Polygon points are stored as-is — no check that lat ∈ [-90, 90] and lon ∈ [-180, 180]. NaN, Infinity, or wildly out-of-range values would be persisted to config.json and could cause downstream issues (point-in-polygon math, Leaflet rendering). Validate each point after decoding.
Should-fix
4. writeEnabled leaks API key presence to unauthenticated callers (routes.go:~L429)
The GET endpoint (no auth required) returns writeEnabled: true/false, which tells any anonymous visitor whether the server has a strong API key configured. This is a minor information leak — consider removing it from the unauthenticated response and having the UI infer editability from a separate authenticated probe, or accept the risk with a comment explaining the trade-off.
5. No upper bound on polygon point count
A polygon with 100,000 points is technically valid per the current checks. Consider a reasonable cap (e.g., 1000 points) to bound storage and computation.
Nits (non-blocking)
SaveGeoFiltererror messages include filesystem paths (config.json not found in /app/data). These get logged server-side (fine) but also returned to the client viawriteError. Consider generic client-facing messages.- The
_commentfield inconfig.example.jsonis a nice touch for discoverability.
What's done well
requireAPIKeymiddleware on the PUT — correct layer for authzIsWeakAPIKeycheck forwriteEnabled— defense in depth- Atomic write via temp + rename — prevents partial writes on crash
- Polygon minimum 3 points validation
- Comprehensive test coverage: auth rejection, save/clear/validation, disk persistence
- UI correctly gates edit controls on server-reported
writeEnabled - Clean separation: standalone builder for offline use, customizer tab for live editing
Items 1–3 are must-fix before merge. The rest can be addressed in follow-ups.
- Fix data race on s.cfg.GeoFilter: add cfgMu RWMutex with getGeoFilter/ setGeoFilter accessors used in all handler goroutines - Add 1 MB MaxBytesReader cap on PUT /api/config/geo-filter request body - Validate polygon coordinate ranges (lat ∈ [-90,90], lon ∈ [-180,180]) and reject NaN/Inf values - Cap polygon at 1000 points to bound storage and computation - Document writeEnabled information-leak trade-off with a comment - Add tests for coordinate range rejection and oversized polygon rejection Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
All must-fix and should-fix items from the security review have been addressed in commit Must-fix — resolved:
Should-fix — resolved: Nit (filesystem path in error response) — left as-is; the path leaks only to authenticated callers (PUT requires Build was also broken by a missing closing |
Final Re-Review — NEEDS REBASEFollow-up to the security review from 2026-04-15. Verifying @efiten's claimed fixes in 🔒 djb — Security VerificationMust-fix #1 (data race): ✅ Fixed. Must-fix #2 (unbounded body): ✅ Fixed. Must-fix #3 (coordinate validation): ✅ Fixed. NaN/Inf/range checks at L487-490, lat ∈ [-90,90], lon ∈ [-180,180]. Should-fix #4 (writeEnabled leak): ✅ Acknowledged. Comment at L448-450 documents the risk-accepted trade-off. Reasonable. Should-fix #5 (polygon cap): ✅ Fixed. 1000-point cap at L484. Nit (error paths leak): ✅ Fixed. All five original findings verified as resolved. 📐 dijkstra — Correctness of the Mutex DisciplineThe RWMutex usage is correct: reads take RLock, writes take Lock. The invariant "in-memory ≥ disk" holds because One observation: 🆕 New Findings
VerdictReady after rebase. All must-fix and should-fix items verified as resolved. No new blocking issues. The PR is currently |
|
@efiten can you please resurrect this PR - either rebase it and let's get it merged or if it's stale let's close it as such. |
…bot#669 M3) Backend: - Add PUT /api/config/geo-filter (requires X-API-Key) — saves geo_filter back to config.json atomically and updates in-memory config immediately, no restart needed - Add SaveGeoFilter() to config.go: reads config as raw map (preserving _comment fields), updates geo_filter key, writes back via temp+rename - Add writeEnabled field to GET /api/config/geo-filter response so the frontend can gate editing controls on server write capability - Add Server.configDir field; wired from -config-dir flag in main.go - Tests: TestPutConfigGeoFilter (4 cases) + TestSaveGeoFilter (3 cases) Frontend: - Add GeoFilter tab (🗺️) to the customizer between Display and Export - Tab shows current polygon on a Leaflet map (read-only for all users) - Editing controls (undo, clear, buffer km, API key input, save/remove) are only revealed when the server reports writeEnabled=true — i.e. the deployment has a write-capable apiKey configured. Public instances see a read-only polygon view. - Save calls PUT /api/config/geo-filter; Remove clears the filter - Map is destroyed on tab switch and panel close to avoid Leaflet leaks Docs: - Add docs/user-guide/geofilter.md (full guide: config, customizer, builder, prune script, API) - Update configuration.md and customization.md with geo_filter section - Update config.example.json _comment to mention the Customizer tab Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clicking the small inline map in the customizer GeoFilter tab now opens a full-screen modal (92vw × 86vh) with Undo/Clear/Done/Cancel controls. The inline map becomes a read-only preview. Both maps and the standalone geofilter-builder.html now use CartoDB Positron (light) instead of dark. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix data race on s.cfg.GeoFilter: add cfgMu RWMutex with getGeoFilter/ setGeoFilter accessors used in all handler goroutines - Add 1 MB MaxBytesReader cap on PUT /api/config/geo-filter request body - Validate polygon coordinate ranges (lat ∈ [-90,90], lon ∈ [-180,180]) and reject NaN/Inf values - Cap polygon at 1000 points to bound storage and computation - Document writeEnabled information-leak trade-off with a comment - Add tests for coordinate range rejection and oversized polygon rejection Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
e12dc6d to
5719b9e
Compare
Summary
Part of #669 — M3: Customizer integration.
Backend
PUT /api/config/geo-filter(requiresX-API-Key) — savesgeo_filtertoconfig.jsonatomically (temp+rename) and updates in-memory config immediately. No restart needed.SaveGeoFilter()inconfig.goreads config as a raw map (preserving_commentfields and any unknown keys), updates just thegeo_filterkey, writes back.GET /api/config/geo-filternow includeswriteEnabled: bool—truewhen server has a strongapiKeyconfigured.Server.configDirfield wired from-config-dirflag.TestPutConfigGeoFilter× 4,TestSaveGeoFilter× 3, updatedTestConfigGeoFilterEndpoint× 2).Frontend
GET /api/config/geo-filteron tab open; shows current polygon on a Leaflet map.writeEnabled=false). On servers with a write-capableapiKey, the edit UI reveals itself: undo/clear, buffer km, API key input, Save and Remove buttons.PUT /api/config/geo-filter; applies immediately in-memory.Docs
docs/user-guide/geofilter.md: complete guide covering config schema, customizer tab, builder, prune script, and API.docs/user-guide/configuration.mdandcustomization.md.config.example.json_commentto mention the Customizer tab.Test plan
apiKey: GeoFilter tab shows map, no edit controlsapiKey: edit controls appear after tab loadsgeo_filtergo test ./...passes🤖 Generated with Claude Code