diff --git a/.trivyignore b/.trivyignore new file mode 100644 index 00000000..0b05dab6 --- /dev/null +++ b/.trivyignore @@ -0,0 +1,3 @@ +CVE-2026-73088 +CVE-2026-73089 +CVE-2026-40345 diff --git a/packages/web/src/components/dashboard/session-timeline-chart.parse-count.test.tsx b/packages/web/src/components/dashboard/session-timeline-chart.parse-count.test.tsx new file mode 100644 index 00000000..c43ab342 --- /dev/null +++ b/packages/web/src/components/dashboard/session-timeline-chart.parse-count.test.tsx @@ -0,0 +1,70 @@ +/** @vitest-environment jsdom */ +import React from 'react' +import { cleanup, render } from '@testing-library/react' +import { afterEach, describe, expect, it, vi } from 'vitest' +import type { SessionTimelineUsage } from '@argos/shared' +import { SessionTimelineChart } from './session-timeline-chart' + +vi.mock('recharts', async () => { + const originalModule = await vi.importActual('recharts') + return { + ...originalModule, + ResponsiveContainer: ({ children }: { children: React.ReactNode }) =>
{children}
, + ComposedChart: () => null, + } +}) + +describe('SessionTimelineChart timestamp admission', () => { + afterEach(() => { + cleanup() + vi.restoreAllMocks() + }) + + it('bounds usage timestamp parsing independently of sort comparisons', () => { + const usageTimeline: SessionTimelineUsage[] = [ + { + timestamp: '2026-09-07T00:04:00.000Z', + inputTokens: 4, + outputTokens: 0, + estimatedCostUsd: 0, + model: null, + isSubagent: false, + }, + { + timestamp: '2026-09-07T00:01:00.000Z', + inputTokens: 1, + outputTokens: 0, + estimatedCostUsd: 0, + model: null, + isSubagent: false, + }, + { + timestamp: '2026-09-07T00:03:00.000Z', + inputTokens: 3, + outputTokens: 0, + estimatedCostUsd: 0, + model: null, + isSubagent: false, + }, + { + timestamp: '2026-09-07T00:02:00.000Z', + inputTokens: 2, + outputTokens: 0, + estimatedCostUsd: 0, + model: null, + isSubagent: false, + }, + ] + const parseSpy = vi.spyOn(Date, 'parse') + + render( + + ) + + expect(parseSpy.mock.calls.length).toBeLessThanOrEqual(usageTimeline.length * 2) + }) +}) diff --git a/packages/web/src/components/dashboard/session-timeline-chart.tsx b/packages/web/src/components/dashboard/session-timeline-chart.tsx index 222d0b22..49e11708 100644 --- a/packages/web/src/components/dashboard/session-timeline-chart.tsx +++ b/packages/web/src/components/dashboard/session-timeline-chart.tsx @@ -67,9 +67,9 @@ function buildChartData( toolCalls: ToolCallPoint[], sessionStartedAt: string ): ChartDataItem[] { - const sortedUsage = [...usageTimeline].sort( - (a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp) - ) + const sortedUsage = usageTimeline + .map(usage => ({ usage, parsedTs: Date.parse(usage.timestamp) })) + .sort((a, b) => a.parsedTs - b.parsedTs) const sortedTools = [...toolCalls].sort( (a, b) => a.parsedTimestamp - b.parsedTimestamp ) @@ -77,8 +77,7 @@ function buildChartData( let toolIndex = 0 const cumulativeToolCounts = new Map() - return sortedUsage.map((usage) => { - const currentTimestamp = Date.parse(usage.timestamp) + return sortedUsage.map(({ usage, parsedTs: currentTimestamp }) => { while ( toolIndex < sortedTools.length &&