Module: Pikuri::Sanitizer
- Defined in:
- lib/pikuri/sanitizer.rb
Overview
Renders attacker-controlled text safe to display, and reports why it was unsafe.
Every string an LLM composes is untrusted (a bash command, a tool
observation echoed to the user, a confirmation-prompt description). A model
— usually one being prompt-injected — can embed bytes a terminal acts on
rather than prints: a CR that overwrites the line just read, an ESC that
recolors/repositions, a backspace that erases, a bidi override that reorders
text so it reads differently than it runs, a zero-width char, or a Cyrillic
а posing as Latin a. A confirmation prompt is worthless if the bytes the
user approves aren't the bytes that execute.
Sanitizer.sanitize is the one chrome-independent primitive every renderer routes through, returning a Result:
- Neutralize — make dangerous bytes visible without changing structure:
control bytes →
\xNN, bidi/zero-width →\u{NNNN}, tab →\t; newlines preserved. Faithful, not beautifying — it never collapses whitespace or rewrites a tab, because the user must see exactly what they approve (a Makefile's leading tab stays a tab). A web chrome addshtml_escapeon top; the HTML layer is the caller's. - Warn — one Warning per category detected (kind + offending tokens + plain-English explanation). Presentation is the chrome's.
Scope (deliberately closed)
Detection is complete on the invisibility / cursor-control / reordering
classes, each a finite codepoint set: C0/C1 controls, DEL, bidi overrides,
zero-width chars. Plus mixed-script tokens — one word welding Latin +
Cyrillic + Greek, the homoglyph-spoof signature, near-zero false positives
(+café+ all-Latin, Москва all-Cyrillic, only Pаypal mixes).
Two confusable classes are out of scope — detecting them needs Unicode
confusables tables and heavy false positives on legitimate multilingual
text: whole-script homoglyphs (an all-Cyrillic string that looks Latin,
no mixing to detect) and single-symbol confusables (the Greek ; U+037E,
full-width forms). "Solid" means complete on the classes above, not
exhaustive over Unicode.
Defined Under Namespace
Constant Summary collapse
- BIDI_OVERRIDES =
Bidirectional-override codepoints: the explicit LRO/RLO/PDF/LRE/RLE set plus the isolate set (LRI/RLI/FSI/PDI). Reordering attacks.
[*0x202a..0x202e, *0x2066..0x2069].freeze
- ZERO_WIDTH =
Zero-width and invisible codepoints: ZWSP, ZWNJ, ZWJ, and the BOM / zero-width no-break space.
[0x200b, 0x200c, 0x200d, 0xfeff].freeze
- SUSPECT =
Codepoints sanitize rewrites: C0 controls including tab (U+0009) but excluding newline (U+000A, passes through), C1 + DEL (U+007F–009F), the zero-width set, and the bidi overrides. Newline is the one control a faithful render keeps, so the C0 range splits around it.
/[\u0000-\u0009\u000b-\u001f\u007f-\u009f\u200b-\u200d\u202a-\u202e\u2066-\u2069\ufeff]/- CONFUSABLE_SCRIPTS =
The three Latin-confusable scripts whose mixing inside one token signals a homoglyph spoof. Punctuation/digits/spaces are
Commonand match none, so they never count toward the two-script threshold. { 'Latin' => /\p{Latin}/, 'Cyrillic' => /\p{Cyrillic}/, 'Greek' => /\p{Greek}/ }.freeze
Class Method Summary collapse
-
.mixed_script_tokens(text) ⇒ Array<String>
Tokens (whitespace-delimited runs) that combine letters from two or more of CONFUSABLE_SCRIPTS — the homoglyph-spoof signature.
-
.sanitize(text) ⇒ Result
Neutralize
textfor literal display and report what was flagged.
Class Method Details
.mixed_script_tokens(text) ⇒ Array<String>
Tokens (whitespace-delimited runs) that combine letters from two or more of CONFUSABLE_SCRIPTS — the homoglyph-spoof signature.
115 116 117 118 119 |
# File 'lib/pikuri/sanitizer.rb', line 115 def self.mixed_script_tokens(text) text.split(/\s+/).reject(&:empty?).select do |token| CONFUSABLE_SCRIPTS.count { |_name, re| token.match?(re) } >= 2 end.uniq end |
.sanitize(text) ⇒ Result
Neutralize text for literal display and report what was flagged.
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/pikuri/sanitizer.rb', line 85 def self.sanitize(text) backspace = false control = [] bidi = [] zero_width = [] clean = text.gsub(SUSPECT) do |ch| cp = ch.ord if cp == 0x09 '\\t' elsif cp == 0x08 backspace = true '\\x08' elsif BIDI_OVERRIDES.include?(cp) format('\\u{%04x}', cp).tap { |t| bidi << t } elsif ZERO_WIDTH.include?(cp) format('\\u{%04x}', cp).tap { |t| zero_width << t } else format('\\x%02x', cp).tap { |t| control << t } end end Result.new(text: clean, warnings: warnings_for(backspace, control, bidi, zero_width, mixed_script_tokens(text))) end |