docs(21-01): complete triage-note sanitizer/formatter plan

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6RuWdiUiXrPK6FLBHjtpY
This commit is contained in:
lorentz 2026-07-16 12:07:24 -04:00
parent 17ba0e880f
commit 03bb560874

View file

@ -0,0 +1,105 @@
---
phase: 21-autotask-triage-note
plan: 01
subsystem: api
tags: [phishing-triage, sanitization, autotask, vitest, pure-functions]
# Dependency graph
requires:
- phase: 17-mimecast-blast-radius-lookup
provides: BlastRadiusResult discriminated union imported as a type
- phase: 19-classification-engine
provides: Verdict type and classification shape (verdict/confidence/summary/reasons/recommended_actions) this formatter summarizes
- phase: 20-remediation-approval-audit-safety
provides: remediation_actions status lifecycle (proposed/approved/completed) this formatter renders
provides:
- "sanitizeUrl / sanitizeNoteText pure functions (lib/services/triage-note-sanitize.ts)"
- "formatTriageNote(evidence) pure function + TriageNoteEvidence contract (lib/services/triage-note-format.ts)"
affects: [21-02-triage-note-service-and-endpoint]
# Tech tracking
tech-stack:
added: []
patterns:
- "SECURITY-CRITICAL module-header convention mirrored from lib/services/analyzer/itglue-redact.ts (why-it-exists doc-comment + exported-pure-function-for-tests shape)"
- "Formatter routes all free text and URLs through a shared sanitizer before returning, so no source field can bypass redaction"
key-files:
created:
- lib/services/triage-note-sanitize.ts
- lib/services/triage-note-sanitize.test.ts
- lib/services/triage-note-format.ts
- lib/services/triage-note-format.test.ts
modified: []
key-decisions:
- "sanitizeUrl always strips query+fragment wholesale (never selectively keeps benign params) per NOTE-01's 'full malicious URL query strings' requirement"
- "sanitizeNoteText intentionally does NOT redact bare emails or hex attachment hashes — those are evidence per 21-CONTEXT.md Claude's Discretion, not credentials"
- "formatTriageNote passes its entire assembled string through sanitizeNoteText (not just discrete fields) so a secret embedded anywhere in free text is caught regardless of source field"
patterns-established:
- "Pure-function-first design for security-critical text transforms: no I/O, fully unit-testable, consumed later by a thin service layer (Plan 02)"
requirements-completed: [NOTE-01]
# Metrics
duration: 2min
completed: 2026-07-16
---
# Phase 21 Plan 01: Triage Note Sanitizer + Formatter Summary
**Pure sanitizer (URL query/fragment stripping + Bearer/credential redaction) and pure formatter (`formatTriageNote`) turning classification + blast-radius + remediation-state evidence into human-readable, secret-free Autotask note text, both fully unit-tested with Vitest.**
## Performance
- **Duration:** ~2 min
- **Started:** 2026-07-16T12:04:58-04:00
- **Completed:** 2026-07-16T12:06:31-04:00
- **Tasks:** 2 completed
- **Files modified:** 4 (all newly created)
## Accomplishments
- `sanitizeUrl` strips every URL down to scheme+host+path (query + fragment always removed), never throws on malformed input, falls back to truncating at `?`/`#`
- `sanitizeNoteText` redacts Bearer/Authorization tokens and credential-style query-param values (`token`, `access_token`, `password`, `api_key`, etc.) while explicitly preserving bare sender emails and attachment hashes as evidence
- `formatTriageNote` renders a `TriageNoteEvidence` object into labeled prose sections (header, Classification, Reasons, Blast Radius — both `ok`/`unavailable` branches, Recommended Actions, Current Remediation State, Indicator URLs), routing every URL through `sanitizeUrl` and the whole assembled string through `sanitizeNoteText`
- `TriageNoteEvidence` interface exported for Plan 02 to build its evidence-gathering service against
## Task Commits
Each task followed RED → GREEN (TDD):
1. **Task 1: Sanitizer** - `cc93707` (test: RED, 8 failing tests) → `226f300` (feat: GREEN, all 8 pass)
2. **Task 2: Note formatter + TriageNoteEvidence contract** - `b4e05eb` (test: RED, 9 failing tests) → `17ba0e8` (feat: GREEN, all 9 pass)
_TDD gate compliance: both tasks have a `test(...)` commit followed by a `feat(...)` commit; no refactor step was needed._
## Files Created/Modified
- `lib/services/triage-note-sanitize.ts` - `sanitizeUrl` + `sanitizeNoteText` pure functions with `REDACTED_MARKER` constant
- `lib/services/triage-note-sanitize.test.ts` - 8 Vitest cases covering query/fragment stripping, malformed-input safety, token/credential redaction, email/hash preservation
- `lib/services/triage-note-format.ts` - `TriageNoteEvidence` interface + `formatTriageNote(evidence)` pure function
- `lib/services/triage-note-format.test.ts` - 9 Vitest cases covering verdict/confidence rendering, both blast-radius branches, empty/populated remediation state, URL sanitization, whole-output secret redaction, null-verdict handling
## Decisions Made
- Query strings are stripped unconditionally in `sanitizeUrl` — no allowlist of "safe" params, since selectively keeping some params risks missing a novel credential-param name (matches NOTE-01's wording exactly: "full malicious URL query strings").
- `sanitizeNoteText` explicitly does not touch email addresses or hex hashes — confirmed as intentional in `21-CONTEXT.md` Claude's Discretion block (these are attacker-identity evidence, not secrets belonging to Pulse/its operators).
- `formatTriageNote` sanitizes the entire assembled output string (not just individual fields) as a defense-in-depth measure — if a future evidence field carries an embedded secret, it's still caught at the final sanitize pass regardless of which section it landed in.
## Deviations from Plan
None - plan executed exactly as written. Both tasks' `<behavior>`, `<action>`, and `<acceptance_criteria>` blocks were implemented as specified; no architectural changes, no missing critical functionality found, no blocking issues encountered.
## Issues Encountered
None.
## User Setup Required
None - no external service configuration required. Both modules are pure functions with no DB/network/Autotask dependency.
## Next Phase Readiness
- `TriageNoteEvidence` interface and `formatTriageNote()` are ready for Plan 02 to import and build its evidence-gathering service + `POST /api/phishing/campaigns/{id}/triage-note` endpoint against.
- `sanitizeUrl`/`sanitizeNoteText` are exported and stable for direct reuse if Plan 02 needs to sanitize any additional free text outside the formatter's own URL list.
- No blockers.
---
*Phase: 21-autotask-triage-note*
*Completed: 2026-07-16*