Skip to content

[WAVE-44] LOW: slaHoursForPlan / domainCapForPlan silently fall through on unexpected plan values — billing contract violation #359

Description

@walidboulanouar

Files:

  • apps/web/src/lib/plan-sla.ts lines 4–13 (slaHoursForPlan), 18–27 (domainCapForPlan)
  • packages/jobs/src/takedown.ts lines 74–85 (slaHoursForPlan duplicate)

Vulnerability:
Both slaHoursForPlan and domainCapForPlan accept plan: string and fall through to a default branch that silently assigns entry-tier values (72 h SLA, 25-domain cap) for any unexpected input:

export function slaHoursForPlan(plan: string): number {
  switch (plan) {
    case "enterprise": return 2;
    case "mid":        return 24;
    default:           return 72; // ← silently hit by null, "", or a new enum value
  }
}

If org.plan is ever null (raw SQL update, future migration bug, or an admin bypassing Drizzle) or a new enum value is added without updating these switches, enterprise orgs silently receive:

  • 72-hour DMCA SLA instead of 2 hours (breach of the enterprise contract; the 1-hour escalation gate in goals.md never fires)
  • 25-domain cap instead of 5000 (import rejected for legitimate enterprise usage)

The organizations.plan column is notNull() today, but the defensive gap is the silent default — no warning is raised and the caller can't distinguish "entry plan" from "unexpected plan value".

Fix:

  1. Narrow the parameter type to Plan (the branded union type already in org-context.tsx) so TypeScript catches missing cases at compile time.
  2. Add a runtime guard that logs a warning on unrecognized values:
export function slaHoursForPlan(plan: Plan | string): number {
  switch (plan) {
    case "enterprise": return 2;
    case "mid":        return 24;
    case "entry":      return 72;
    default:
      console.error(`[slaHoursForPlan] unknown plan "${plan}" — defaulting to 72h`);
      return 72;
  }
}
  1. Sync the duplicate slaHoursForPlan in packages/jobs/src/takedown.ts (lines 74–85) with the same guard, or better: extract both into a shared @repo/plan-sla package.

Metadata

Metadata

Assignees

No one assigned

    Labels

    securitySecurity findings

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions