tftsr-devops_investigation/TICKET-pii-bypass-chat-attachments.md

79 lines
4.4 KiB
Markdown
Raw Normal View History

# TICKET: PII Detection Bypass in AI Chat
**Branch**: `feature/attachment-db-storage-recall`
---
## Description
Two PII detection bypasses were identified and fixed in the AI triage chat interface.
### Bypass 1 — File Attachments (Critical)
When a user attached a file to a chat message, its content was read via `readTextFile()`, sliced to 8 KB, and embedded directly into the AI message string — bypassing the existing PII pipeline entirely. The message was forwarded to the configured AI provider in plaintext. The audit log recorded the full file content without a SHA-256 hash or redaction marker.
**Root cause**: `handleAttach` stored raw file content in React state. `handleSend` concatenated it into `aiMessage` with no call to `detectPiiCmd` or `applyRedactionsCmd`. The backend `chat_message` command applied no validation.
### Bypass 2 — Typed Chat Messages (High)
Plain typed chat messages were sent to the AI provider without any PII scan. A user typing `How secure is my password: abc123!!` would have the password forwarded to the AI and persisted in the audit log in plaintext.
### Related Fix — Wrong Return Type on `detect_pii`
`detect_pii` was serialising `pii::PiiDetectionResult` (`spans`, `original_text`) while the TypeScript interface expected `db::models::PiiDetectionResult` (`detections`, `total_pii_found`). All frontend code reading `result.detections` received `undefined`, meaning the LogUpload PII review workflow was silently broken. Fixed as part of this work.
---
## Acceptance Criteria
- [x] Attaching a text file containing PII blocks send with a descriptive error naming the file and PII types
- [x] The user is directed to use Log Analysis to redact before attaching
- [x] Attaching a clean text file proceeds normally
- [x] Attaching an image (binary, `content === null`) skips PII scan and proceeds
- [x] Typing a message containing PII triggers a one-time warning; sending the same message a second time proceeds (explicit acknowledgment)
- [x] After a successful send the warning state is cleared
- [x] Backend `chat_message` independently rejects attachment content containing PII, regardless of frontend state
- [x] Backend attachment parser catches headers both with and without trailing `---`
- [x] `detectPiiCmd` returns `detections: PiiSpan[]` and `total_pii_found: number` matching the TypeScript contract
- [x] All Rust and frontend tests pass; zero clippy warnings; tsc clean
---
## Work Implemented
### `src-tauri/src/commands/analysis.rs`
- Fixed `detect_pii` to return `db::models::PiiDetectionResult` (`detections`, `total_pii_found`) instead of `pii::PiiDetectionResult` (`spans`, `original_text`)
- Added `scan_text_for_pii(text: String)` command: scans arbitrary text for PII without creating DB records
### `src-tauri/src/commands/ai.rs`
- Added defence-in-depth PII scan inside `chat_message` before user message is appended to the AI request
- Extracts body content from all `--- Attached:` blocks; catches headers with and without trailing `---`
- Returns a hard error if PII spans are found in attachment content
### `src-tauri/src/lib.rs`
- Registered `scan_text_for_pii` in `generate_handler![]`
### `src/lib/tauriCommands.ts`
- Added `scanTextForPiiCmd(text: string)` wrapper
### `src/pages/Triage/index.tsx`
- Updated `PendingFile` type: added `logFileId: string`
- Stored `logFile.id` when attaching files
- Added attachment PII gate in `handleSend`: calls `detectPiiCmd` on each text attachment; hard-blocks if PII found
- Added message PII warning in `handleSend`: calls `scanTextForPiiCmd` on typed message; warns once; second send with same message proceeds
- Added `piiWarnedMessageRef` to track prior warning state; cleared in `finally` after every send attempt
---
## Testing Needed
1. Attach a file containing `password: secret123` → send is blocked; error names the file and PII type
2. Attach a clean text file → send proceeds
3. Attach an image (.png) → no PII scan, send proceeds
4. Type `My password is abc123!!` in chat → first send shows PII warning
5. Send the same message again → proceeds (acknowledgment)
6. Send a different message → prior warning cleared, sends normally
7. On LogUpload page, upload a file with a known IP/email → confirm PII spans appear in the review UI (was previously broken due to wrong struct)
8. Directly call `chat_message` IPC with a message containing `--- Attached: test ---\npassword: secret` → backend returns error
9. `cargo test` → 228/228 pass; `npm run test:run` → 103/103 pass