Summary
parseCSVInt and its float sibling parse operator-supplied CSV cells with fmt.Sscanf, which stops at the first character it cannot consume and still reports success. Money quantities read from an operator-editable file therefore pass validation with wrong values, contrary to the project's strict-integer-parsing rule. The parsed counts flow into savingsPerInstance, ApplyInstanceLimit and the purchase loop.
Location
cmd/multi_service_csv.go:167-190 at 3c0f8ac
Failure scenario
A Count cell reading 3.7 parses as 3 with err == nil. 12 units parses as 12. -5 parses as -5 and reaches the purchase loop as a negative count. An EstimatedSavings cell of 1000 USD becomes 1000. All four were run against the pinned toolchain and behave exactly this way. No boundary rejection exists for any of them.
Evidence
func parseCSVInt(record []string, colIdx map[string]int, fieldName string, target *int) error {
value := getCSVField(record, colIdx, fieldName)
if value == "" { return nil }
if _, err := fmt.Sscanf(value, "%d", target); err != nil {
return fmt.Errorf("invalid %s value '%s': %w", fieldName, value, err)
}
return nil
}
Suggested fix
Parse the whole trimmed cell with strconv.Atoi and strconv.ParseFloat, which reject trailing characters, and reject a negative Count explicitly.
Found by the 2026-09-02 codebase audit, finding A10-006, reported by one reviewer and independently confirmed by a second. Full report: docs/audits/codebase-audit-2026-09-02.md.
Summary
parseCSVIntand its float sibling parse operator-supplied CSV cells withfmt.Sscanf, which stops at the first character it cannot consume and still reports success. Money quantities read from an operator-editable file therefore pass validation with wrong values, contrary to the project's strict-integer-parsing rule. The parsed counts flow intosavingsPerInstance,ApplyInstanceLimitand the purchase loop.Location
cmd/multi_service_csv.go:167-190at 3c0f8acFailure scenario
A
Countcell reading3.7parses as3witherr == nil.12 unitsparses as12.-5parses as-5and reaches the purchase loop as a negative count. AnEstimatedSavingscell of1000 USDbecomes1000. All four were run against the pinned toolchain and behave exactly this way. No boundary rejection exists for any of them.Evidence
Suggested fix
Parse the whole trimmed cell with
strconv.Atoiandstrconv.ParseFloat, which reject trailing characters, and reject a negativeCountexplicitly.Found by the 2026-09-02 codebase audit, finding
A10-006, reported by one reviewer and independently confirmed by a second. Full report:docs/audits/codebase-audit-2026-09-02.md.