fix(routes): stop leaking apiKey presence via writeEnabled on public GET

PR review found GET /api/config/geo-filter is unauthenticated and was
returning writeEnabled (derived from APIKey != "" && !IsWeakAPIKey).
That tells any anonymous caller whether a strong API key is configured
on the server — low-severity info disclosure, but free to fix.

Approach: drop the field entirely instead of gating the endpoint behind
requireAPIKey. The polygon itself is intentionally public (read-only
clients depend on it), so auth-gating the whole endpoint would break
them. Clients that want to write should just attempt PUT and handle
401/403 — the customizer JS already has that error path.

- routes.go: remove writeEnabled from both response branches
- customize-v2.js: always reveal edit controls; rely on PUT error path
- docs/user-guide/geofilter.md: drop the writeEnabled API doc + the
  "controls only appear when..." UX line

Green commit for the red test in the previous commit.
This commit is contained in:
openclaw-bot
2026-05-21 02:41:07 +00:00
parent e01b5f58c2
commit dd2f81270a
3 changed files with 16 additions and 15 deletions
+6 -6
View File
@@ -467,15 +467,15 @@ func (s *Server) handleConfigMap(w http.ResponseWriter, r *http.Request) {
func (s *Server) handleConfigGeoFilter(w http.ResponseWriter, r *http.Request) {
gf := s.getGeoFilter()
// writeEnabled leaks whether the server has a strong API key to unauthenticated
// callers. Risk accepted: the information (key is/isn't configured) is low-sensitivity
// and the GET endpoint is intentionally public for read-only clients.
writeEnabled := s.cfg != nil && s.cfg.APIKey != "" && !IsWeakAPIKey(s.cfg.APIKey)
// NOTE: do NOT include any field that derives from APIKey presence/strength here.
// This endpoint is intentionally public; leaking whether a write-capable key is
// configured is an info-disclosure. Clients that want to write should just try
// PUT and handle 401/403. See PR #736 review.
if gf == nil || len(gf.Polygon) == 0 {
writeJSON(w, map[string]interface{}{"polygon": nil, "bufferKm": 0, "writeEnabled": writeEnabled})
writeJSON(w, map[string]interface{}{"polygon": nil, "bufferKm": 0})
return
}
writeJSON(w, map[string]interface{}{"polygon": gf.Polygon, "bufferKm": gf.BufferKm, "writeEnabled": writeEnabled})
writeJSON(w, map[string]interface{}{"polygon": gf.Polygon, "bufferKm": gf.BufferKm})
}
func (s *Server) handlePutConfigGeoFilter(w http.ResponseWriter, r *http.Request) {
+2 -2
View File
@@ -62,7 +62,7 @@ If your server has an `apiKey` configured, the **GeoFilter tab** in the Customiz
5. Enter your **Server API Key** (the `apiKey` value from `config.json`)
6. Click **Save to server** — the filter is applied immediately, no restart needed
The editing controls only appear when the server has a write-capable API key configured. On deployments without an `apiKey`, the tab shows the current polygon as read-only.
The editing controls are always visible. Saving requires entering the server's `apiKey`; on servers without one (or with a weak key), the save request returns `401`/`403` and the error is shown inline.
To remove the filter, click **Remove filter** (also requires the API key).
@@ -86,7 +86,7 @@ For local/offline use without a running server, open `tools/geofilter-builder.ht
GET /api/config/geo-filter
```
Returns the current geo filter configuration. Also includes a `writeEnabled` boolean indicating whether the `PUT` endpoint is available (i.e., server has a write-capable `apiKey`).
Returns the current geo filter configuration (`polygon`, `bufferKm`). Whether the `PUT` endpoint will accept a write depends on whether the server has an `apiKey` configured; clients should attempt the write and handle `401`/`403` if it isn't.
```
PUT /api/config/geo-filter
+8 -7
View File
@@ -1508,12 +1508,13 @@
if (!_gfLoaded) {
api('/config/geo-filter', { ttl: 0 }).then(function (gf) {
// Show edit controls only on servers that have a write-capable API key configured
if (gf && gf.writeEnabled) {
_gfWriteEnabled = true;
var editEl = container.querySelector('#cv2-gf-edit');
if (editEl) editEl.style.display = '';
}
// Always expose edit controls. The server no longer leaks apiKey presence
// via writeEnabled (info-disclosure on a public endpoint); instead, the
// user enters their API key into the editor and writes either succeed or
// get a 401/403 surfaced as an inline error.
_gfWriteEnabled = true;
var editEl = container.querySelector('#cv2-gf-edit');
if (editEl) editEl.style.display = '';
if (gf && gf.polygon && gf.polygon.length >= 3) {
_gfPoints = gf.polygon.map(function (p) { return [p[0], p[1]]; });
var buf = container.querySelector('#cv2-gf-buffer');
@@ -1523,7 +1524,7 @@
_gfStatus(container, gf.polygon.length + ' points · bufferKm=' + (gf.bufferKm || 0));
} else {
_gfPoints = [];
_gfStatus(container, gf && gf.writeEnabled ? 'No geo filter. Click the map to open the editor.' : 'No geo filter configured.');
_gfStatus(container, 'No geo filter. Click the map to open the editor.');
_gfMap.setView([50.5, 4.4], 5);
}
_gfLoaded = true;