Date 2026-06-21. Target: Grafana datasource proxy (CASH / HackerOne). first_patched-verifier on CVE-2025-3454. Result: source-strong INCOMPLETE-FIX candidate, REFUTED by a live test of the upstream. The Floor's live-E2E gate caught a plausible CASH false-positive for the cost of one tiny container.
CVE-2025-3454 fixed a datasource-proxy authz bypass: a path like /api/datasources/proxy/uid/<ds>/foo/../api/v2/silences made the per-route RBAC route-match MISS, skipping the Editor/alert.instances.external:write check, while the forwarder still sent the request upstream. The fix added CleanRelativePath (= filepath.Clean) to the route-match. VERIFIED in source that the fix is INCOMPLETE for percent-ENCODED traversal:
- getProxyPath feeds c.Req.URL.EscapedPath() (ENCODED) into the route-match (datasourceproxy.go:162).
- CleanRelativePath = filepath.Clean(...) does NOT URL-decode, so %2e%2e survives as a literal segment and never collapses (pkg/plugins/filepath.go:158).
- So foo/%2e%2e/api/v2/silences fails the HasPrefix route-match -> continue -> RBAC skipped (ds_proxy.go:356-374).
- The post-loop method-block fallback covers only Prometheus flavors + Loki; Alertmanager is excluded (ds_proxy.go:386-394) -> unmatched Alertmanager route returns nil = ALLOWED for POST/DELETE.
- The forwarder preserves the encoding upstream (Grafana's own test ds_proxy_test.go:1035).
Reachability gate PASSED: Viewer (datasources:query) + own datasource UID (privilege-escalation, not an unguessable cross-tenant id). Not disclosed (the CVE described literal-slash, read-only; this is encoded + Alertmanager WRITE). On paper: a submittable Viewer->write authz bypass on a CASH target.
The whole chain hinges on a load-bearing assumption the agent flagged: that the UPSTREAM (Alertmanager) decodes %2e%2e -> .. and path-cleans to resolve /foo/%2e%2e/api/v2/silences -> /api/v2/silences. The agent reasoned "Go net/http and most routers normalize." TESTED IT - and it is FALSE for Alertmanager:
- Ran ONLY prom/alertmanager (tiny container, no Grafana needed). --path-as-is to send the raw path.
- GET /api/v2/status -> 200 (baseline).
- GET /foo/%2e%2e/api/v2/status -> 404. POST /foo/%2e%2e/api/v2/silences -> 404.
- Control GET /foo/../api/v2/status (LITERAL ..) -> 307 (router cleans literal .. but does NOT decode %2e%2e first).
So Alertmanager treats %2e%2e as a literal path segment -> the encoded-traversal request 404s at the upstream -> the forwarded request never reaches the protected endpoint -> NO write happens. The Grafana-side RBAC bypass is real but INERT.
http.ServeMux decodes (URL.Path) then cleans, but many real servers use custom routers (Alertmanager uses prometheus/common/route, go-openapi) that do NOT decode %2e%2e before routing. Encoded-dot and encoded-slash normalization is router-specific - empirically variable.%2e%2e handling. Spinning up only prom/alertmanager (a tiny container) and curling --path-as-is resolved the load-bearing link in ~2 minutes, with no Grafana/datasource/Viewer-token setup. For any proxy-bypass candidate, isolate the unverified link and test THAT component alone.The bypass could still be live against a DIFFERENT datasource whose upstream DOES decode %2e%2e AND which lacks the method-block fallback. But: Prometheus/Loki (write-blocked by the fallback) are immune to the write impact regardless, and Alertmanager (the prime no-method-block target) does not normalize %2e%2e. To resurrect this, find a proxied datasource type that (a) is not in the Prometheus/Loki method-block list AND (b) runs an upstream router that decode-then-cleans %2e%2e. Low odds; documented for completeness.
Re-tested the CVE-2025-3454 dsproxy variant fully (cycle278 KILLED). The encoding-authz-bypass needs ALL of: (1) proxy-authz-normalizes-A != forwarder-sends-B (Grafana CleanRelativePath doesn't decode = REAL), (2) upstream-decodes-B-to-the-protected-route, (3) the protected route is reachable. NEW 4th gate that killed it: a METHOD-LEVEL BACKSTOP independent of path. Grafana's ds_proxy validateRequest has, AFTER the per-route reqRole loop, method-based fallbacks that path-encoding CANNOT bypass: - Prometheus-type DS: trailing "non allow-listed POSTs/PUTs/DELETEs not allowed" (rejects all non-GET). - Alertmanager DS: plugin.json CATCH-ALL routes {method:POST/PUT/DELETE, reqRole:Admin} (empty path -> matches everything) -> any unmatched non-GET needs Admin. Single-char %-encode (%6c->l) DOES decode on Prometheus (unlike %2e%2e which 404s everywhere), so the upstream gate (2) passes for Prometheus - but the method backstop (4) blocks the only privilege-gain routes (non-GET writes), and Prometheus GET routes are Viewer-allowed (no gain). RULE: when auditing a route-authz-bypass-via-encoding, after confirming the route-match is skipped, CHECK FOR A METHOD-LEVEL OR CATCH-ALL BACKSTOP that re-blocks independent of the path - it's the common reason a real bypass is inert. Confirm by RESPONSE-BODY diff (different error message = different code path = bypass real but caught downstream), not just status code (both 403 here).