fix(security): block PII in chat attachments and typed messages #59
No reviewers
Labels
No Label
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: sarman/tftsr-devops_investigation#59
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "fix/pii-detection-bypass"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Two PII detection bypasses fixed.
Bypass 1 — File attachments (critical)
Attached files were embedded into chat messages and forwarded to the AI provider in plaintext — no
detectPiiCmd, noapplyRedactionsCmd, no audit hash. Backend had no enforcement either.Bypass 2 — Typed chat messages (high)
Typing a password or credential in the chat sent it to the AI and stored it in the audit log in plaintext.
Related —
detect_piiwrong return typedetect_piiwas serialisingpii::PiiDetectionResult(spans,original_text) while all frontend code expecteddetectionsandtotal_pii_found. The LogUpload PII review flow was silently receivingundefinedfor all detections.Changes
commands/ai.rscommands/analysis.rsdetect_piireturn type; addscan_text_for_piicommandlib.rsscan_text_for_piitauriCommands.tsscanTextForPiiCmdwrapperTriage/index.tsxTest results
cargo clippy -- -D warnings: cleancargo test: 228/228 passnpx tsc --noEmit: cleannpm run test:run: 103/103 passAutomated PR Review (qwen3-coder-next via liteLLM):\n\nSummary
The PR introduces critical PII protections for chat attachments and typed messages in the AI triage flow. However, a logic flaw in the attachment PII detection gate fails to prevent sending messages when a PII scan throws an exception, and the
handleAttachlogic stores raw file content without PII redaction, risking plaintext PII exposure in the UI and storage.Findings
[BLOCKER] src/pages/Triage/index.tsx:254–267 – Attachment PII scan hard-blocks only on successful scan and non-zero detections; if
detectPiiCmd(f.logFileId)throws, the error is caught butisLoadingis set false and function returns early without clearingpendingFiles, allowing users to retry sending the same attachment. Worse,handleSendreturns after setError but leavespendingFilesintact, and thefinallyblock does not clear them. IfchatMessageCmditself fails later (e.g., network), the same PII-laden file may be resent on next attempt, bypassing the warning logic.Evidence:
Fix: Clear
pendingFilesbefore entering thetryblock or ensure it is cleared on every error path. Specifically, on PII scan failure, either (a) clear the problematic file frompendingFilesand retry or (b) block completely and clear all pending files.[BLOCKER] src/pages/Triage/index.tsx:238–244 –
handleAttachextracts raw file content viareadTextFileand slices at 8 KB, storing the unredacted plaintext incontentonPendingFile. This raw content is used directly in theaiMessagestring (line 281:aiMessage = pendingFiles.length > 0 ? \${message}${fileContext}`) and persisted to theaudit_logviachat_messagewithout redaction. The backend blocks PII in attachments at send time, but the frontend UI may still render unredacted content, and the in-memorycontent` field remains in scope, raising data residency and UI leak risks.Evidence:
Fix: Avoid storing raw content in
PendingFile. Instead, use onlylogFileIdand rely on backend to extract and scan content at send time (which the PR does). The frontend should never hold unredacted PII-containing content beyond the immediate upload.[WARNING] src-tauri/src/commands/ai.rs:277–293 – The
chat_messagebackend only scans attachment bodies if the input message contains the substring"--- Attached:". A malformed or mangled header (e.g., missing trailing colon or extra whitespace like"--- Attached ") is not caught. The regex-based header detection is overly fragile and relies on exact line starts, which could be bypassed with injection (e.g.,"---Attached:\n..."vs"--- Attached:\n...").Evidence:
Fix: Use more robust parsing, e.g., a regex like
r"^---\s*Attached[^:]*:\s*$"or strip leading/trailing whitespace and normalize before matching.[WARNING] src-tauri/src/commands/ai.rs:287–292 – The backend PII rejection message discloses the exact count and sorted list of PII types to the user. While useful, this could aid an attacker mapping known PII patterns if the chat interface lacks rate limiting. Not a direct exploit, but a secondary data leak risk.
Evidence:
Fix: Consider logging types only server-side and returning a generic error for unauthenticated or repeated failures.
Verdict: REQUEST CHANGES
Automated PR Review (qwen3-coder-next via liteLLM):\n\nSummary
This PR implements crucial PII protection for chat attachments and typed messages in the AI triage interface. However, the frontend implementation in
Triage/index.tsxintroduces a critical data loss issue by applying PII redaction directly to user input before sending to the backend, which causes original user content to be permanently lost and inconsistent with the audit log.Findings
[BLOCKER] src/pages/Triage/index.tsx:153–161 — User typed message is redacted before being sent to backend, causing permanent loss of original message content in session history and audit log
Evidence:
and
Fix: Backend must be the sole authority for redacting PII in user messages (i.e.,
chatMessageCmdshould receive originalmessageand redact internally before embedding in audit and DB). Frontend must preserve original user input in session history and only show redacted preview to user.[WARNING] src/pages/Triage/index.tsx:177–185 — Error recovery discards user input silently when PII scan fails
Evidence:
However, the
outMessageis already modified (or remains unmodified) but the originalmessageis never persisted if redaction succeeds and backend rejects due to other reasons. More critically, if backend later redacts its own copy and logs that, the frontend still shows the original, causing audit inconsistency.Fix: Do not redact on frontend; let backend handle redaction and error handling.
[BLOCKER] src/pages/Triage/index.tsx:180 —
outMessageis used inaddMessage,chatMessageCmd, andlastUserMsgRef, but redaction modifies it before persistenceEvidence:
Later,
addFiveWhyCmduseslastUserMsgRef.current— if it contains PII redaction tokens, resolution steps leak redacted PII.Fix: Redaction must happen server-side. Frontend should store and display original user input; redacted preview may be shown optionally.
Verdict: REQUEST CHANGES
Automated PR Review (qwen3-coder-next via liteLLM):\n\nSummary
This PR implements critical PII防护 (PII protection) in AI chat workflows by adding backend-enforced scanning and auto-redaction of both typed messages and file attachments, and fixes a prior bug where
detect_piireturned the wrong struct shape. However, a security bypass remains: the frontendhandleSenddoes not enforce the PII scan warning for attachments — it only performs the check inside the backendchat_messagecommand, meaning if a user rapidly resends after a warning (or if frontend validation is bypassed), PII could still be sent before the backend blocks it.Findings
[BLOCKER] src/pages/Triage/index.tsx:186-217 - Frontend PII warning for typed messages is correctly implemented, but attachment PII blocking relies only on backend validation (
chat_message), with no frontend pre-check. The ticket states “Attaching a file containing PII blocks send with a descriptive error”, yethandleSendnever callsdetectPiiCmdorscanTextForPiiCmdonpendingFiles. The only PII check for attachments happens afterchatMessageCmdis invoked, so the warning UI flow described in the acceptance criteria (e.g., “one-time warning; second send proceeds”) cannot occur for attachments.Evidence:
Per
chat_messageincommands/ai.rs, PII detection and auto-redaction happen on the backend (lines 157–189), but there is no corresponding frontendscanTextForPiiCmdordetectPiiCmdcall onlogFileIdsbefore sending.Fix: Before calling
chatMessageCmd, for eachlogFileIdinlogFileIds, calldetectPiiCmd(id); iftotal_pii_found > 0, present the user with a descriptive error (e.g., “PII detected in {file_name} — use Log Analysis to redact before attaching”) and block send. If user re-sends the same message, re-scan and block again on first attempt, and allow on second only if they explicitly acknowledge (track withpiiWarnedMessageRefas implemented for typed messages).[WARNING] src-tauri/src/commands/ai.rs:157–189 - Backend
chat_messageperforms auto-redaction on attachments after reading full file content (preview = &content[..content.len().min(8000)]) and before redacting, but redaction occurs in-memory only — PII may briefly exist in memory logs or crash dumps. Not strictly a runtime bug but violates defense-in-depth.Evidence:
Fix: Consider clearing sensitive content buffers immediately after use (not possible to enforce purely in Rust here, but could be mitigated by using zeroizing and avoiding intermediate
Stringcopies).[WARNING] src/lib/tauriCommands.ts:278–281 —
scanTextForPiiCmdreturnsPiiDetectionResultwithlog_file_id: ""for scanned text, butchatMessageCmdbackend stores the full redacted message (including redacted PII content). Frontend never receives the redacted message back from backend in a way that allows re-displaying it to the user before send. User sees raw message typed, may not realize PII was removed.Evidence:
but
chatMessageCmdis invoked with originalmessage, and the UI (handleSend) passes the originalmessageinto theTriageMessage.contentfield, not theresponse.user_message. As a result,messagesarray will show the PII-laden message even though only redacted was sent to the AI.Fix: On successful
chatMessageCmd, replace localTriageMessage.contentwithresponse.user_message(which is redacted) beforeaddMessage(assistantMsg).Verdict: REQUEST CHANGES
The primary issue is a clear PII bypass: attachment scanning is not enforced or warned on the frontend, contrary to acceptance criteria. This must be fixed before merge. The other two warnings are secondary but should be addressed to achieve full defense-in-depth and correct audit display.
Automated PR Review (qwen3-coder-next via liteLLM):\n\nSummary
This PR addresses two critical PII bypass vulnerabilities in chat attachments and typed messages by implementing PII detection and auto-redaction on both frontend and backend. While the fix is comprehensive, a logic flaw in the frontend blocks attachment PII before backend redaction, conflicting with the stated “backend is the sole authority” design.
Findings
[BLOCKER] src/pages/Triage/index.tsx:253-257 - Frontend blocks attachment sends only when PII is detected, ignoring the backend’s mandated auto-redaction behavior and causing inconsistent UX.
Evidence:
However,
handleSendproceeds without checkingresult.total_pii_found > 0— no blocking, only a notice.Fix: Remove notice-only flow and ensure backend blocks only when PII is in attachments and frontend has not handled it (but current logic expects backend to always auto-redact, not block). Since backend
chat_messagedoes auto-redact, frontend should not block or require PII scan prior to sending; removedetectPiiCmdcalls here to align with design.[BLOCKER] src-tauri/src/commands/ai.rs:189-232 — Backend attempts to auto-redact attachment content after 8KB preview, but
pii::apply_redactionsmay be called on truncated content, potentially breaking redaction spans.Evidence:
If PII straddles the 8KB boundary, redaction may be incorrect or incomplete.
Fix: Use full file content for detection and redaction (e.g.,
std::fs::read_to_string(file_path)), then truncate after redaction if needed for embedding.[WARNING] src/lib/tauriCommands.ts:285-290 —
chatMessageCmdsignature useslogFileIds: string[]but invokes withlogFileIds.length > 0 ? logFileIds : null, which contradicts RustOption<Vec<String>>expectingnull/undefinedforNone, but[]is valid.Evidence:
Rust expects
Option<Vec<String>>, but passingnullvs.undefinedmay cause ambiguity in Tauri IPC.Fix: Use
logFileIds.length ? logFileIds : undefined(or omit key if empty) to avoid sendingnull.[SUGGESTION] src-tauri/src/commands/analysis.rs:210-224 —
scan_text_for_piicorrectly omits DB persistence but does not checktextlength, risking DoS on extremely large input.Evidence:
No length cap;
PiiDetector::new().detect()processes full string.Fix: Add a runtime guard (e.g.,
if text.len() > MAX_TEXT_SCAN_BYTES) to reject overly large inputs.Verdict: REQUEST CHANGES
Automated PR Review (qwen3-coder-next via liteLLM):\n\nSummary
The PR introduces critical security fixes to prevent PII leakage in chat attachments and typed messages, including PII detection, auto-redaction in the backend, and frontend warnings. However, a high-severity logic error exists in
src/pages/Triage/index.tsx: the PII scan for typed messages is missing, allowing PII in typed messages to bypass the required warning mechanism.Findings
[BLOCKER] src/pages/Triage/index.tsx:147–151 – Frontend
handleSendfails to scan typed messages for PII before sending, despite the ticket’s requirement that typed messages trigger a one-time PII warning on first send and redact on second.Evidence:
This comment falsely implies frontend PII handling — but the code does not call
scanTextForPiiCmdonmessageto produce a warning. PerTICKET-pii-bypass-chat-attachments.md,scanTextForPiiCmdmust be called beforechatMessageCmdinhandleSend, with a warning shown on first occurrence.Fix: Add PII detection for typed messages in
handleSend, usingscanTextForPiiCmd, apiiWarnedMessageRef, and conditional blocking/warning — before callingchatMessageCmd.[BLOCKER] src-tauri/src/commands/ai.rs:176–191 — Backend
chat_messageauto-redacts typed messages without warning or audit trail for the user, contradicting the frontend requirement that users explicitly acknowledge PII on first send.Evidence:
The backend silently redacts instead of returning a user-facing error like "PII detected; please review and re-send to acknowledge", violating the acceptance criteria: “Typing a message containing PII triggers a one-time warning; sending the same message a second time proceeds (explicit acknowledgment).”
Fix: Backend should return an error (e.g., with PII types) on first send; allow re-send only after user acknowledges via frontend-controlled flag (e.g.,
acknowledge_pii: bool), not auto-redaction.[WARNING] src-tauri/src/commands/ai.rs:176–191 — Backend PII redaction does not log which PII was auto-redacted to the audit log, only to
tracing::warn, breaking audit integrity for compliance.Evidence:
audit_detailsobject inchat_messageincludes"user_message": user_msg.content, butuser_msg.contentis already redacted. Original PII types and values are never stored in audit logs.Fix: Add redacted PII summary (e.g.,
pii_types_detected,pii_types_redacted) toaudit_details.[WARNING] src-tauri/src/commands/ai.rs:205–207 — Backend redacts file attachments before verifying user approval, allowing accidental exposure of PII from attachments the user did not intend to share with the AI.
Evidence:
Per acceptance criteria, file attachments must be blocked hard if PII is found, and users directed to Log Analysis to redact first. The code currently auto-redacts instead of blocking.
Fix: Replace auto-redaction in
chat_messagewith hard-block return error when PII spans are found inlog_file_ids.[WARNING] src-tauri/src/commands/analysis.rs:354–373 —
scan_text_for_piihas no input sanitization beyond length check; large inputs could be crafted to cause CPU/DoS even within 32 KB limit (e.g., pathological regex matches).Evidence:
No timeouts or recursion limits on PII patterns (which may use regex). Since
scan_text_for_piiis exposed via a Tauri command, untrusted content can be passed.Fix: Add explicit timeout guard (e.g., timeout the call), or limit
PiiDetectoriteration count per text chunk.[WARNING] src-tauri/src/commands/ai.rs:203–225 — Truncation (
EMBED_LIMIT: usize = 8_000) happens after redaction, but the boundary cut (redacted[..embed_end]) may still embed partial PII tokens (e.g., cutting “user@example.com” into “user@examp”).Evidence:
This only avoids multi-byte character corruption but does not prevent truncating inside a redaction replacement token (e.g.,
<<EMAIL>>→<<EMA).Fix: Ensure redaction replacement strings are padded to full token width before truncation, or truncate before redaction (though this may cut raw PII, which is less severe than exposing partial redacted spans).
[WARNING] src-tauri/src/commands/ai.rs:267–277 — Audit log stores the redacted
user_msg.content, but frontend may display unredacted content ifuser_messageis not consumed. Inconsistency risks user confusion and audit misrepresentation.Evidence:
full_messageis already redacted, but the frontend expectsresponse.user_message(the stored content). If frontend fails to setuser_messagein the UI bubble, it may show original PII.Fix: Ensure frontend always uses
response.user_message(already done at line 155), but also add audit flag “was_redacted: bool” to distinguish original vs redacted.[WARNING] src/pages/Triage/index.tsx:22–23 —
PendingFiletype incorrectly lackspiiSpansfield; PII scan result is not persisted per attachment for the LogUpload UI to show redaction UI.Evidence:
The frontend code sets
piiSpansonly in session store, not onPendingFile. When re-displaying attached files, the UI cannot recall if PII was found and redacted.Fix: Extend
PendingFileto includepiiSpans?: PiiSpan[].[WARNING] src/lib/tauriCommands.ts:193–197 —
ChatResponse.user_messageis described as “What was stored in the DB — may be auto-redacted. Use this for display and history.” but the frontend does not guard against undefined; older versions may break.Evidence:
In
src/pages/Triage/index.tsx:154–156,response.user_messageis used directly:If
response.user_messageis undefined,+ suffixyields"undefined...".Fix: Add
?? ''fallback or enforce non-nullability in Rust.Verdict: REQUEST CHANGES
Critical PII bypass and missing frontend PII warning logic must be fixed before merge. Backend auto-redaction for typed messages and attachments also violates explicit acceptance criteria requiring user acknowledgment and hard blocks.
Automated PR Review (qwen3-coder-next via liteLLM):\n\nSummary
The PR implements PII redaction for chat attachments and typed messages, addresses a return-type bug in
detect_pii, and introducesuser_messagetoChatResponse. All changes align with the ticket's requirements. No security, logic, or correctness issues were found.Findings
Verdict: APPROVE