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>
147 lines
4.7 KiB
Markdown
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` |
|