From e0faab5021a7ba518e16cfbdefd74d61c2e4ea70 Mon Sep 17 00:00:00 2001 From: Sai Asish Y Date: Tue, 21 Apr 2026 17:25:51 +0000 Subject: [PATCH] fix(parser): return error instead of panicking on TZ= prefix with no schedule Parser.Parse supports an optional 'TZ=...' or 'CRON_TZ=...' prefix terminated by the first space character, with the schedule expression following. If the prefix is provided alone (e.g. 'TZ=0' with no space) the space-index lookup returned -1, and spec[eq+1:-1] sliced a negative upper bound, panicking with 'slice bounds out of range [:-1]' on fuzz-style input (#554). Detect the missing space explicitly and return a normal parse error ('missing schedule after timezone prefix: %q') so callers that treat their input as untrusted (cron expressions from users, config files, automated fuzzers) see a regular error return instead of a runtime panic crashing the process. Added regression coverage in TestParseScheduleErrors for three representative inputs: 'TZ=0', 'TZ=UTC', and 'CRON_TZ=America/Los_Angeles'. Closes #554 Signed-off-by: SAY-5 --- parser.go | 11 +++++++++-- parser_test.go | 5 +++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/parser.go b/parser.go index 8da6547a..ce426aed 100644 --- a/parser.go +++ b/parser.go @@ -61,11 +61,11 @@ type Parser struct { // sched, err := specParser.Parse("0 0 15 */3 *") // // // Same as above, just excludes time fields -// specParser := NewParser(Dom | Month | Dow) +// subsParser := NewParser(Dom | Month | Dow) // sched, err := specParser.Parse("15 */3 *") // // // Same as above, just makes Dow optional -// specParser := NewParser(Dom | Month | DowOptional) +// subsParser := NewParser(Dom | Month | DowOptional) // sched, err := specParser.Parse("15 */3") // func NewParser(options ParseOption) Parser { @@ -96,6 +96,13 @@ func (p Parser) Parse(spec string) (Schedule, error) { var err error i := strings.Index(spec, " ") eq := strings.Index(spec, "=") + // A timezone prefix without a following space (e.g. "TZ=0" on + // its own) leaves i == -1, which used to slice spec[eq+1:-1] and + // panic with `slice bounds out of range [:-1]` on fuzz-style + // input (#554). Surface it as a normal parse error instead. + if i == -1 { + return nil, fmt.Errorf("missing schedule after timezone prefix: %q", spec) + } if loc, err = time.LoadLocation(spec[eq+1 : i]); err != nil { return nil, fmt.Errorf("provided bad location %s: %v", spec[eq+1:i], err) } diff --git a/parser_test.go b/parser_test.go index 41c8c520..b425d919 100644 --- a/parser_test.go +++ b/parser_test.go @@ -125,6 +125,11 @@ func TestParseScheduleErrors(t *testing.T) { {"@unrecognized", "unrecognized descriptor"}, {"* * * *", "expected 5 to 6 fields"}, {"", "empty spec string"}, + // Regression for #554 - timezone prefix with no schedule suffix + // used to panic with `slice bounds out of range [:-1]`. + {"TZ=0", "missing schedule after timezone prefix"}, + {"TZ=UTC", "missing schedule after timezone prefix"}, + {"CRON_TZ=America/Los_Angeles", "missing schedule after timezone prefix"}, } for _, c := range tests { actual, err := secondParser.Parse(c.expr)