fix(security): add SDK/sign body limits, IP validation, error sanitization, file perms, mode validation
S1: http.MaxBytesReader (64KB) on SDK decodeRequestBody S2: http.MaxBytesReader (64KB) on /v1/sign endpoint S3: net.ParseIP validation in admin IP ban handler S4: Generic error message for internal /v1/sign errors S5: Settings file permissions tightened to 0600 S6: JSON API envelope for SDK requireMethod errors S7: SetApprovalMode returns error for invalid modes
cognitive committed
Mar 5, 2026 at 03:03 UTC
0a58e85248a92221e40ee0fced70d6b6a6f40464
6 files changed
+30
-8
cmd/relay-server/registry.go
+2
-1
@@ -222,11 +222,12 @@ func (r *SDKRegistry) requireMethod(w http.ResponseWriter, req *http.Request, me
222
}
223
224
w.Header().Set("Allow", method)
225
- http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
225
+ writeAPIError(w, http.StatusMethodNotAllowed, "method_not_allowed", "method not allowed")
226
return false
227
}
228
229
func (r *SDKRegistry) decodeRequestBody(w http.ResponseWriter, req *http.Request, dst any, logMessage string) bool {
230
+ req.Body = http.MaxBytesReader(w, req.Body, 1<<16)
231
if err := json.NewDecoder(req.Body).Decode(dst); err != nil {
232
log.Error().Err(err).Msg(logMessage)
233
writeAPIError(w, http.StatusBadRequest, "invalid_request", "invalid request body")
cmd/relay-server/serve.go
+6
-1
@@ -219,6 +219,7 @@ func handleKeylessSign(w http.ResponseWriter, r *http.Request, signer *keyless.S
219
return
220
}
221
222
+ r.Body = http.MaxBytesReader(w, r.Body, 1<<16)
223
defer r.Body.Close()
224
225
var req keyless.SignRequest
@@ -238,7 +239,11 @@ func handleKeylessSign(w http.ResponseWriter, r *http.Request, signer *keyless.S
239
case errors.Is(err, keyless.ErrPermissionDenied):
240
status = http.StatusForbidden
241
}
241
- writeSignError(w, status, err.Error())
242
+ msg := err.Error()
243
+ if status == http.StatusInternalServerError {
244
+ msg = "internal signing error"
245
+ }
246
+ writeSignError(w, status, msg)
247
return
248
}
249
portal/admin/handler.go
+6
-1
@@ -4,6 +4,7 @@ import (
4
"encoding/base64"
5
"encoding/json"
6
"errors"
7
+ "net"
8
"net/http"
9
"strings"
10
@@ -273,7 +274,7 @@ func (h *Handler) handleApprovalModeRequest(w http.ResponseWriter, r *http.Reque
274
h.writeAPIError(w, http.StatusBadRequest, "invalid_mode", "invalid mode (must be 'auto' or 'manual')")
275
return
276
}
276
- approveManager.SetApprovalMode(mode)
277
+ _ = approveManager.SetApprovalMode(mode) // mode already validated above
278
h.service.SaveSettings(serv)
279
log.Info().Str("mode", string(mode)).Msg("[Admin] Approval mode changed")
280
h.writeAPIData(w, http.StatusOK, types.AdminApprovalModeResponse{
@@ -498,6 +499,10 @@ func (h *Handler) handleIPBanRequest(w http.ResponseWriter, r *http.Request, ser
499
h.writeAPIError(w, http.StatusBadRequest, "invalid_ip", "invalid IP address")
500
return
501
}
502
+ if net.ParseIP(ip) == nil {
503
+ h.writeAPIError(w, http.StatusBadRequest, "invalid_ip", "invalid IP address")
504
+ return
505
+ }
506
507
ipManager := h.service.GetIPManager()
508
if ipManager == nil {
portal/admin/service.go
+4
-2
@@ -135,7 +135,7 @@ func (s *Service) SaveSettings(serv *portal.RelayServer) {
135
}
136
}
137
138
- if err := os.WriteFile(s.settingsPath, data, 0644); err != nil {
138
+ if err := os.WriteFile(s.settingsPath, data, 0600); err != nil {
139
log.Error().Err(err).Msg("[Admin] Failed to save admin settings")
140
return
141
}
@@ -180,7 +180,9 @@ func (s *Service) LoadSettings(serv *portal.RelayServer) {
180
}
181
182
if payload.ApprovalMode != "" {
183
- s.approveManager.SetApprovalMode(payload.ApprovalMode)
183
+ if err := s.approveManager.SetApprovalMode(payload.ApprovalMode); err != nil {
184
+ log.Warn().Err(err).Str("mode", string(payload.ApprovalMode)).Msg("[Admin] Ignoring invalid approval mode from settings")
185
+ }
186
}
187
188
for _, leaseID := range payload.ApprovedLeases {
portal/admin/service_test.go
+3
-1
@@ -18,7 +18,9 @@ func TestServiceSaveLoadSettingsRoundTrip(t *testing.T) {
18
sourceServer := mustNewTestRelayServer(t)
19
sourceServer.GetLeaseManager().BanLease("lease-ban")
20
service.GetBPSManager().SetBPSLimit("lease-bps", 4096)
21
- service.GetApproveManager().SetApprovalMode(policy.ModeManual)
21
+ if err := service.GetApproveManager().SetApprovalMode(policy.ModeManual); err != nil {
22
+ t.Fatalf("SetApprovalMode: %v", err)
23
+ }
24
service.GetApproveManager().ApproveLease("lease-approved")
25
service.GetApproveManager().DenyLease("lease-denied")
26
service.GetIPManager().BanIP("203.0.113.10")
portal/policy/approver.go
+9
-2
@@ -1,6 +1,9 @@
1
package policy
2
3
-import "sync"
3
+import (
4
+ "fmt"
5
+ "sync"
6
+)
7
8
// Mode represents the approval mode for new connections.
9
type Mode string
@@ -32,10 +35,14 @@ func (m *Approver) GetApprovalMode() Mode {
35
return m.approvalMode
36
}
37
35
-func (m *Approver) SetApprovalMode(mode Mode) {
38
+func (m *Approver) SetApprovalMode(mode Mode) error {
39
+ if mode != ModeAuto && mode != ModeManual {
40
+ return fmt.Errorf("invalid approval mode: %q", mode)
41
+ }
42
m.mu.Lock()
43
defer m.mu.Unlock()
44
m.approvalMode = mode
45
+ return nil
46
}
47
48
func (m *Approver) IsLeaseApproved(leaseID string) bool {