fix: reject duplicate env var keys with 400 instead of deduplicating
All checks were successful
Check / check (pull_request) Successful in 3m18s

Replace deduplicateEnvPairs with validateEnvPairs that returns a 400
Bad Request error when duplicate keys are submitted, instead of
silently deduplicating (last wins).

Ref: #158
This commit is contained in:
clawbot
2026-03-10 18:37:07 -07:00
parent eaf3d48eae
commit 316ccae665
2 changed files with 19 additions and 32 deletions

View File

@@ -912,13 +912,12 @@ type envPairJSON struct {
// envVarMaxBodyBytes is the maximum allowed request body size for env var saves (1 MB).
const envVarMaxBodyBytes = 1 << 20
// deduplicateEnvPairs validates and deduplicates env var pairs.
// It rejects empty keys (returns a non-empty error string) and
// deduplicates by key, keeping the last occurrence.
func deduplicateEnvPairs(pairs []envPairJSON) ([]models.EnvVarPair, string) {
seen := make(map[string]int, len(pairs))
// validateEnvPairs validates env var pairs.
// It rejects empty keys and duplicate keys (returns a non-empty error string).
func validateEnvPairs(pairs []envPairJSON) ([]models.EnvVarPair, string) {
seen := make(map[string]bool, len(pairs))
var result []models.EnvVarPair
result := make([]models.EnvVarPair, 0, len(pairs))
for _, p := range pairs {
trimmedKey := strings.TrimSpace(p.Key)
@@ -926,12 +925,13 @@ func deduplicateEnvPairs(pairs []envPairJSON) ([]models.EnvVarPair, string) {
return nil, "empty environment variable key is not allowed"
}
if idx, exists := seen[trimmedKey]; exists {
result[idx] = models.EnvVarPair{Key: trimmedKey, Value: p.Value}
} else {
seen[trimmedKey] = len(result)
result = append(result, models.EnvVarPair{Key: trimmedKey, Value: p.Value})
if seen[trimmedKey] {
return nil, "duplicate environment variable key: " + trimmedKey
}
seen[trimmedKey] = true
result = append(result, models.EnvVarPair{Key: trimmedKey, Value: p.Value})
}
return result, ""
@@ -941,7 +941,7 @@ func deduplicateEnvPairs(pairs []envPairJSON) ([]models.EnvVarPair, string) {
// It reads a JSON array of {key, value} objects from the request body,
// deletes all existing env vars for the app, and inserts the full
// submitted set atomically within a database transaction.
// Duplicate keys are deduplicated server-side (last occurrence wins).
// Duplicate keys are rejected with a 400 Bad Request error.
func (h *Handlers) HandleEnvVarSave() http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
appID := chi.URLParam(request, "id")
@@ -967,7 +967,7 @@ func (h *Handlers) HandleEnvVarSave() http.HandlerFunc {
return
}
modelPairs, validationErr := deduplicateEnvPairs(pairs)
modelPairs, validationErr := validateEnvPairs(pairs)
if validationErr != "" {
h.respondJSON(writer, request, map[string]string{
"error": validationErr,