Problem
RuntimeOrchestrationContext.createTimer() (packages/durabletask-js/src/worker/runtime-orchestration-context.ts, line 296) accepts a number | Date parameter but performs no validation on the input value. When non-finite numbers (NaN, Infinity, -Infinity) or invalid Date objects are passed, the method silently creates timers with corrupted timestamps instead of reporting the error.
Example: ctx.createTimer(parseInt("abc")) → NaN → new Date(NaN) → protobuf Timestamp with seconds=0 (Unix epoch) → timer fires immediately instead of failing.
Specific cases:
ctx.createTimer(NaN) → timer fires at Unix epoch (1970-01-01) — silently wrong
ctx.createTimer(Infinity) → timer fires at Unix epoch — silently wrong
ctx.createTimer(new Date("not a date")) → timer fires at Unix epoch — silently wrong
Root Cause
The createTimer method converts number inputs to Date objects via new Date(this._currentUtcDatetime.getTime() + fireAt * 1000) but never validates that fireAt is a finite number. When fireAt is NaN or Infinity, the resulting Date is invalid. The protobuf Timestamp.fromDate() method then converts NaN timestamps to 0 (epoch) during serialization, producing a timer that fires immediately.
Similarly, passing an invalid Date object bypasses the number conversion path (since instanceof Date is true) but produces the same corrupted timestamp.
The nearby createRetryTimer method (line 536) does validate its delay parameter (if (delayMs <= 0) throw Error), making this an inconsistency within the same class.
Proposed Fix
Add input validation to createTimer():
- For
number inputs: verify typeof === "number" and Number.isFinite(fireAt), throwing a descriptive error for NaN/Infinity
- For
Date inputs: verify !isNaN(date.getTime()), throwing a descriptive error for invalid Date objects
- Add unit tests covering all invalid and valid input combinations
Impact
Severity: Medium-High. Orchestrator authors who compute timer delays from external data (e.g., parseInt(userInput)) or construct Date objects from strings can silently get timers that fire at the wrong time. This causes:
- Retry delays not being applied (immediate retry instead of backoff)
- Rate-limiting timers being skipped
- Scheduled operations executing immediately instead of at the intended time
The error is silent — no exception is thrown, no log is emitted — making it very difficult to diagnose.
Problem
RuntimeOrchestrationContext.createTimer()(packages/durabletask-js/src/worker/runtime-orchestration-context.ts, line 296) accepts anumber | Dateparameter but performs no validation on the input value. When non-finite numbers (NaN,Infinity,-Infinity) or invalidDateobjects are passed, the method silently creates timers with corrupted timestamps instead of reporting the error.Example:
ctx.createTimer(parseInt("abc"))→NaN→new Date(NaN)→ protobufTimestampwith seconds=0 (Unix epoch) → timer fires immediately instead of failing.Specific cases:
ctx.createTimer(NaN)→ timer fires at Unix epoch (1970-01-01) — silently wrongctx.createTimer(Infinity)→ timer fires at Unix epoch — silently wrongctx.createTimer(new Date("not a date"))→ timer fires at Unix epoch — silently wrongRoot Cause
The
createTimermethod converts number inputs toDateobjects vianew Date(this._currentUtcDatetime.getTime() + fireAt * 1000)but never validates thatfireAtis a finite number. WhenfireAtisNaNorInfinity, the resultingDateis invalid. The protobufTimestamp.fromDate()method then convertsNaNtimestamps to0(epoch) during serialization, producing a timer that fires immediately.Similarly, passing an invalid
Dateobject bypasses the number conversion path (sinceinstanceof Dateis true) but produces the same corrupted timestamp.The nearby
createRetryTimermethod (line 536) does validate its delay parameter (if (delayMs <= 0) throw Error), making this an inconsistency within the same class.Proposed Fix
Add input validation to
createTimer():numberinputs: verifytypeof === "number"andNumber.isFinite(fireAt), throwing a descriptive error forNaN/InfinityDateinputs: verify!isNaN(date.getTime()), throwing a descriptive error for invalid Date objectsImpact
Severity: Medium-High. Orchestrator authors who compute timer delays from external data (e.g.,
parseInt(userInput)) or construct Date objects from strings can silently get timers that fire at the wrong time. This causes:The error is silent — no exception is thrown, no log is emitted — making it very difficult to diagnose.