mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
fix(frontend): Validate and sanitize cron expressions for scheduler API (#10719)
<!-- Clearly explain the need for these changes: --> ### Need for these changes 💥 This PR resolves Linear issue `SECRT-1290`, addressing a critical bug where the scheduler API fails with a "Wrong number of fields" error when empty or invalid cron expressions are submitted from the frontend. This was causing production errors and a poor user experience. It was an off by one error ### Changes 🏗️ Fix off by one error + add additional logging / error messaging when someone makes an invalid cron https://github.com/user-attachments/assets/775881a9-707b-4c4f-b23a-bd7118a358ee ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Attempt to schedule an agent with an empty cron expression from the UI and confirm a frontend toast error. - [x] Attempt to schedule an agent with an incomplete yearly cron (no months selected) from the UI and confirm a frontend toast error and UI warning. - [x] Attempt to schedule an agent with an incomplete monthly cron (no days selected) from the UI and confirm a frontend toast error and UI warning. - [x] Attempt to schedule an agent with an incomplete weekly cron (no days selected) from the UI and confirm a frontend toast error and UI warning. - [x] Verify that valid cron expressions can still be scheduled successfully. - [x] Run backend unit tests for scheduler cron validation. - [x] Run frontend unit tests for cron expression utility. --- Linear Issue: [SECRT-1290](https://linear.app/autogpt/issue/SECRT-1290) <a href="https://cursor.com/background-agent?bcId=bc-8bc10502-9498-4dbd-afa2-93e15990fa8c"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/open-in-cursor-dark.svg"> <source media="(prefers-color-scheme: light)" srcset="https://cursor.com/open-in-cursor-light.svg"> <img alt="Open in Cursor" src="https://cursor.com/open-in-cursor.svg"> </picture> </a> <a href="https://cursor.com/agents?id=bc-8bc10502-9498-4dbd-afa2-93e15990fa8c"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/open-in-web-dark.svg"> <source media="(prefers-color-scheme: light)" srcset="https://cursor.com/open-in-web-light.svg"> <img alt="Open in Web" src="https://cursor.com/open-in-web.svg"> </picture> </a> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Swifty <craigswift13@gmail.com>
This commit is contained in:
@@ -39,6 +39,17 @@ export function CronSchedulerDialog({
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate cron expression before proceeding
|
||||
if (!cronExpression || cronExpression.trim() === "") {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Invalid schedule",
|
||||
description: "Please enter a valid cron expression",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
afterCronCreation(cronExpression, scheduleName);
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
@@ -213,6 +213,11 @@ export function CronScheduler({
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
{selectedWeekDays.length === 0 && (
|
||||
<p className="text-sm text-red-500">
|
||||
Please select at least one day of the week
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{frequency === "monthly" && (
|
||||
@@ -274,6 +279,11 @@ export function CronScheduler({
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{selectedMonthDays.length === 0 && (
|
||||
<p className="text-sm text-red-500">
|
||||
Please select at least one day of the month
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{frequency === "yearly" && (
|
||||
@@ -287,7 +297,9 @@ export function CronScheduler({
|
||||
if (selectedMonths.length === months.length) {
|
||||
setSelectedMonths([]);
|
||||
} else {
|
||||
setSelectedMonths(Array.from({ length: 12 }, (_, i) => i));
|
||||
setSelectedMonths(
|
||||
Array.from({ length: 12 }, (_, i) => i + 1),
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -319,6 +331,11 @@ export function CronScheduler({
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{selectedMonths.length === 0 && (
|
||||
<p className="text-sm text-red-500">
|
||||
Please select at least one month
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -872,6 +872,16 @@ export default function useAgentGraph(
|
||||
) => {
|
||||
if (!savedAgent || isScheduling) return;
|
||||
|
||||
// Validate cron expression
|
||||
if (!cronExpression || cronExpression.trim() === "") {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Invalid schedule",
|
||||
description: "Please enter a valid cron expression",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setIsScheduling(true);
|
||||
try {
|
||||
await api.createGraphExecutionSchedule({
|
||||
@@ -891,7 +901,7 @@ export default function useAgentGraph(
|
||||
router.push("/monitoring");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
console.error("Error scheduling agent:", error);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error scheduling agent",
|
||||
|
||||
@@ -49,16 +49,19 @@ export function makeCronExpression(params: CronExpressionParams): string {
|
||||
if (frequency === "daily") return `${params.minute} ${params.hour} * * *`;
|
||||
if (frequency === "weekly") {
|
||||
const { minute, hour, days } = params;
|
||||
if (days.length === 0) return ""; // Return empty string for invalid weekly schedule
|
||||
const weekDaysExpr = days.sort((a, b) => a - b).join(",");
|
||||
return `${minute} ${hour} * * ${weekDaysExpr}`;
|
||||
}
|
||||
if (frequency === "monthly") {
|
||||
const { minute, hour, days } = params;
|
||||
if (days.length === 0) return ""; // Return empty string for invalid monthly schedule
|
||||
const monthDaysExpr = days.sort((a, b) => a - b).join(",");
|
||||
return `${minute} ${hour} ${monthDaysExpr} * *`;
|
||||
}
|
||||
if (frequency === "yearly") {
|
||||
const { minute, hour, months } = params;
|
||||
if (months.length === 0) return ""; // Return empty string for invalid yearly schedule
|
||||
const monthList = months.sort((a, b) => a - b).join(",");
|
||||
return `${minute} ${hour} 1 ${monthList} *`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user