On the web purchase path, validateAndTotalRecommendations (internal/api/handler_purchases.go:1108-1127) only checks UpfrontCost/Savings sign + a $10M cap, and validateExecutePurchaseRequest (:1054-1087) checks count bounds + capacity + account scope — but nothing validates each rec's Term / Payment / Provider / Service, which are client-supplied (config/types.go:295-297) and flow straight to the cloud SDK at execute time. A malformed Term:7 / Payment:"foo" / negative Count reaches the provider call.
Fix: validate each rec's Term/Payment against the provider's allowed values, and Count > 0, at the API boundary (reuse the CLI's validateFlags whitelist if one exists). Add a table-driven test.
Dedup: #379 covers the CLI --input-csv path (cmd/multi_service.go), NOT the web API. Distinct. Surfaced in the purchase-workflow trace.
On the web purchase path,
validateAndTotalRecommendations(internal/api/handler_purchases.go:1108-1127) only checks UpfrontCost/Savings sign + a $10M cap, andvalidateExecutePurchaseRequest(:1054-1087) checks count bounds + capacity + account scope — but nothing validates each rec'sTerm/Payment/Provider/Service, which are client-supplied (config/types.go:295-297) and flow straight to the cloud SDK at execute time. A malformedTerm:7/Payment:"foo"/ negativeCountreaches the provider call.Fix: validate each rec's Term/Payment against the provider's allowed values, and Count > 0, at the API boundary (reuse the CLI's validateFlags whitelist if one exists). Add a table-driven test.
Dedup: #379 covers the CLI
--input-csvpath (cmd/multi_service.go), NOT the web API. Distinct. Surfaced in the purchase-workflow trace.