Skip to content

fix(cron): normalize flat legacy job rows#71534

Merged
vincentkoc merged 1 commit into
mainfrom
fix-cron-legacy-flat-jobs
Apr 25, 2026
Merged

fix(cron): normalize flat legacy job rows#71534
vincentkoc merged 1 commit into
mainfrom
fix-cron-legacy-flat-jobs

Conversation

@vincentkoc

Copy link
Copy Markdown
Member

Summary

  • normalize stored flat legacy cron rows with top-level cron, tz, session, and message into canonical schedule, sessionTarget, and payload
  • preserve legacy tools as payload.toolsAllow and remove the obsolete top-level flat fields from the normalized in-memory shape
  • add regression coverage for both direct normalization and store-load recompute so startup/list paths do not crash on missing nested .kind

Fixes #43351.

Related to #38766 and #57640.

Validation

  • pnpm test src/cron/normalize.test.ts src/cron/service/store.load-missing-session-target.test.ts src/cli/cron-cli/shared.test.ts
  • OPENCLAW_LOCAL_CHECK=0 pnpm check:changed

@vincentkoc vincentkoc self-assigned this Apr 25, 2026
@vincentkoc vincentkoc marked this pull request as ready for review April 25, 2026 10:27
@openclaw-barnacle openclaw-barnacle Bot added the maintainer Maintainer-authored PR label Apr 25, 2026
@aisle-research-bot

aisle-research-bot Bot commented Apr 25, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🟠 High DoS risk: legacy flat schedule hydration enables extremely frequent schedules (everyMs/cron) without minimum interval validation
1. 🟠 DoS risk: legacy flat schedule hydration enables extremely frequent schedules (everyMs/cron) without minimum interval validation
Property Value
Severity High
CWE CWE-400
Location src/cron/normalize.ts:147-515

Description

The new legacy hydration path inferTopLevelSchedule() copies top-level schedule-like fields into next.schedule and passes them through coerceSchedule(), but no minimum interval or frequency validation is enforced.

This makes it possible for attacker-controlled or corrupted stored job rows using legacy flat fields (e.g., everyMs: 0, everyMs: 1, or high-frequency cron expressions) to be normalized into an active schedule and then executed extremely frequently, potentially causing CPU exhaustion / tight execution loops.

Key points:

  • inferTopLevelSchedule() blindly copies at/atMs/everyMs/anchorMs/expr/cron/tz/staggerMs from the raw object when present.
  • coerceSchedule() normalizes fields and deduces kind, but does not enforce sane lower bounds for everyMs or validate cron frequency.
  • Downstream scheduling clamps everyMs to a minimum of 1ms (e.g., Math.max(1, ...)), which is still effectively an abusive schedule.

Vulnerable code:

for (const field of ["at", "atMs", "everyMs", "anchorMs", "expr", "cron", "tz", "staggerMs"]) {
  if (field in next) {
    schedule[field] = next[field];
  }
}
...
next.schedule = inferredSchedule;

If cron job definitions can be influenced by untrusted users (API input) or by tampering with persisted job specs (store), this can be used to trigger resource exhaustion by creating jobs that are due continuously.

Recommendation

Enforce schedule safety constraints during normalization (and ideally also at persistence boundaries):

  • Reject or clamp everyMs below a safe minimum (commonly >= 1000ms or a configurable minimum).
  • Validate cron expressions and reject schedules that would run more frequently than the minimum allowed cadence.
  • Consider rejecting ambiguous combinations even when kind is specified (e.g., kind:"cron" but everyMs also present).

Example (enforce a minimum interval in coerceSchedule() for every):

const MIN_EVERY_MS = 1000;
...
if (next.kind === "every") {
  const everyMsRaw = coerceFiniteScheduleNumber(schedule.everyMs);
  if (everyMsRaw === undefined || everyMsRaw < MIN_EVERY_MS) {
    throw new Error(`invalid everyMs: must be >= ${MIN_EVERY_MS}`);
  }
  next.everyMs = Math.floor(everyMsRaw);
}

And for cron, compute the next two run times and enforce that the delta is >= the minimum cadence, rejecting expressions that violate the policy.


Analyzed PR: #71534 at commit c0b8596

Last updated on: 2026-04-25T10:30:43Z

@vincentkoc vincentkoc merged commit e0546ed into main Apr 25, 2026
95 of 96 checks passed
@vincentkoc vincentkoc deleted the fix-cron-legacy-flat-jobs branch April 25, 2026 10:29
@greptile-apps

greptile-apps Bot commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds normalization for legacy flat cron job rows (where cron, tz, session, message, and tools live at the top level rather than nested under schedule/payload) so that the startup and list paths do not crash on missing nested .kind. The implementation correctly infers a nested schedule from kind/cron/tz, maps sessionsessionTarget, lifts messagepayload, migrates toolspayload.toolsAllow, and strips all obsolete top-level fields via stripLegacyTopLevelFields.

Confidence Score: 5/5

This PR is safe to merge — no logic errors or regressions found.

The normalization pipeline is correct: inferTopLevelSchedule correctly reads top-level kind/cron/tz fields and converts them via the existing coerceSchedule helper (which already handles cronexpr aliasing). sessionsessionTarget mapping is guarded by the existing normalizeSessionTarget validator. toolspayload.toolsAllow fallback uses ?? correctly with the allowNull: true sentinel. stripLegacyTopLevelFields cleanly removes all migrated fields. The two new test cases (direct normalization and store-load recompute) provide solid regression coverage. No P1/P0 issues identified.

No files require special attention.

Reviews (1): Last reviewed commit: "fix(cron): normalize flat legacy job row..." | Re-trigger Greptile

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c0b8596051

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CHANGELOG.md
- Google video generation: download direct MLDev Veo `video.uri` results instead of passing them through the Files API path, fixing 404s after successful generation/polling. Fixes #71200. Thanks @panhaishan.
- Google video generation: fall back to the REST `predictLongRunning` Veo endpoint for text-only SDK 404s while keeping reference image/video generation on the SDK path. Fixes #62309 and #63008. (#62343) Thanks @leoleedev.
- MiniMax music generation: switch the bundled default model from the unsupported `music-2.5+` id to the current `music-2.6` API model. Fixes #64870 and addresses the music default from #62315. Thanks @noahclanman and @edwardzheng1.
- Cron: hydrate flat legacy job rows with top-level `cron`, `tz`, `session`, and `message` fields into canonical schedule, target, and payload objects before startup recomputes run times. Fixes #43351.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add "Thanks @author" attribution to changelog entry

The new changelog bullet is missing contributor attribution, which violates the repo rule in AGENTS.md that says every added entry must include at least one Thanks @author. This is likely to be flagged during maintainer review and makes the release note inconsistent with the required changelog format.

Useful? React with 👍 / 👎.

Comment thread src/cron/normalize.ts
Comment on lines +414 to +418
delete next.cron;
delete next.tz;
delete next.at;
delete next.atMs;
delete next.everyMs;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Strip top-level expr when normalizing legacy cron rows

inferTopLevelSchedule now accepts flat legacy expr values, but the legacy-field stripping block does not remove next.expr. That leaves an extra top-level expr after normalization, so cron.add/cron.update requests using flat legacy expr still fail schema validation (additionalProperties: false) even though a canonical schedule.expr was produced.

Useful? React with 👍 / 👎.

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

Labels

maintainer Maintainer-authored PR size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Cron fails to start: TypeError Cannot read properties of undefined (reading 'kind')

1 participant