Description
while looking at the daily-focus route i noticed the POST accepts goal_text and date from the request body without any length cap or format check, which is inconsistent with how goals and milestones handle input.
The Problem
src/app/api/daily-focus/route.ts:49-56:
const body = await req.json();
const { goal_text, date } = body;
if (!goal_text || !goal_text.trim()) {
return NextResponse.json({ error: "Goal cannot be empty" }, { status: 400 });
}
const targetDate = date || new Date().toISOString().split("T")[0];
problems:
- no length limit on
goal_text. an authenticated user can send "a".repeat(5_000_000) and it goes straight to supabase upsert on line 60. compare with src/app/api/goals/route.ts:213-215 (MAX_TITLE_LEN = 100) and src/app/api/milestones/route.ts:63 (stripHtml(title).slice(0, MAX_TITLE_LEN)) that both cap input.
- no html strip, so
<script> payloads land in the DB and get rendered later.
date from body has zero format validation. sending date: "not-a-date" throws a supabase constraint error (500 to the user instead of a proper 400). sending date: "9999-12-31" lets the user pin a "focus" onto any future or past day of their choice, which breaks the "daily" semantics of the feature.
the GET/DELETE also read date from searchParams without validation (src/app/api/daily-focus/route.ts:16-22, 87-92) so the same fix should cover those.
Proposed Fix
- add
MAX_GOAL_LEN = 280 (match daily-note which does cap at 280 chars in src/app/api/daily-note/route.ts:81) and slice/reject longer input
- run
stripHtml on goal_text before saving, same as goals route
- validate
date (and the query-param variants in GET/DELETE) against /^\d{4}-\d{2}-\d{2}$/ and reject with 400 if bad
- optionally clamp
targetDate so users cannot set a focus for a day other than today in their timezone
I would like to work on this under GSSoC 26. please assign!
for labels i think gssoc + level1 + type:bug fits here.
Description
while looking at the daily-focus route i noticed the POST accepts
goal_textanddatefrom the request body without any length cap or format check, which is inconsistent with how goals and milestones handle input.The Problem
src/app/api/daily-focus/route.ts:49-56:
problems:
goal_text. an authenticated user can send"a".repeat(5_000_000)and it goes straight to supabase upsert on line 60. compare with src/app/api/goals/route.ts:213-215 (MAX_TITLE_LEN = 100) and src/app/api/milestones/route.ts:63 (stripHtml(title).slice(0, MAX_TITLE_LEN)) that both cap input.<script>payloads land in the DB and get rendered later.datefrom body has zero format validation. sendingdate: "not-a-date"throws a supabase constraint error (500 to the user instead of a proper 400). sendingdate: "9999-12-31"lets the user pin a "focus" onto any future or past day of their choice, which breaks the "daily" semantics of the feature.the GET/DELETE also read
datefromsearchParamswithout validation (src/app/api/daily-focus/route.ts:16-22, 87-92) so the same fix should cover those.Proposed Fix
MAX_GOAL_LEN = 280(match daily-note which does cap at 280 chars in src/app/api/daily-note/route.ts:81) and slice/reject longer inputstripHtmlongoal_textbefore saving, same as goals routedate(and the query-param variants in GET/DELETE) against/^\d{4}-\d{2}-\d{2}$/and reject with 400 if badtargetDateso users cannot set a focus for a day other than today in their timezoneI would like to work on this under GSSoC 26. please assign!
for labels i think gssoc + level1 + type:bug fits here.