disarm 0.13.0-x86_64-linux → 0.14.0-x86_64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/disarm/3.1/disarm.so +0 -0
- data/lib/disarm/3.2/disarm.so +0 -0
- data/lib/disarm/3.3/disarm.so +0 -0
- data/lib/disarm/version.rb +1 -1
- data/lib/disarm.rb +97 -8
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4e0669625aaa90d822968d52986b7fe06971564991ab212439d18c23e8f6a71b
|
|
4
|
+
data.tar.gz: 27e026ce2b0985c5c42a837ec80078fba14ab8c41dd9e853212c2999cf335f23
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 41f8fa679ebcb84c16b801f33c7d5a6ef943d4bd6b87a80ab7f40d152a02c62c6ac394fe0376224e4a22746ee26432dc807e223e7991d1737ce4d78f42a826a5
|
|
7
|
+
data.tar.gz: d96b321c9a911bd50d3d02deb89e9cee5dde089f2c56edb418999a093e6a772f5f9e6cd10bb9276724d91e60d5979e3e1147e2e3bb8ebfe0ad9815c2f8422dba
|
data/lib/disarm/3.1/disarm.so
CHANGED
|
Binary file
|
data/lib/disarm/3.2/disarm.so
CHANGED
|
Binary file
|
data/lib/disarm/3.3/disarm.so
CHANGED
|
Binary file
|
data/lib/disarm/version.rb
CHANGED
data/lib/disarm.rb
CHANGED
|
@@ -54,8 +54,18 @@ module Disarm
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
# Fold cross-script confusables toward `target:` (:latin or :cyrillic).
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
#
|
|
58
|
+
# `digit_policy:` selects how non-Latin DIGITS fold (#561). `:numeric` (default)
|
|
59
|
+
# sends them to the ASCII digit — `०` becomes `0` — which is right for prose, where
|
|
60
|
+
# a Devanagari zero really is a zero. `:tr39` uses upstream's targets, which send
|
|
61
|
+
# most of them to a Latin letter (`०` → `o`; three of the 45 rows fold to `.` or
|
|
62
|
+
# to the two characters `rn` instead); that is what an identifier *skeleton*
|
|
63
|
+
# wants, since its only job is to make two confusable identifiers collide. The two
|
|
64
|
+
# differ on 45 rows and agree everywhere else. Scoped to `target: :latin` — the
|
|
65
|
+
# override rows are generated from the Latin table and carry TR39's Latin-script
|
|
66
|
+
# targets, so with `target: :cyrillic` it is a no-op.
|
|
67
|
+
def normalize_confusables(text, target: :latin, digit_policy: :numeric)
|
|
68
|
+
translate_errors { _normalize_confusables(text, target.to_s, digit_policy.to_s) }
|
|
59
69
|
end
|
|
60
70
|
|
|
61
71
|
# Whether `text` contains a character confusable with `target:` (:latin or
|
|
@@ -151,9 +161,43 @@ module Disarm
|
|
|
151
161
|
translate_errors { _fold_case(text) }
|
|
152
162
|
end
|
|
153
163
|
|
|
164
|
+
# Whether `text` is a stable identity key under case folding — whether
|
|
165
|
+
# `fold_case` and `String#downcase` agree on it (#619). A false result means
|
|
166
|
+
# some other string folds to the same value, so a table keyed on this one can
|
|
167
|
+
# collide: "groß.txt" and "gross.txt" are the pair node-tar collided on
|
|
168
|
+
# (CVE-2026-23950). It is a fact about the string, not an accusation.
|
|
169
|
+
def case_fold_stable?(text)
|
|
170
|
+
translate_errors { _is_case_fold_stable?(text) }
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Which of `values` are the same name under `key` (#620).
|
|
174
|
+
#
|
|
175
|
+
# Every other disarm detector is a single-string predicate, and a collision is
|
|
176
|
+
# not a property of a single string — "groß.txt" is an ordinary German filename,
|
|
177
|
+
# and "аdmin" is only a problem next to "admin". This is the set-shaped question
|
|
178
|
+
# node-tar's PathReservations guard failed to ask (CVE-2026-23950).
|
|
179
|
+
#
|
|
180
|
+
# `key:` is one of "fold_case", "search_key", "catalog_key", "canonicalize",
|
|
181
|
+
# "canonicalize_strict", "normalize_confusables". There is no default: a stronger
|
|
182
|
+
# key finds more collisions, including ones nobody attacked ("search_key" collides
|
|
183
|
+
# "Muller" with "Müller"), so the choice is the policy.
|
|
184
|
+
#
|
|
185
|
+
# A group is reported only when two or more DISTINCT inputs share a key; the same
|
|
186
|
+
# name twice is the same name twice. `lang:` reaches "search_key" and
|
|
187
|
+
# "catalog_key" and is ignored by the rest.
|
|
188
|
+
#
|
|
189
|
+
# Returns an array of hashes with `:key`, `:values` (distinct, first-appearance
|
|
190
|
+
# order) and `:indices` (every position, ascending — not parallel to `:values`).
|
|
191
|
+
def find_key_collisions(values, key:, lang: nil)
|
|
192
|
+
translate_errors { _find_key_collisions(values, key.to_s, lang&.to_s) }
|
|
193
|
+
.map { |k, vals, idx| { key: k, values: vals, indices: idx } }
|
|
194
|
+
end
|
|
195
|
+
|
|
154
196
|
# Whether the hostname looks like a mixed-script / confusable / bidi-reorder
|
|
155
197
|
# IDN spoof. Flags a mixed-script label, a Latin confusable, or a
|
|
156
|
-
# bidi-direction conflict (see #bidi_conflict?, the "BiDi Swap" precondition)
|
|
198
|
+
# bidi-direction conflict (see #bidi_conflict?, the "BiDi Swap" precondition),
|
|
199
|
+
# or a UAX #9 bidi control character (#603 — the RLO spoof, which the direction
|
|
200
|
+
# conflict is blind to).
|
|
157
201
|
# A false result asserts nothing was *found*, not that the host is safe.
|
|
158
202
|
def suspicious_hostname?(host)
|
|
159
203
|
translate_errors { _suspicious_hostname?(host) }
|
|
@@ -163,21 +207,24 @@ module Disarm
|
|
|
163
207
|
# as a Hash. `:suspicious` is a maximally conservative screen (an any-character
|
|
164
208
|
# confusable test flags essentially every non-Latin host), not a precise verdict;
|
|
165
209
|
# branch on the granular signals plus your own policy. Keys: :suspicious,
|
|
166
|
-
# :scripts, :mixed_script, :has_confusables, :bidi_conflict, :
|
|
210
|
+
# :scripts, :mixed_script, :has_confusables, :bidi_conflict, :bidi_control,
|
|
211
|
+
# :has_invisible, :cross_label_script,
|
|
167
212
|
# :label_scripts, :whole_script_confusable, :label_whole_script_confusable,
|
|
168
213
|
# :canonical. `:whole_script_confusable` is a graded signal, NOT folded into
|
|
169
214
|
# `:suspicious` (see #545).
|
|
170
|
-
def analyze_hostname(host)
|
|
215
|
+
def analyze_hostname(host, contractions: false)
|
|
171
216
|
suspicious, scripts, mixed_script, has_confusables, bidi_conflict,
|
|
172
|
-
cross_label_script, label_scripts,
|
|
173
|
-
label_whole_script_confusable, canonical =
|
|
174
|
-
translate_errors { _analyze_hostname(host) }
|
|
217
|
+
bidi_control, has_invisible, cross_label_script, label_scripts,
|
|
218
|
+
whole_script_confusable, label_whole_script_confusable, canonical =
|
|
219
|
+
translate_errors { _analyze_hostname(host, contractions) }
|
|
175
220
|
{
|
|
176
221
|
suspicious:,
|
|
177
222
|
scripts:,
|
|
178
223
|
mixed_script:,
|
|
179
224
|
has_confusables:,
|
|
180
225
|
bidi_conflict:,
|
|
226
|
+
bidi_control:,
|
|
227
|
+
has_invisible:,
|
|
181
228
|
cross_label_script:,
|
|
182
229
|
label_scripts:,
|
|
183
230
|
whole_script_confusable:,
|
|
@@ -314,6 +361,41 @@ module Disarm
|
|
|
314
361
|
end
|
|
315
362
|
end
|
|
316
363
|
|
|
364
|
+
# Every upstream confusable source the bundled table does not fold, as a sorted
|
|
365
|
+
# Array of single-character Strings (#563). An Array rather than a Set, matching
|
|
366
|
+
# `list_scripts` and avoiding a `require "set"` in the gem entrypoint.
|
|
367
|
+
#
|
|
368
|
+
# Read as exposure, not as a score — this is where an adaptive attacker goes once
|
|
369
|
+
# the mapped sources stop working. It includes five ASCII characters ("%", "0",
|
|
370
|
+
# "1", "I", "m"): TR39 is a skeleton transform, and disarm deliberately does not
|
|
371
|
+
# apply those rows because folding a legitimate "m" to "rn" corrupts prose.
|
|
372
|
+
def unmapped_confusables(target: :latin)
|
|
373
|
+
translate_errors { _unmapped_confusables(target.to_s) }
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# Confusable sources in `text` the bundled table does not fold, as an Array of
|
|
377
|
+
# `{ char:, offset: }` hashes in order of appearance — the confusables analogue of
|
|
378
|
+
# `find_untranslatable`, with the same byte-offset convention (#563).
|
|
379
|
+
def find_unmapped_confusables(text, target: :latin)
|
|
380
|
+
translate_errors do
|
|
381
|
+
_find_unmapped_confusables(text, target.to_s)
|
|
382
|
+
.map { |ch, offset| { char: ch, offset: offset } }
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# ML/NLP normalization: NFKC → emoji→text → transliterate → strip accents →
|
|
387
|
+
# [case fold] → strip control → strip zero-width → collapse whitespace.
|
|
388
|
+
#
|
|
389
|
+
# `fold_case:` defaults to true. Pass false in front of a CASED model — folding is
|
|
390
|
+
# destructive, cannot be undone downstream, and an uncased evaluation harness cannot
|
|
391
|
+
# measure what it cost. It restores case, not diacritics: accents are still stripped.
|
|
392
|
+
#
|
|
393
|
+
# Folds no confusables, so it is not a homoglyph defence at any setting; compose it
|
|
394
|
+
# after `normalize_confusables` when a model needs both.
|
|
395
|
+
def ml_normalize(text, lang: nil, emoji_style: "cldr", fold_case: true)
|
|
396
|
+
translate_errors { _ml_normalize(text, lang&.to_s, emoji_style.to_s, fold_case) }
|
|
397
|
+
end
|
|
398
|
+
|
|
317
399
|
# The Unicode scripts present in `text`, in first-appearance order
|
|
318
400
|
# (Common/Inherited excluded), as stable UCD identifiers (e.g. "Latin").
|
|
319
401
|
def detect_scripts(text)
|
|
@@ -356,6 +438,13 @@ module Disarm
|
|
|
356
438
|
translate_errors { _script_info(name.to_s) }
|
|
357
439
|
end
|
|
358
440
|
|
|
441
|
+
# The Unicode `confusables.txt` release the bundled confusable tables were folded
|
|
442
|
+
# from, e.g. "17.0.0". Not a Unicode version for the library as a whole — the
|
|
443
|
+
# case-folding and width tables track different releases (see docs/provenance.md).
|
|
444
|
+
def confusables_version
|
|
445
|
+
translate_errors { _confusables_version }
|
|
446
|
+
end
|
|
447
|
+
|
|
359
448
|
# Every script disarm knows, as stable UCD script identifiers (includes
|
|
360
449
|
# "Common"/"Inherited"), sorted by name.
|
|
361
450
|
def list_scripts
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: disarm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.14.0
|
|
5
5
|
platform: x86_64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Richard Quinn
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|