Skip to content

fix(subscription): reject interval_seconds that overflow chrono Duration (#51) - #65

Open
OluRemiFour wants to merge 1 commit into
StellarSend:mainfrom
OluRemiFour:fix/subscription-interval-overflow-panic
Open

fix(subscription): reject interval_seconds that overflow chrono Duration (#51)#65
OluRemiFour wants to merge 1 commit into
StellarSend:mainfrom
OluRemiFour:fix/subscription-interval-overflow-panic

Conversation

@OluRemiFour

Copy link
Copy Markdown
Contributor

Summary

CreateSubscriptionRequest.interval_seconds is a plain i64 validated only for being positive. A client-supplied value near i64::MAX deserializes cleanly and is passed into chrono::Duration::seconds(), which panics once the value would overflow chrono's internal millisecond representation (i64::MAX / 1000-ish). That panic was reachable:

  1. At creation timePOST /api/subscriptions with "interval_seconds": 9223372036854775807 panicked instead of returning a 4xx, with no catch_unwind/CatchPanicLayer anywhere in the middleware stack.
  2. Inside the keeper looprun_due_executions runs the identical, unguarded expression for every successful execution; if a malformed interval ever reached the DB, the panic would fire inside run_keeper_loop's spawned task and silently take down scheduling for every subscription.

Fix

  • SubscriptionService::create — bound interval_seconds with chrono::Duration::try_seconds at validation time, returning AppError::Validation (→ 422/VALIDATION_ERROR) instead of ever panicking. No hardcoded internal threshold; the fallible constructor defines the safe range.
  • SubscriptionService::run_due_executions — use try_seconds to reschedule (defense in depth for stored/less-trusted values). If a stored interval can't be represented, the row is marked failed with a descriptive last_error and the loop continues, so one corrupt row can no longer kill the keeper loop for everyone else.

Acceptance criteria

  • POST /api/subscriptions with interval_seconds at or near i64::MAX returns a 4xx validation error instead of panicking.
  • Both call sites use the non-panicking Duration::try_seconds constructor.
  • Regression test submits i64::MAX and other extreme values to SubscriptionService::create and asserts a clean AppError::Validation (absence of panic) — no DB needed, validation short-circuits before any sqlx call.

Tests

cargo test: 34 passed; the new create_rejects_interval_seconds_outside_duration_range covers the panic boundary. cargo check --all-targets clean; no new clippy/rustfmt findings in this file.

The retry_at = Utc::now() + Duration::seconds(60) constant is not attacker-controlled and is intentionally left unchanged (out of scope per the issue). Both this and the companion "no minimum interval" issue touch the same field and call site; this change is deliberately additive (only rejects values chrono cannot represent) so the two fixes merge cleanly.

Fixes #51

…ion (StellarSend#51)

Duration::seconds panics when a value near i64::MAX overflows chrono's internal millisecond representation, turning a POST /api/subscriptions into a server panic and, if such a value ever reached the DB, silently killing the background keeper loop from within its spawned task.

Bound interval_seconds at validation time with Duration::try_seconds, returning AppError::Validation instead of panicking, and use the same fallible constructor inside run_due_executions to reschedule, marking a row with an unrepresentable stored interval as failed so the keeper loop keeps running for every other subscription.

Adds a regression test submitting i64::MAX and other extreme values to create() and asserting a clean AppError::Validation rather than a panic.
@OluRemiFour
OluRemiFour force-pushed the fix/subscription-interval-overflow-panic branch from 6947aef to 14592e4 Compare August 18, 2026 10:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

interval_seconds near i64::MAX panics chrono::Duration::seconds() in SubscriptionService::create and run_due_executions

1 participant