feat: add sensitive data redaction to export (Phase C)

Server-side regex redaction masks IPs, emails, bearer/API tokens, and
UNC paths in exported session content. Redaction runs post-generation
and post-variable-resolution with fail-closed error handling. Frontend
gets a "Mask Sensitive Data" toggle in the export preview modal with
a summary of what was redacted. 24 unit tests passing, frontend build clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-02-14 00:11:20 -05:00
parent 1172c5394f
commit 303570ca2c
9 changed files with 427 additions and 45 deletions

View File

@@ -1,5 +1,5 @@
import apiClient from './client'
import type { Session, SessionCreate, SessionUpdate, SessionExport, SaveAsTreeRequest, SaveAsTreeResponse, SessionComplete } from '@/types'
import type { Session, SessionCreate, SessionUpdate, SessionExport, SaveAsTreeRequest, SaveAsTreeResponse, SessionComplete, RedactionSummary } from '@/types'
export interface SessionListParams {
page?: number
@@ -54,6 +54,28 @@ export const sessionsApi = {
return response.data
},
async exportWithMeta(
id: string,
options: SessionExport
): Promise<{
content: string
redactionMode: 'none' | 'mask'
redactionSummary: RedactionSummary | null
}> {
const response = await apiClient.post<string>(`/sessions/${id}/export`, options)
const redactionMode = (response.headers['x-redaction-mode'] as 'none' | 'mask') || 'none'
let redactionSummary: RedactionSummary | null = null
const summaryHeader = response.headers['x-redaction-summary']
if (summaryHeader) {
try {
redactionSummary = JSON.parse(summaryHeader)
} catch {
// Ignore malformed header
}
}
return { content: response.data, redactionMode, redactionSummary }
},
async updateScratchpad(id: string, content: string): Promise<Session> {
const response = await apiClient.patch<Session>(`/sessions/${id}/scratchpad`, { scratchpad: content })
return response.data