Skip to content

Clipboard suggestion: mask based on content patterns (OTP/CC/JWT/seed/secret) - #2491

Open
dsremo wants to merge 1 commit into
HeliBorg:mainfrom
dsremo:dsremo/clipboard-content-masking
Open

Clipboard suggestion: mask based on content patterns (OTP/CC/JWT/seed/secret)#2491
dsremo wants to merge 1 commit into
HeliBorg:mainfrom
dsremo:dsremo/clipboard-content-masking

Conversation

@dsremo

@dsremo dsremo commented May 13, 2026

Copy link
Copy Markdown
Contributor

Summary

isClipSensitive() currently only returns true when the target input field declares inputType=textPassword or the OS-side ClipDescription sensitivity flag is set. That leaves OTPs, credit-card numbers, BIP-39 seed phrases, JWTs, and PEM private keys visible in plaintext on the suggestion strip whenever the user is pasting into a normal text field (chat composer, email body, search bar) — which is the most common shoulder-surf risk in practice.

This PR extends isClipSensitive() to additionally inspect the clipboard content itself, returning true (and therefore masking the visible suggestion to ***...***) if the content matches any of:

Pattern Matches
P_OTP Standalone 4–8 digit run (whole-string match)
P_CREDIT_CARD 13–19 digit run with optional spaces/dashes
P_JWT eyJ…\.<base64url>\.<base64url>
P_LONG_HEX Hex string ≥ 32 chars (SHA-256, API tokens, hex-encoded keys)
P_SEED_PHRASE 12–24 lowercase 3–8 char words space-separated (BIP-39 style)
P_SECRET_KEYWORD password, passwd, secret, api_key, access_token, bearer, private_key, ssh-rsa, ssh-ed25519, PEM private key block

Behavior

  • The masking only affects the visible suggestion strip text. The actual paste still uses the real content via latinIME.onTextInput(content.toString()) — so functionality is unchanged for the user, they just don't see a 6-digit OTP rendered in 18pt next to whoever might be looking over their shoulder.
  • 5000-char ceiling on the regex scan to avoid pathological clipboard payloads (e.g., full HTML pages on the clipboard) hitting the regex engine on every keystroke.
  • Pattern list is intentionally conservative — false positives just mean "user sees *** instead of the value for ~3 minutes of clipboard freshness", false negatives mean the value is visible.

Files touched

  • app/src/main/java/helium314/keyboard/latin/ClipboardHistoryManager.kt (+25 / −1)

Tested

Built and installed on Android 16 (HeliBoard_3.9-debug.apk). Verified manually with sample clipboard contents matching each pattern.

Notes

No new permissions. No new strings (the existing *-repeat path is reused). No settings — this is pure defense-in-depth on an existing privacy-sensitive code path. If you'd prefer this gated behind a setting, happy to add one.

…atterns

Currently isClipSensitive() only returns true when the target input field
declares inputType=textPassword (or the OS-side ClipDescription sensitivity
flag is set). That leaves OTPs, credit-card numbers, BIP-39 seed phrases,
JWTs, etc. visible in plaintext on the suggestion strip whenever the user
is pasting into a normal text field (chat composer, email body, search bar) -
which is the most common shoulder-surf risk.

This patch extends isClipSensitive() to additionally inspect the clipboard
content itself and mask the suggestion if it matches any of:

  - OTP: standalone 4-8 digit run (whole-string)
  - Credit card: 13-19 digit run with optional spaces/dashes
  - JWT: eyJ...\.<base64url>\.<base64url>
  - Long hex: hex string >=32 chars (SHA-256, API tokens, hex keys)
  - BIP-39-style seed phrase: 12-24 lowercase 3-8 char words
  - Secret keywords: password, passwd, secret, api_key, access_token,
    bearer, private_key, ssh-rsa, ssh-ed25519, PEM private key block

The masking only affects the visible suggestion strip text; the actual
paste still uses the real content via latinIME.onTextInput(content). 5000
char ceiling on the regex scan to avoid pathological clipboard payloads.

No new permissions, no UI changes, no settings - this is a pure
defense-in-depth tightening of an existing privacy-sensitive code path.
@MiMoHo

MiMoHo commented Jul 6, 2026

Copy link
Copy Markdown

Thanks for working on this – masking obvious secrets in the clipboard suggestion is a nice idea. I read through the patterns in ClipboardHistoryManager.kt and I think several of them will over-match and mask a lot of harmless clipboard content, which is risky because the user then can't see what they're about to paste:

  • P_SECRET_KEYWORD uses .find(), so any text that merely contains one of the keywords is masked — e.g. "I forgot my password, can you reset it?" or "the API key is in the docs" would be hidden.
  • P_LONG_HEX = \b[a-fA-F0-9]{32,}\b matches every git commit SHA (40 hex), MD5 (32), and dashless UUID — extremely common developer clipboard content that isn't secret.
  • P_CREDIT_CARD = \b(?:\d[ -]*?){13,19}\b with no Luhn check matches any 13–19 digit run: 13-digit millisecond epoch timestamps, international phone numbers, order/tracking numbers. A Luhn checksum would cut the false positives dramatically.
  • P_OTP = ^\s*\d{4,8}\s*$ masks every standalone 4–8 digit number — years (2026), PINs, prices, quantities.
  • P_SEED_PHRASE (.matches() on the trimmed text) masks any message that happens to be 12–24 lowercase 3–8-letter words with nothing else — i.e. ordinary lowercase chat sentences of that length.

(P_JWT is nicely specific, no complaint there.)

Two suggestions:

  1. Since this is a noticeable behaviour change, it might be best as an opt-in setting — there's even a related // todo: setting whether to store sensitive clip data? in this same file, and CONTRIBUTING.md notes that noticeable adjustments should generally be optional.
  2. If kept on by default, tightening the heuristics (Luhn for cards, anchoring the keyword match to key: value / assignment shapes rather than .find() anywhere, excluding 32/40-hex SHAs) would reduce the false positives a lot.

Happy to be corrected if some of these are intentional.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants