From abb6a0ae77500326f82bbe8fc62401405ca8edc5 Mon Sep 17 00:00:00 2001 From: kbkb628 <278338969+kbkb628@users.noreply.github.com> Date: Tue, 8 Sep 2026 13:07:16 +0800 Subject: [PATCH] fix(longbridge-tools): validate calendar event args before exec getCalendarEvents was the only tool that passed user-supplied symbols straight into the LongBridge CLI argv without the repo-wide symbol check (CLAUDE.md: validate with /^[A-Z0-9]{1,5}\.(US|HK|SG|SH|SZ|HAS)$/ before exec). It also forwarded unvalidated start/end/count strings, so values like "--symbol" could be misread as CLI flags. - validate every symbol with validateSymbolOrThrow (INVALID_SYMBOL) - require YYYY-MM-DD for start/end (new INVALID_ARGUMENT code) - require a positive integer for count - add regression tests for all three rejections --- packages/longbridge-tools/src/errors.ts | 1 + .../longbridge-tools/src/phase-two.test.ts | 27 +++++++++++++++++++ .../longbridge-tools/src/tools/calendar.ts | 22 +++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/packages/longbridge-tools/src/errors.ts b/packages/longbridge-tools/src/errors.ts index 1e8d83b..d2f7d59 100644 --- a/packages/longbridge-tools/src/errors.ts +++ b/packages/longbridge-tools/src/errors.ts @@ -4,6 +4,7 @@ export type ErrorCode = | 'LONGBRIDGE_RATE_LIMITED' | 'LONGBRIDGE_TIMEOUT' | 'INVALID_SYMBOL' + | 'INVALID_ARGUMENT' | 'LONGBRIDGE_PARSE_FAILURE' | 'LONGBRIDGE_UNKNOWN'; diff --git a/packages/longbridge-tools/src/phase-two.test.ts b/packages/longbridge-tools/src/phase-two.test.ts index a9019d0..825683f 100644 --- a/packages/longbridge-tools/src/phase-two.test.ts +++ b/packages/longbridge-tools/src/phase-two.test.ts @@ -294,4 +294,31 @@ describe('phase-2 error handling', () => { /parse depth response/ ); }); + + it('rejects invalid calendar symbols before invoking the CLI', async () => { + await expect( + getCalendarEvents({ eventType: 'dividend', symbols: ['NVDA.US', 'not-a-symbol'] }) + ).rejects.toMatchObject({ code: 'INVALID_SYMBOL' }); + expect(execaMock).not.toHaveBeenCalled(); + }); + + it('rejects calendar dates that are not YYYY-MM-DD', async () => { + await expect( + getCalendarEvents({ eventType: 'dividend', start: '--symbol' }) + ).rejects.toMatchObject({ code: 'INVALID_ARGUMENT' }); + await expect( + getCalendarEvents({ eventType: 'dividend', end: '2026/01/01' }) + ).rejects.toMatchObject({ code: 'INVALID_ARGUMENT' }); + expect(execaMock).not.toHaveBeenCalled(); + }); + + it('rejects a non-positive or non-integer calendar count', async () => { + await expect( + getCalendarEvents({ eventType: 'dividend', count: -1 }) + ).rejects.toMatchObject({ code: 'INVALID_ARGUMENT' }); + await expect( + getCalendarEvents({ eventType: 'dividend', count: 1.5 }) + ).rejects.toMatchObject({ code: 'INVALID_ARGUMENT' }); + expect(execaMock).not.toHaveBeenCalled(); + }); }); diff --git a/packages/longbridge-tools/src/tools/calendar.ts b/packages/longbridge-tools/src/tools/calendar.ts index eaf6176..71f38d6 100644 --- a/packages/longbridge-tools/src/tools/calendar.ts +++ b/packages/longbridge-tools/src/tools/calendar.ts @@ -1,5 +1,7 @@ import { executeLongBridge } from '../executor.ts'; import { parseCalendarResponse } from '../parser.ts'; +import { LongBridgeError } from '../errors.ts'; +import { validateSymbolOrThrow } from '../validator.ts'; import type { CalendarEvent } from '../types.ts'; export type CalendarEventType = @@ -23,10 +25,30 @@ export interface GetCalendarEventsOptions { count?: number; } +const DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/; + /** Upcoming finance-calendar events of a given type. */ export async function getCalendarEvents( options: GetCalendarEventsOptions ): Promise { + for (const symbol of options.symbols ?? []) { + validateSymbolOrThrow(symbol); + } + for (const [name, value] of [['start', options.start], ['end', options.end]] as const) { + if (value !== undefined && !DATE_REGEX.test(value)) { + throw new LongBridgeError( + `INVALID_ARGUMENT: ${name} must be YYYY-MM-DD, got "${value}"`, + 'INVALID_ARGUMENT' + ); + } + } + if (options.count !== undefined && (!Number.isInteger(options.count) || options.count < 1)) { + throw new LongBridgeError( + `INVALID_ARGUMENT: count must be a positive integer, got ${options.count}`, + 'INVALID_ARGUMENT' + ); + } + const args = ['finance-calendar', options.eventType]; for (const symbol of options.symbols ?? []) { args.push('--symbol', symbol);