Require secrets via headers only: drop ?key= and ?password= query fallbacks (#137, #141)
CI / test (pull_request) Successful in 28s
CI / docker (pull_request) Skipped

Query strings leak into Traefik access logs, browser history, and Referer
headers. Admin key is now accepted only via X-Admin-Key; paste and can
passwords only via X-Paste-Password (or the POST unlock form). Tests
updated; new negative cases assert 401 for the query paths.
This commit is contained in:
fen
2026-09-09 23:23:10 -05:00
parent 98222e762f
commit d013f3965f
8 changed files with 29 additions and 29 deletions
@@ -2,7 +2,7 @@ package api
// #81: ALL password verification attempts (GET query param, header, POST
// form) must go through the per-IP unlock limiter. Regression: N wrong
// passwords via GET ?password= must eventually yield 429.
// passwords via X-Paste-Password must eventually yield 429.
import (
"encoding/json"
@@ -26,7 +26,7 @@ func createPasswordPaste(t *testing.T, s *apiServer, pw string) string {
}
// TestRateLimitGetPasswordQuery: repeated wrong passwords via GET
// ?password= must eventually return 429 (unlock limiter: burst 5).
// X-Paste-Password wrong attempts must eventually return 429 (unlock limiter: burst 5).
func TestRateLimitGetPasswordQuery(t *testing.T) {
s := testServer(t)
h := s.routes()
@@ -35,7 +35,8 @@ func TestRateLimitGetPasswordQuery(t *testing.T) {
var saw429 bool
// more attempts than the unlock burst (5)
for i := 0; i < 10; i++ {
req := httptest.NewRequest("GET", "/api/pastes/"+id+"?password=wrong"+string(rune('a'+i)), nil)
req := httptest.NewRequest("GET", "/api/pastes/"+id, nil)
req.Header.Set("X-Paste-Password", "wrong"+string(rune('a'+i)))
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code == 429 {
@@ -47,7 +48,7 @@ func TestRateLimitGetPasswordQuery(t *testing.T) {
}
}
if !saw429 {
t.Fatal("expected 429 after repeated wrong ?password= attempts, never got one")
t.Fatal("expected 429 after repeated wrong password attempts, never got one")
}
}
@@ -83,7 +84,8 @@ func TestRateLimitGetPasswordCorrectStillAllowed(t *testing.T) {
h := s.routes()
id := createPasswordPaste(t, s, "hunter2")
req := httptest.NewRequest("GET", "/api/pastes/"+id+"?password=hunter2", nil)
req := httptest.NewRequest("GET", "/api/pastes/"+id, nil)
req.Header.Set("X-Paste-Password", "hunter2")
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != 200 {