Skip to content

fix(cli): fmt.Sscanf CSV parsing truncates 3.7 to 3 and accepts trailing garbage #1944

Description

@cristim

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions