Skip to content

security(wave60): cancelGuardJobsForOrg fetches Trigger.dev runs without timeout or pagination — DoS risk + incomplete cancellation #449

Description

@walidboulanouar

Problem

File: apps/web/src/lib/actions/settings.tscancelGuardJobsForOrg

const res = await fetch(`${TRIGGER_BASE}/api/v3/runs?${qs}`, { headers });

Issue 1 — No AbortSignal/timeout: All 16 Trigger.dev fetch calls (4 tasks × 4 statuses) have no timeout. If the Trigger.dev API is slow or unresponsive, the Server Action that triggered this (plan update / product removal) stalls indefinitely — blocking the admin's response.

Issue 2 — Fetches first page only, no pagination: The /api/v3/runs endpoint is paginated. The code only reads body.data from the first page. An org with many queued runs (e.g., a large NRD feed fan-out spike) will have subsequent pages that are never fetched and never cancelled. When the Guard subscription lapses those runs continue executing against resources the org no longer pays for (#247).

Issue 3 — Runs filtered in JS, not by API: The code fetches ALL runs for a task/status globally, then filters by metadata.org_id in JavaScript. This is inefficient and means the first-page truncation issue in point 2 is worse — the API may return 100 runs for other orgs before the target org's runs appear.

Fix

  1. Add signal: AbortSignal.timeout(10_000) to each fetch call.
  2. Either use the Trigger.dev SDK (already a dep in @repo/jobs) to cancel by tag/metadata server-side, or loop through pages until next_page is null.
  3. If the Trigger API supports filtering by metadata, pass metadata[org_id]=${orgId} as a query param to reduce client-side filtering.

Metadata

Metadata

Assignees

No one assigned

    Labels

    securitySecurity findings

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions