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:
@@ -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 ---
|
||||
|
||||
Reference in New Issue
Block a user