Files
resolutionflow/docs/archive/2026-01-13-report-phase-c-pt1.md
chihlasm 350c977eda feat: add procedural flows with intake forms, navigation, and seed templates
Adds a new "procedural" tree type for linear step-by-step project workflows
(domain controller setup, M365 onboarding, VPN config, etc). Includes intake
form builder, two-panel step navigation, variable resolution, procedural
exports, 3 seed templates, and UI rename from "Trees" to "Flows".

Also archives 19 implemented plan docs and creates deferred features backlog.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 04:13:52 -05:00

147 lines
4.7 KiB
Markdown

# Phase C: Sensitive Data Redaction — Design Document
> **Status:** Approved — ready for implementation planning
> **Spec:** `docs/plans/2026-02-13-EXPORT-IMPROVEMENTS-SPEC.md` section C1
> **UI Decision:** Simple toggle (Option 1)
> **Branch:** `feat/export-phase-c`
## Overview
Server-side regex redaction with a simple checkbox toggle in the export preview modal. No rich editor — keeps the existing textarea. User sees a summary of what was masked and can manually edit the result.
---
## Backend
### New File: `backend/app/services/redaction_service.py`
**`apply_redaction(session) -> tuple[Session, RedactionSummary]`**
- Deep-copies the session (original ORM object never mutated)
- Walks `decisions` list and `custom_steps`, applies regex replacements to all string fields: `answer`, `notes`, `command_output`, `content`, `action_performed`
- Also redacts top-level session fields: `scratchpad`, `outcome_notes`, `next_steps`
- Returns the sanitized copy and a summary of what was found
**`RedactionSummary` dataclass:**
```python
@dataclass
class RedactionSummary:
ips: int = 0
emails: int = 0
tokens: int = 0
unc_paths: int = 0
```
### Regex Patterns (conservative — false positives > false negatives)
| Pattern | Regex | Replacement |
|---------|-------|-------------|
| IPv4 | `\b(?:\d{1,3}\.){3}\d{1,3}\b` | `[IP REDACTED]` |
| IPv6 | `\b(?:[0-9a-fA-F]{1,4}:){2,7}[0-9a-fA-F]{1,4}\b` | `[IP REDACTED]` |
| Email | `\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z\|a-z]{2,}\b` | `[EMAIL REDACTED]` |
| Bearer tokens | `Bearer\s+[A-Za-z0-9._-]+` | `[TOKEN REDACTED]` |
| API key patterns | Long hex/base64 strings (32+ chars) | `[TOKEN REDACTED]` |
| UNC paths | `\\\\[\w.-]+\\[\w$.-]+` | `[UNC PATH REDACTED]` |
Hostname redaction is **not** included — MSP tickets legitimately reference hostnames.
### Schema Change: `backend/app/schemas/session.py`
Add to `SessionExport`:
```python
redaction_mode: Literal["none", "mask"] = "none"
```
### Integration Point: `backend/app/api/endpoints/sessions.py`
Insert at ~line 297 (after session fetch, before format branching):
```python
redaction_summary = None
if export_options.redaction_mode == "mask":
session, redaction_summary = apply_redaction(session)
```
Export generators receive `redaction_summary` and append a footer when present:
```
--- Redacted: 3 IPs, 2 emails, 1 token ---
```
### Response
The redaction summary is returned via an `X-Redaction-Summary` response header (JSON-encoded) to avoid changing the existing content-based response body.
### No Migration Needed
All changes are runtime — no database schema changes.
---
## Frontend
### `ExportPreviewModal.tsx`
New props:
- `redactionEnabled?: boolean`
- `onToggleRedaction?: (enabled: boolean) => void`
- `redactionSummary?: { ips: number; emails: number; tokens: number; unc_paths: number } | null`
Add a "Mask Sensitive Data" checkbox next to the existing "Include Summary" checkbox, using the same visual pattern:
```tsx
<label className="flex items-center gap-2 text-sm text-white/60 cursor-pointer">
<input type="checkbox" checked={redactionEnabled} onChange={...} />
Mask Sensitive Data
</label>
```
When `redactionSummary` has matches, show an info line below the toggles in `text-blue-400`:
```
Masked: 3 IPs, 2 emails, 1 token
```
If redaction is on but nothing was found: `"No sensitive data detected"` in `text-white/40`.
### `SessionDetailPage.tsx`
- Add `redactionMode` state (`'none' | 'mask'`)
- Wire into export options object
- Pass toggle callback to `ExportPreviewModal`
- Same pattern as existing `includeSummary` state
### `types/session.ts`
Add to `SessionExport` type:
```typescript
redaction_mode?: 'none' | 'mask'
```
---
## Testing
### Backend: `backend/tests/test_psa_export.py` — `TestPhaseC` class
- Test redaction of each pattern type individually (IP, email, bearer token, API key, UNC path)
- Test `redaction_mode="none"` leaves content untouched
- Test original session object is not mutated (deep copy verification)
- Test redaction summary counts are accurate
- Test redaction across all text fields (`notes`, `command_output`, `answer`, `scratchpad`, `outcome_notes`, `next_steps`)
- Test edge cases: empty strings, no matches, overlapping patterns
### Frontend
`npm run build` validates types. No new component tests needed for a checkbox toggle.
---
## Files to Create/Modify
| Action | File |
|--------|------|
| Create | `backend/app/services/redaction_service.py` |
| Modify | `backend/app/schemas/session.py` |
| Modify | `backend/app/api/endpoints/sessions.py` |
| Modify | `frontend/src/types/session.ts` |
| Modify | `frontend/src/components/session/ExportPreviewModal.tsx` |
| Modify | `frontend/src/pages/SessionDetailPage.tsx` |
| Extend | `backend/tests/test_psa_export.py` |