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:

  1. 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 adds html_escape on top; the HTML layer is the caller's.
  2. 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

Classes: Result, Warning

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 Common and 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

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.

Parameters:

  • text (String)

Returns:

  • (Array<String>)

    distinct offending tokens, first-seen order



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.

Parameters:

  • text (String)

    attacker-controlled text (an LLM-composed command, description, or tool observation), e.g. "echo hi\rrm -rf /"

Returns:

  • (Result)

    the neutralized text plus an Array<Warning> (empty when clean)



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