feat(privacy): redact PII from LLM context when privacy.redactPII is enabled#47959
feat(privacy): redact PII from LLM context when privacy.redactPII is enabled#47959kraocode wants to merge 1 commit into
Conversation
Greptile SummaryThis PR adds a Key observations:
Confidence Score: 4/5
|
| function hashId(value: string): string { | ||
| return createHash("sha256").update(value).digest("hex").slice(0, 12); |
There was a problem hiding this comment.
Unsalted hash makes phone-number pseudonyms reversible
return createHash("sha256").update(value).digest("hex").slice(0, 12);For SenderE164 (phone numbers), the input space is small and well-structured (~15 decimal digits, ITU-T E.164). An attacker who obtains the hashed prompt logs can reverse every pseudonymised phone number by pre-computing hashes for all valid numbers — a lookup table of the entire global number space is feasible in seconds on modern hardware. The truncation to 12 hex chars makes collisions more likely but does not protect against this reversal.
The PR description explicitly calls this out as an intentional design trade-off ("Deterministic hash (no salt): same user always maps to same pseudonym, consistent across sessions"), which is reasonable for cross-session correlation. Just worth making sure operators are aware that redactPII: true provides pseudonymisation (unlinkability to an outside observer without the original data) rather than true anonymisation (irreversibility).
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auto-reply/reply/inbound-meta.ts
Line: 7-8
Comment:
**Unsalted hash makes phone-number pseudonyms reversible**
```ts
return createHash("sha256").update(value).digest("hex").slice(0, 12);
```
For `SenderE164` (phone numbers), the input space is small and well-structured (~15 decimal digits, ITU-T E.164). An attacker who obtains the hashed prompt logs can reverse every pseudonymised phone number by pre-computing hashes for all valid numbers — a lookup table of the entire global number space is feasible in seconds on modern hardware. The truncation to 12 hex chars makes collisions more likely but does not protect against this reversal.
The PR description explicitly calls this out as an intentional design trade-off ("Deterministic hash (no salt): same user always maps to same pseudonym, consistent across sessions"), which is reasonable for cross-session correlation. Just worth making sure operators are aware that `redactPII: true` provides pseudonymisation (unlinkability to an outside observer without the original data) rather than true anonymisation (irreversibility).
How can I resolve this? If you propose a fix, please make it concise.…enabled Add privacy.redactPII config option (boolean, default false). When enabled: - SenderE164 (phone number) is stripped from user context - SenderId is hashed (user_ + 12-char sha256) in both conversation info and sender block - chat_id is hashed (preserving channel prefix) in system prompt Public fields (SenderName, SenderUsername, SenderTag) are preserved. No functional side effects: auth/routing happens at gateway layer before LLM call. Closes openclaw#47958
0cec448 to
01d8d40
Compare
|
Closing: re-submitting with cleaner branch name and commit history. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 01d8d4033e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const colonIdx = value.indexOf(":"); | ||
| if (colonIdx > 0) { | ||
| const prefix = value.slice(0, colonIdx); | ||
| const id = value.slice(colonIdx + 1); | ||
| return `${prefix}:${hashId(id)}`; |
There was a problem hiding this comment.
Hash full topic IDs before preserving chat_id prefixes
hashChatId assumes the segment before the first : is always a safe channel prefix, but valid routing IDs in this repo can start with the real chat identifier and append :topic:<id> (for example Telegram topic IDs in extensions/telegram/src/targets.ts). With privacy.redactPII=true, an input like -1001234567890:topic:42 becomes -1001234567890:<hash>, which still exposes the raw chat ID to the LLM. Redaction should only preserve known non-PII prefixes (like telegram:/channel:) and otherwise hash the entire identifier.
Useful? React with 👍 / 👎.
| const senderE164 = options?.redactPII ? undefined : safeTrim(ctx.SenderE164); | ||
| const rawSenderId = safeTrim(ctx.SenderId); | ||
| const senderId = options?.redactPII && rawSenderId ? hashSenderId(rawSenderId) : rawSenderId; |
There was a problem hiding this comment.
Apply redactPII to inbound history sender labels
The new redaction path only sanitizes the current sender fields (SenderE164/SenderId), but this function still emits InboundHistory[].sender unchanged later, so phone numbers can still reach the model when redaction is enabled. This is reachable on current channel code paths (for example WhatsApp builds history senders as name (e164) in extensions/whatsapp/src/auto-reply/monitor/group-gating.ts:53-56), so privacy.redactPII currently does not satisfy its privacy contract for grouped history context.
Useful? React with 👍 / 👎.
Summary
Add
privacy.redactPIIconfig option (boolean, defaultfalse). When enabled, redacts personally identifiable information from the prompt context sent to the LLM provider.What's redacted
SenderE164(phone number)SenderId(platform user ID)user_+ 12-char sha256)chat_idSenderNameSenderUsernameSenderTagWhy
Phone numbers (
SenderE164) and user IDs (SenderId,chat_id) are PII that the LLM has no functional need for. Auth and routing happen at the gateway layer before the LLM call. Signal/WhatsApp always include phone numbers, and Telegram private chatchat_idequals the user ID.Config
{ "privacy": { "redactPII": true } }Design decisions
formatOwnerDisplayId(12-char sha256).SenderE164is stripped (redundant field).SenderIdandchat_idare hashed (needed for speaker differentiation and context identification).SenderName,SenderUsername,SenderTagare user-chosen and publicly visible — not treated as PII.Changes
src/config/zod-schema.ts— newprivacy.redactPIIfieldsrc/auto-reply/reply/inbound-meta.ts— hash/strip PII in both builder functionssrc/auto-reply/reply/get-reply-run.ts— reads config, passes to builderssrc/auto-reply/reply/inbound-meta.test.ts— 7 new test cases (30 total, all passing)Closes #47958