fix: improve gateway security and clean up helpers
Use constant-time comparison for auth token validation to prevent timing attacks. Consolidate writeJSONError into writeJSON, add error logging for JSON encoding failures, remove unused isSecretKey helper, and handle syscall.Exec error on restart. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7e73df9752
commit
fd17e4b174
5 changed files with 23 additions and 34 deletions
|
|
@ -590,7 +590,10 @@ func gatewayCmd() {
|
|||
fmt.Printf("Error finding executable: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
syscall.Exec(exe, os.Args, os.Environ())
|
||||
if err := syscall.Exec(exe, os.Args, os.Environ()); err != nil {
|
||||
fmt.Printf("Error restarting: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package gateway
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"crypto/subtle"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
|
@ -29,7 +29,7 @@ func (s *Server) authMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
|||
}
|
||||
|
||||
token := authHeader[len(prefix):]
|
||||
if token != apiKey {
|
||||
if subtle.ConstantTimeCompare([]byte(token), []byte(apiKey)) != 1 {
|
||||
writeJSONError(w, http.StatusForbidden, "invalid token")
|
||||
return
|
||||
}
|
||||
|
|
@ -39,7 +39,5 @@ func (s *Server) authMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
|||
}
|
||||
|
||||
func writeJSONError(w http.ResponseWriter, code int, message string) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(code)
|
||||
json.NewEncoder(w).Encode(map[string]string{"error": message})
|
||||
writeJSON(w, code, map[string]string{"error": message})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ package gateway
|
|||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/KarakuriAgent/clawdroid/pkg/config"
|
||||
"github.com/KarakuriAgent/clawdroid/pkg/logger"
|
||||
)
|
||||
|
||||
// handleGetSchema returns the configuration schema.
|
||||
|
|
@ -134,15 +134,7 @@ func restoreSecrets(incoming, current map[string]interface{}) {
|
|||
func writeJSON(w http.ResponseWriter, code int, v interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(code)
|
||||
json.NewEncoder(w).Encode(v)
|
||||
}
|
||||
|
||||
// isSecretKey returns true if the given JSON key should be treated as a secret.
|
||||
// This is used by the handlers to check whether to mask a value, and is also
|
||||
// exposed for testing purposes.
|
||||
func isSecretKey(key string) bool {
|
||||
// Check the leaf key (last segment after dots)
|
||||
parts := strings.SplitN(key, ".", -1)
|
||||
leaf := parts[len(parts)-1]
|
||||
return secretKeys[leaf]
|
||||
if err := json.NewEncoder(w).Encode(v); err != nil {
|
||||
logger.ErrorCF("gateway", "failed to encode JSON response", map[string]interface{}{"error": err.Error()})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,8 +48,8 @@ var secretKeys = map[string]bool{
|
|||
func BuildSchema(defaultCfg *config.Config) SchemaResponse {
|
||||
var sections []SchemaSection
|
||||
|
||||
cfgType := reflect.TypeOf(*defaultCfg)
|
||||
cfgVal := reflect.ValueOf(*defaultCfg)
|
||||
cfgType := reflect.TypeOf(defaultCfg).Elem()
|
||||
cfgVal := reflect.ValueOf(defaultCfg).Elem()
|
||||
|
||||
for i := 0; i < cfgType.NumField(); i++ {
|
||||
field := cfgType.Field(i)
|
||||
|
|
|
|||
|
|
@ -962,22 +962,18 @@ func TestAuthMiddleware_CorrectToken_200(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestIsSecretKey(t *testing.T) {
|
||||
tests := []struct {
|
||||
key string
|
||||
want bool
|
||||
}{
|
||||
{"api_key", true},
|
||||
{"gateway.api_key", true},
|
||||
{"channels.telegram.token", true},
|
||||
{"model", false},
|
||||
{"gateway.port", false},
|
||||
func TestSecretKeys(t *testing.T) {
|
||||
wantSecret := []string{"api_key", "token", "bot_token", "app_token", "channel_secret", "channel_access_token"}
|
||||
for _, k := range wantSecret {
|
||||
if !secretKeys[k] {
|
||||
t.Errorf("secretKeys[%q] = false, want true", k)
|
||||
}
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
got := isSecretKey(tc.key)
|
||||
if got != tc.want {
|
||||
t.Errorf("isSecretKey(%q) = %v, want %v", tc.key, got, tc.want)
|
||||
notSecret := []string{"model", "host", "port", "enabled"}
|
||||
for _, k := range notSecret {
|
||||
if secretKeys[k] {
|
||||
t.Errorf("secretKeys[%q] = true, want false", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue