tftsr-devops_investigation/TICKET-pii-bypass-chat-attachments.md
Shaun Arman cd26801a39
Some checks failed
Test / rust-fmt-check (pull_request) Failing after 1m31s
Test / frontend-tests (pull_request) Successful in 1m34s
Test / frontend-typecheck (pull_request) Successful in 1m36s
Test / rust-clippy (pull_request) Successful in 3m5s
PR Review Automation / review (pull_request) Successful in 4m31s
Test / rust-tests (pull_request) Successful in 4m27s
fix(security): block PII in chat attachments and typed messages
File attachments were embedded into AI messages without any PII
scanning, allowing credentials, tokens, and other sensitive data
to be forwarded to AI providers in plaintext.

Typed chat messages had the same gap: a user could type a password
or API key directly and it would be sent unscanned.

Changes:
- chat_message (Rust): defence-in-depth scan of all attachment body
  content (between --- Attached: markers); hard rejects if PII found
- detect_pii (Rust): fix return type from pii::PiiDetectionResult
  (spans/original_text) to db::models::PiiDetectionResult
  (detections/total_pii_found) to match the TypeScript contract; the
  LogUpload PII review workflow was receiving undefined for detections
- scan_text_for_pii (Rust): new command — scans arbitrary text for PII
  without creating DB records; used for typed message warnings
- Triage/index.tsx: PendingFile now carries logFileId; handleSend gates
  each text attachment through detectPiiCmd (hard block on PII found);
  typed message text scanned via scanTextForPiiCmd with a one-time
  warning — second send of same message proceeds as acknowledgment
2026-05-31 19:05:51 -05:00

4.4 KiB

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.

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

  • Attaching a text file containing PII blocks send with a descriptive error naming the file and PII types
  • The user is directed to use Log Analysis to redact before attaching
  • Attaching a clean text file proceeds normally
  • Attaching an image (binary, content === null) skips PII scan and proceeds
  • Typing a message containing PII triggers a one-time warning; sending the same message a second time proceeds (explicit acknowledgment)
  • After a successful send the warning state is cleared
  • Backend chat_message independently rejects attachment content containing PII, regardless of frontend state
  • Backend attachment parser catches headers both with and without trailing ---
  • detectPiiCmd returns detections: PiiSpan[] and total_pii_found: number matching the TypeScript contract
  • 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