fix(security): include csrf token in scan retry

Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
This commit is contained in:
XiaoSeS 2026-09-03 18:59:17 +08:00
parent fd932cc160
commit 1b7e679d45
3 changed files with 29 additions and 3 deletions

View file

@ -232,6 +232,7 @@ class ScanTaskConsumerLoggingTest {
ScannerType scannerType,
String reason) {
}
}
private static final class TestProducer implements ScanTaskProducer {

View file

@ -12,12 +12,22 @@ import { describe, expect, it, vi } from 'vitest'
// Capture the options passed to useQuery so we can assert on them.
let capturedOptions: Record<string, unknown> | undefined
let capturedMutationOptions: Record<string, unknown> | undefined
const apiMocks = vi.hoisted(() => ({
fetchJson: vi.fn(),
getCsrfHeaders: vi.fn(() => ({ 'X-XSRF-TOKEN': 'csrf-token' })),
}))
vi.mock('@tanstack/react-query', () => ({
useQuery: (options: Record<string, unknown>) => {
capturedOptions = options
return { data: undefined, isLoading: false }
},
useMutation: (options: Record<string, unknown>) => {
capturedMutationOptions = options
return { mutate: vi.fn(), isPending: false }
},
useQueryClient: () => ({ invalidateQueries: vi.fn() }),
}))
// Mock fetchJson to avoid actual network calls. The hook's queryFn
@ -30,11 +40,12 @@ vi.mock('@/api/client', () => ({
this.status = status
}
},
fetchJson: vi.fn(),
fetchJson: apiMocks.fetchJson,
getCsrfHeaders: apiMocks.getCsrfHeaders,
}))
// Dynamic import to ensure mocks are established first.
const { useSecurityAudits } = await import('./use-security-audit')
const { useRetrySecurityScan, useSecurityAudits } = await import('./use-security-audit')
describe('useSecurityAudits', () => {
it('uses the correct query key structure', () => {
@ -78,4 +89,17 @@ describe('useSecurityAudits', () => {
expect(capturedOptions?.retry).toBe(false)
})
it('sends the CSRF header when retrying a security scan', async () => {
apiMocks.fetchJson.mockResolvedValueOnce({ status: 'SCANNING' })
useRetrySecurityScan(42, 100)
await (capturedMutationOptions?.mutationFn as () => Promise<unknown>)()
expect(apiMocks.getCsrfHeaders).toHaveBeenCalledOnce()
expect(apiMocks.fetchJson).toHaveBeenCalledWith(
'/api/v1/skills/42/versions/100/security-audit/retry',
{ method: 'POST', headers: { 'X-XSRF-TOKEN': 'csrf-token' } },
)
})
})

View file

@ -1,5 +1,5 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { ApiError, fetchJson } from '@/api/client'
import { ApiError, fetchJson, getCsrfHeaders } from '@/api/client'
import type { SecurityAuditRecord } from './types'
async function fetchSecurityAudits(
@ -37,6 +37,7 @@ export function useRetrySecurityScan(skillId: number, versionId: number) {
return useMutation({
mutationFn: () => fetchJson(`/api/v1/skills/${skillId}/versions/${versionId}/security-audit/retry`, {
method: 'POST',
headers: getCsrfHeaders(),
}),
onSuccess: () => {
void queryClient.invalidateQueries({ queryKey: ['security-audits', skillId, versionId] })