79 lines
4.4 KiB
Markdown
79 lines
4.4 KiB
Markdown
|
|
# 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
|