Skip to content

fix(azure): web path silently coerces partial-upfront to all-upfront (bill schedule mismatch) #1503

Description

@cristim

Silent-money bug (web/API path)

crossProviderPaymentAlias (internal/config/validation.go:151-153) coerces an Azure partial-upfront payment option to "upfront" before validation, with the comment "coerce to the all-upfront tier so the rec survives validation."

On the web/API purchase path this means:

POST purchase {provider: azure, service: compute, payment: "partial-upfront"}
  -> normalized to "upfront" (internal/api/validation.go:568)
  -> passes the {upfront, monthly} whitelist
  -> rec.Payment = "upfront"
  -> buildReservationBody maps to billingPlan=Upfront
  -> purchased ALL-UPFRONT silently, on a schedule the caller did not request

Why it matters now

PR #1495 wired Azure billingPlan from the payment option. Before that, Azure always billed upfront regardless, so the coercion was invisible. Now the coerced value drives a live billing schedule, so the silent coercion produces a real cash-flow outcome the caller did not choose. It also contradicts the contract the MCP tool and docs advertise ("partial-upfront is a hard error rather than silently purchased under all-upfront or no-upfront").

This is the exact class the project's feedback_no_silent_fallbacks rule targets: a money-affecting field silently rewritten instead of rejected.

Mitigating factors (why should-fix, not blocker)

  • The normal web UI only offers converter-generated recommendations (providers/azure/internal/recommendations/converter.go:391,397 emit only upfront/monthly), so a typical user cannot trigger this; it requires a hand-crafted partial-upfront payload.
  • Azure charges the same total for upfront vs monthly, so this is a billing-schedule/cash-flow mismatch, not an overcharge.
  • Surface inconsistency: the CLI path now fails loud on Azure partial-upfront (errors in buildReservationBody), while the web path coerces. The two surfaces should behave the same.

Fix

Reject Azure partial-upfront at the web/API validation boundary (fail loud, consistent with the CLI and MCP surfaces) instead of aliasing it to upfront. Azure supports only Upfront and Monthly; partial-upfront has no Azure equivalent and must not be silently mapped.

Found by

Adversarial money-path review during PR #1495 (Azure billing-plan wiring). Pre-existing (the alias predates #1495); #1495 is what makes the coercion reach a live billingPlan.

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