diff --git a/apps/app/src/app/(app)/[orgId]/security/penetration-tests/_components/FindingContextSection.test.tsx b/apps/app/src/app/(app)/[orgId]/security/penetration-tests/_components/FindingContextSection.test.tsx index e6792dc26..9e72c6eef 100644 --- a/apps/app/src/app/(app)/[orgId]/security/penetration-tests/_components/FindingContextSection.test.tsx +++ b/apps/app/src/app/(app)/[orgId]/security/penetration-tests/_components/FindingContextSection.test.tsx @@ -125,6 +125,39 @@ describe('FindingContextSection', () => { }); }); + it('saves against the page run id, not the provider per-issue runId', async () => { + // Maced's Issue.runId is a per-issue run id that has no ownership-marker + // row, so sending it makes the API's upsert → getReport → assertRunOwnership + // 404 (the bare "HTTP 404:" toast). The PUT must carry the pentest run id + // the page is built on. + permissionsMock.canUpdatePentest = true; + contextsMock.saveContext.mockResolvedValue(undefined); + const user = userEvent.setup(); + + render( + , + ); + + await user.type( + screen.getByRole('textbox'), + 'Accepted by design — reads are non-sensitive.', + ); + await user.click(screen.getByRole('button', { name: /save context/i })); + + await waitFor(() => { + expect(contextsMock.saveContext).toHaveBeenCalledWith({ + issueId: 'issue_1', + runId: 'run_page', + context: 'Accepted by design — reads are non-sensitive.', + }); + }); + }); + it('lets pentest:update users remove a saved note', async () => { permissionsMock.canUpdatePentest = true; contextsMock.contextByIssueId = new Map([['issue_1', savedNote]]); diff --git a/apps/app/src/app/(app)/[orgId]/security/penetration-tests/_components/FindingContextSection.tsx b/apps/app/src/app/(app)/[orgId]/security/penetration-tests/_components/FindingContextSection.tsx index 225657776..b7d206de7 100644 --- a/apps/app/src/app/(app)/[orgId]/security/penetration-tests/_components/FindingContextSection.tsx +++ b/apps/app/src/app/(app)/[orgId]/security/penetration-tests/_components/FindingContextSection.tsx @@ -44,7 +44,13 @@ export function FindingContextSection({ const { contextByIssueId, isSaving, saveContext, removeContext } = usePentestFindingContexts(orgId, targetUrl); const existing = contextByIssueId.get(issue.id); - const resolvedRunId = issue.runId ?? runId ?? null; + // Save against the pentest run id the page is built on (run.id), NOT + // issue.runId. Maced's Issue.runId is a per-issue run id with no + // ownership-marker row, so the API's upsert → getReport → + // assertRunOwnership would 404 (surfaced as a bare "HTTP 404:" toast). + // The page run id is the one we fetched the report/issues with and that + // the securityPenetrationTestRun marker is keyed on. + const resolvedRunId = runId ?? null; const form = useForm({ resolver: zodResolver(findingContextSchema),