Skip to content

Update completion wait_timeout_seconds is caller-unbounded #775

Closed

Description

@rmcdaniel

Defect (published Server 2.11.1, Workflow 2.10.2, and native)

An update submitted with wait_for: completed waits up to the caller's wait_timeout_seconds. That field validates only nullable|integer|min:1, on POST /api/workflows/{id}/update/{name} and on bridge update_workflow targets. UpdateWaitPolicy::requestedTimeoutSeconds (Workflow 2.10.2) returns any positive integer unchanged, and DW_V2_UPDATE_WAIT_COMPLETION_TIMEOUT_SECONDS is only the default when the field is absent. So one request can hold a server worker, or in native a request task, for as long as it asks while the update stays incomplete.

Native matches PHP here (updates.rs: timeout.unwrap_or(self.update_wait_seconds)). It holds no transaction or pool connection while it waits, only the request and a wake subscription. PHP holds a request worker for the whole wait.

Found while preparing the Rust bridge adapters review (#325), after Security's #773 review asked for every caller-controlled duration and count to be bounded server-side.

Expected

A documented server-side maximum for wait_timeout_seconds in the shared contract, applied at every ingress (update route, bridge update, any SDK-facing alias) before admission. A longer request is clamped or refused, with the choice documented, and covered by exact-boundary and above-boundary tests on both servers. The wait's own completion semantics are unchanged.

Activity

  1. rmcdaniel commented on Oct 26, 2026

    @rmcdaniel
    MemberAuthor

    Security decision: use a documented 60-second maximum and refuse any larger wait_timeout_seconds with 422 before admission at every ingress. This aligns Server with the now-merged Workflow UpdateWaitPolicy::MAX_COMPLETION_TIMEOUT_SECONDS boundary. Cover 60 and 61 seconds on the regular update route, bridge update route, and every other accepted alias on both PHP and native paths.

  2. rmcdaniel commented on Oct 26, 2026

    @rmcdaniel
    MemberAuthor

    Acceptance refinement from the exact sources:

    • Bound the effective configured default as well as request overrides. Workflow main now clamps completionTimeoutSeconds() to 60. Native config_audit::update_wait_completion_timeout_seconds currently applies only max(1), so a request without an override can still inherit an arbitrarily large value. Match PHP by clamping effective native configuration to 60 and cover configured 60/61 values.
    • Caller-supplied values above 60 remain a 422 before admission.
    • Exercise both regular aliases—POST /workflows/{id}/update/{name} and POST /workflows/{id}/runs/{runId}/update/{name}—plus bridge update.
  3. rmcdaniel commented on Oct 26, 2026

    @rmcdaniel
    MemberAuthor

    Dependency caveat: the current Server head pins durable-workflow/workflow 2.13.1 at b2a3369110b8cbdf060802ff91e475110e66e333. The newly merged 60-second Workflow policy is not yet in that installed artifact, and its merge did not authorize a release or Server dependency update.

    Make the Server PHP path safe independently now: validate caller values at 60 and clamp the application effective default to 60, or use an explicitly qualified dependency update through the normal release path. Acceptance must run against the package actually pinned and installed by Server; Workflow main alone is not runtime evidence.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions