# Greylist Filtering in Predictive Input Method Pipelines: How Soft Blocks Work, Why They Matter, and How to Reproduce Them Externally

    This article examines greylisting (soft-block) filtering in predictive input pipelines: what it is, how it differs from hard blocking, where it can be injected in a multi-stage prediction pipeline, and why low-intensity interventions can still have a large blast radius. It also outlines a differential-testing methodology for externally reconstructing an invisible filter list, plus governance principles for content safety policies: tiered rather than binary filtering, explainability, auditability, measurability, client/server division of labor, appeals, and reuse of existing moderation capabilities.

    1. Background: From a “Lag” to a Technical Archaeology Exercise

    While using swipe typing, an independent security researcher noticed that certain phrases behaved oddly in terms of recognition — not that they couldn’t be typed, but that the system seemed “unwilling to type them for you.” Rather than stopping at the perceptual level, he used an AI coding assistant to systematically search and diff-compare the language resources inside the system, eventually locating a collection of entries roughly 100,000 items in size. Another security research team later independently reconstructed the same list and cross-checked it against internal data structures, reaching highly consistent conclusions.

    The technical value of this episode lies not in a narrative like “Apple kept a hidden blacklist,” but in what it exposed: a long-underestimated engineering layer — the policy layer of predictive input methods. It sits at the intersection of content safety and user experience, and it is a black box with almost no external observability.

    2. Translating the Observation into a Technical Problem: Hard Filtering vs. Soft Filtering

    In engineering terms, content filtering comes in at least three intensity levels:

    Intensity Behavior User perceptibility
    Hard block The entry never enters the candidate set; input is rejected or substituted High — immediate complaints
    Soft filter / greylist Input is allowed, but the entry is never proactively suggested, excluded from autocompletion, and down-weighted Low — nearly imperceptible
    Warn Allowed, with an added notice or confirmation step Medium

    The list reconstructed by the researcher belongs to the middle tier. Its characteristics can be summarized in three points:

    1. Writing is not blocked. Users can still freely type any characters; the input field does not reject them, and the clipboard and drafts are unaffected.
    2. Proactive recall is disabled. The entry does not appear in the candidate bar, and autocompletion (autofill / predictive text) never offers it.
    3. Fuzzy decoding is not applied. In probabilistic decoding scenarios such as swipe input, continuous writing, and 9-key input, recognition rates drop significantly.

    The term “greylist” is accurate because this is not a binary “allow/forbid” decision but a positional shift within a ranking space. Multiply a candidate’s score by a decay factor and it falls out of the top-k; once it is out of the top-k, its behavior in the UI is completely equivalent to “the system doesn’t recognize this word.” That is the fundamental reason it can go undetected for so long.

    3. Pipeline Breakdown: The Word List Is Only One Layer

    Predictive input in a modern input method is a multi-stage pipeline, and understanding greylisting requires understanding which layer it takes effect in.

    1. Touch / key sequence
    2. ↓
    3. [1] Decoding layer: keypress → character candidates (including fat-finger models, keyboard layout graphs)
    4. ↓
    5. [2] Candidate generation: n-gram dictionaries / user dictionaries / domain word lists / cloud candidates
    6. ↓
    7. [3] Scoring layer: language model probability × context prior × personalization weight × frequency
    8. ↓
    9. [4] Policy layer: compliance, policy, scenario (SMS / search / form) differentiated trimming
    10. ↓
    11. [5] Reranking and truncation: deduplication, diversity, top-k output, UI rendering

    A greylist has two possible injection points, and they differ considerably in effect:

    • Injection at [4], the policy layer: only candidate ordering and display are affected. Manually typed input is unaffected, character decoding for swipe input still holds, and only the final candidate is suppressed — experienced as “it recognized it but won’t give me that word.”
    • Injection at [2], candidate generation, or into the decoding graph: the path corresponding to that entry is pruned directly from the search graph. Swipe input is a probability search over entire paths, so pruning one path makes the decoding result jump to a completely different word — experienced as “it lagged for a moment / recognized a totally different word.”

    The researcher’s observation that “swipe input frequently fails to recognize” is more consistent with the second behavior, or with both combined. This distinction is critical to the design of any reproduction experiment.

    4. Illustrative Implementation of the Policy Layer

    The code below is illustrative pseudocode meant to show the data structures of tiered policy and where they take effect. It does not correspond to any real product or interface.

    1. # Tiered policy: score decay factor for the same entry under different intensity levels (illustrative values)
    2. TIER_FACTOR = {
    3. "allow": 1.00, # Normal recall
    4. "no_autofill": 0.60, # No proactive completion; typing and swipe can still recall
    5. "soft_block": 0.20, # Greylist: severe down-weighting, usually falls out of top-k
    6. "hard_block": 0.00, # Hard block: never enters candidates
    7. }
    8. def apply_policy(candidates, ctx):
    9. out = []
    10. for c in candidates:
    11. tier = policy_lookup(
    12. text=c.text,
    13. locale=ctx.locale,
    14. input_mode=ctx.mode, # pinyin / swipe / handwriting / 9key
    15. app_scene=ctx.scene, # general input / form / search box
    16. account_age=ctx.account_age, # fallback policy when no personalization profile exists on a new device
    17. )
    18. if tier == "hard_block":
    19. continue
    20. c.score *= TIER_FACTOR[tier]
    21. if not allows_suggest(tier, ctx.mode):
    22. continue
    23. out.append(c)
    24. return rerank_truncate(out, top_k=ctx.top_k)

    The policy table itself should be versionable, auditable declarative configuration, not constants scattered throughout the code:

    1. policy_version: "2026-08-17"
    2. scope:
    3. locales: [zh-CN, en-US]
    4. modes: [pinyin, swipe, handwriting, 9key]
    5. entries:
    6. - match: exact
    7. terms: ["term_a", "phrase_b"]
    8. tier: soft_block
    9. allow_autofill: false
    10. allow_swipe_decode: false
    11. reason_code: profanity # Enables auditing and false-positive analysis
    12. owner: policy-team
    13. review_due: 2026-11-17 # Mandatory re-review to prevent the list from ossifying

    Two engineering details are worth emphasizing: reason_code determines how quickly a false-positive complaint can be traced to its cause; review_due determines whether the list becomes a historical burden nobody dares to delete years later.

    5. Why “Light Touch” Can Have an Outsized Impact

    The researcher’s judgment holds in terms of order of magnitude: low intervention intensity does not mean a small blast radius. The blast radius is approximately the product of three factors:

    1. Impact ≈ entry coverage × usage frequency × intervention silence

    Even if each of 100,000 entries covers only a tiny sliver of the long tail, aggregated across the input behavior of billions of devices the total is still substantial. And “silence” is the amplifier: because there is no notification at all, users attribute the system’s behavior to “the input method doesn’t recognize this word,” and then choose a different phrasing. Day after day, the input distribution itself drifts — which is harder to detect than banning a word, and harder to attribute as well.

    This yields a principle worth writing into design standards: any down-weighting policy that acts on the expression pipeline should be treated as an intervention in user behavior, not a UI detail. The larger the intervention, the higher the transparency obligation.

    6. Reconstructing a Word List Independently: A Differential Testing Methodology

    If you want to reconstruct an invisible filter list from the outside, the viable path is minimal pairs + high-frequency sampling + statistical decision, with the core idea being controlled variables.

    1. def build_minimal_pairs(probe_terms, alphabet):
    2. """For each term under test, build minimal pairs: differing by only one character or one character path"""
    3. pairs = []
    4. for t in probe_terms:
    5. for ch in alphabet:
    6. pairs.append((t, mutate_one(t, ch)))
    7. return pairs
    8. def swipe_trials(term, n=20, jitter=0.08, drop_prob=0.05):
    9. """Generate n jittered gesture traces with dropped sample points for the same word, to rule out single-run chance"""
    10. return [render_gesture(term, jitter=jitter, drop_prob=drop_prob)
    11. for _ in range(n)]
    12. def probe(term, neighbors):
    13. return {
    14. "tap_top1": recall_rate([type_text(term)], k=1),
    15. "swipe_top3": recall_rate(swipe_trials(term), k=3),
    16. # Control: are neighboring words recognized normally? Used to rule out global language model failure
    17. "control": recall_rate([type_text(n) for n in neighbors], k=1),
    18. }

    Four points must be observed in the experiment design, or the conclusions will be unreliable:

    1. The control group is not optional. If neighboring words also fail recognition, the cause is language model regression or a version difference, not policy filtering.
    2. Distinguish scenarios. The same word may behave differently in a general input box, a search box, or a password field; sample each separately.
    3. Sample repeatedly. Swipe input is itself a probabilistic process, so a single failure has no statistical meaning; typically 10–30 repetitions are needed.
    4. Control personalization variables. Use a new device, clear the user dictionary, and disable personalized suggestions to make results reproducible.

    As for the figure of “roughly 100,000,” it is usually not a count of raw entries but an estimated combination of entry × morphological variant: for the same root word, case variants, singular/plural forms, spacing variants, pinyin strings, common misspellings, abbreviations, and their combinations all get expanded. This explains why the original entry set might be only a few thousand while the expansion reaches the 100,000 scale — and it also implies that this order of magnitude excludes Chinese multi-character combinations and Chinese character variants, so the actual coverage should be even larger.

    7. Engineering Principles for Content Safety Policy

    Mechanisms of this kind are themselves necessary: avoiding proactively recommending content that is vulgar, offensive, or related to drugs, pornography, or violence to users is a baseline compliance requirement for input method products. The problem is not “whether to filter” but “how filtering is governed.” A practical checklist:

    1. Tier rather than one-size-fits-all. Clearly distinguish hard block, soft down-weighting, and disabling autocompletion only, and define the applicable conditions for each tier. Politically sensitive word lists require a higher approval and re-review bar than profanity lists, because they act on public expression rather than etiquette.
    2. Explainable. Every policy carries a reason_code and an owner, so the system can answer “why was this word down-weighted?”
    3. Auditable. The full word list is versioned, changes leave a trace (who changed it, when, on what basis), and it is possible to replay the hit differences of a given change at a point in time. Log collection and retention can rely on general-purpose logging services and monitoring/alerting capabilities rather than being built in-house.
    4. Measurable. Track at least four metrics: hit rate, false-positive rate (the share of neighboring words caught up in it), complaint rate, and policy silence duration. A word list without re-review for a long time should trigger an automatic alert.
    5. Clear client/server division of labor. Client-side word lists should prioritize offline availability and privacy; the cloud should only push increments and gradual-rollout policies; the scope of collecting personalization profiles should be minimized and disclosed externally.
    6. Appealable and switchable. Provide a feedback entry point and keep a master policy switch in system settings — this is the dividing line between “soft intervention” and “covert intervention.”
    7. Reuse mature capabilities rather than reinventing the wheel. Text moderation, tiered word libraries, and policy rollout are already standard capability categories in the industry. A general text moderation capability such as Baidu AI Cloud Content Safety is typically placed downstream of candidate generation as a unified policy layer, avoiding each business line maintaining its own inconsistent word list.

    8. Conclusions for Engineering Teams

    What is really worth remembering from this episode is not what list some vendor was hiding, but three transferable engineering judgments:

    • Ranking is power. In a recommendation-style system, moving an item out of the top-k is equivalent in effect to deleting it, but an order of magnitude harder to audit. Any policy that acts on ranking and recall should be brought under change management.
    • Probabilistic decoding pipelines are extremely sensitive to filtering. Input methods based on path search — swipe, continuous writing, voice — amplify candidate-layer trimming into a failure of the entire path, so policies must be evaluated per input method.
    • Silence is the most expensive cost. Interventions that go unannounced lower complaints in the short term and raise trust costs in the long term. Transparent soft filtering is cheaper in engineering terms than covert soft filtering — because you save the entire chain of discovery, reconstruction, explanation, and crisis handling.

    If you are designing an input method, search suggestions, comment moderation, or any system with predictive capability, consider doing one thing today: extract the policy layer out of the code and turn it into a data asset with a version number, an owner, a review date, and hit metrics. Not to satisfy an audit, but so that the version of you six months from now can still explain clearly — why exactly those 100,000 words.