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

@@ -314,12 +314,33 @@ async def export_session(
from app.services.variable_service import resolve_variables
content = resolve_variables(content, session_vars)
# Phase C: Apply redaction AFTER generation and variable resolution
redaction_summary = None
if export_options.redaction_mode == "mask":
from app.services.redaction_service import apply_redaction_to_text, format_redaction_footer
try:
content, redaction_summary = apply_redaction_to_text(content)
footer = format_redaction_footer(redaction_summary)
if footer:
content += footer
except Exception:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Redaction processing failed"
)
# Only mark as exported if session is completed
if session.completed_at:
session.exported = True
await db.commit()
return PlainTextResponse(content=content, media_type=media_type)
# Build response with redaction headers
import json
headers = {"X-Redaction-Mode": export_options.redaction_mode}
if redaction_summary is not None:
headers["X-Redaction-Summary"] = json.dumps(redaction_summary.to_dict())
return PlainTextResponse(content=content, media_type=media_type, headers=headers)
# --- Save Session as Tree ---