Address Gadfly review on #7: garden cap, dim validation, shared errors
Build image / build-and-push (push) Successful in 5s
Build image / build-and-push (push) Successful in 5s
Fixes from the PR #26 adversarial review (graded 18 real / 0 false positive). Correctness / security - maxGardenCM fixed to 10_000 (100 m), matching its comment — it was 100_000 cm (1 km), 10x too lax (5 models flagged this). - Dimension validation now rejects NaN/Inf (which slip past naive comparisons) and subnormal-tiny positives, via a finite [1cm, 100m] check. Name (200) and notes (10_000) are length-capped so untrusted input can't balloon storage. - Update version binding is `required,min=1`, so a negative/zero version is a 400, not a 409. Maintainability / performance - One unified writeServiceError (new errors.go) maps every auth + resource sentinel; writeResourceError removed. writeVersionConflict and parseIDParam moved to errors.go (shared, not in the gardens feature file). - Request structs share an embedded gardenFields (one toInput). - CreateGarden uses INSERT ... RETURNING (one round-trip). - ListGardensForOwner has a defensive LIMIT (pagination is post-v1). Tests: name/notes length, NaN/Inf/subnormal dims, dimension-at-cap valid, negative/zero version -> 400. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01JdQpdYYsTgtkJBxbcpAszi
This commit is contained in:
+27
-10
@@ -2,17 +2,22 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"gitea.stevedudenhoeffer.com/steve/pansy/internal/domain"
|
||||
)
|
||||
|
||||
// Garden sizing bounds. A new garden with no dimensions defaults to a 10 m
|
||||
// square; the max is a generous sanity cap (100 m) that also guards against
|
||||
// absurd or overflow-y values.
|
||||
// Garden sizing and field bounds. A new garden with no dimensions defaults to a
|
||||
// 10 m square; dimensions must be finite and within [1 cm, 100 m] — a generous
|
||||
// sanity range that also rejects NaN/Inf and absurd values. Name/notes are
|
||||
// length-capped so untrusted input can't balloon storage.
|
||||
const (
|
||||
defaultGardenCM = 1000
|
||||
maxGardenCM = 100_000
|
||||
defaultGardenCM = 1000 // 10 m
|
||||
minGardenCM = 1 // 1 cm
|
||||
maxGardenCM = 10_000 // 100 m
|
||||
maxGardenNameLen = 200
|
||||
maxGardenNotesLen = 10_000
|
||||
)
|
||||
|
||||
// gardenRole ranks a user's access to a garden. Higher includes lower
|
||||
@@ -118,12 +123,17 @@ func (s *Service) DeleteGarden(ctx context.Context, actorID, gardenID int64) err
|
||||
// filled in (create); without it, every field must be supplied (update).
|
||||
func gardenFromInput(in GardenInput, applyDefaults bool) (*domain.Garden, error) {
|
||||
name := strings.TrimSpace(in.Name)
|
||||
if name == "" {
|
||||
if name == "" || len(name) > maxGardenNameLen {
|
||||
return nil, domain.ErrInvalidInput
|
||||
}
|
||||
notes := strings.TrimSpace(in.Notes)
|
||||
if len(notes) > maxGardenNotesLen {
|
||||
return nil, domain.ErrInvalidInput
|
||||
}
|
||||
|
||||
// 0 means "unset": defaulted on create, rejected on update. A negative value
|
||||
// is always an explicit error (never silently corrected to the default).
|
||||
// 0 means "unset": defaulted on create, rejected on update. Anything else
|
||||
// must be a finite length in range — this also rejects negatives, NaN, Inf,
|
||||
// and subnormal-tiny values (which are > 0 but below 1 cm).
|
||||
width, height := in.WidthCM, in.HeightCM
|
||||
if applyDefaults {
|
||||
if width == 0 {
|
||||
@@ -133,7 +143,7 @@ func gardenFromInput(in GardenInput, applyDefaults bool) (*domain.Garden, error)
|
||||
height = defaultGardenCM
|
||||
}
|
||||
}
|
||||
if width <= 0 || height <= 0 || width > maxGardenCM || height > maxGardenCM {
|
||||
if !validDimensionCM(width) || !validDimensionCM(height) {
|
||||
return nil, domain.ErrInvalidInput
|
||||
}
|
||||
|
||||
@@ -150,6 +160,13 @@ func gardenFromInput(in GardenInput, applyDefaults bool) (*domain.Garden, error)
|
||||
WidthCM: width,
|
||||
HeightCM: height,
|
||||
UnitPref: unit,
|
||||
Notes: strings.TrimSpace(in.Notes),
|
||||
Notes: notes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// validDimensionCM reports whether v is a finite length within the allowed
|
||||
// garden range. Guards against NaN/Inf (which slip past naive < / > comparisons)
|
||||
// and subnormal-tiny positives.
|
||||
func validDimensionCM(v float64) bool {
|
||||
return !math.IsNaN(v) && !math.IsInf(v, 0) && v >= minGardenCM && v <= maxGardenCM
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user