Changelog: 0.13.x

Archived verbatim from CHANGELOG.md. Newer releases are in the changelog; the archive index lists every older series.

[0.13.0] — 2026-08-17

Added

  • Full HostnameAnalysis across the Node, Ruby, and Java/Kotlin bindings (#549). A new analyzeHostname / analyze_hostname returns the complete analysis (verdict + all granular signals, including whole_script_confusable) — previously those bindings exposed only the .suspicious boolean. Node returns a HostnameAnalysis object, Ruby a Hash, and Java/Kotlin a dev.disarm.HostnameAnalysis record (the List<List<String>> labelScripts and List<Boolean> labelWholeScriptConfusable are marshalled directly across the JNI boundary). The boolean isSuspiciousHostname predicate is unchanged. Mirrors how the anomaly report is already exposed. The C-ABI gains the matching structured entry points in the same window (see below).
  • Structured reports across the C-ABI (#553). Five new #[ffi_export] entry points return their report as a JSON string (freed with the existing disarm_string_free): disarm_analyze_hostname (the full HostnameAnalysis, including whole_script_confusable / label_whole_script_confusable), disarm_inspect_anomalies (per-finding kind/token/start/end/detail/reason, taking a JSON word-array lexicon so the leet/segmentation branches match the other bindings, not just the structural ones), disarm_inspect_auto_lang (script + chosen language + discriminators), and the fallible disarm_lang_info / disarm_script_info metadata lookups. JSON is the one transport for every nested shape (List<List<String>>, List<Boolean>, optionals) — no repr(C) mirror structs, trivially parsed by any C/Go/Swift/ ctypes consumer. serde_json is a C-ABI-only dependency; the pure core still carries no serde. The scalar predicates (disarm_is_suspicious_hostname, …) are unchanged.
  • Whole-script-confusable signal on HostnameAnalysis (#545). Two additive fields — whole_script_confusable (any label qualifies) and the per-label label_whole_script_confusable — name the fact that discriminates a whole-script spoof (аррӏе.com → skeleton apple.com, every letter a confusable) from a genuine non-Latin domain (москва.рф, whose м/к/в survive the skeleton). A label qualifies when it is single-script, non-Latin, and its confusable skeleton is entirely Latin. It is a graded signal, not a verdict, and is deliberately not folded into suspicious: on its own it fires on short non-Latin ccTLDs (руpy) and on real words whose every letter is a confusable (осаoca). The precise, low-false-positive policy — whole_script_confusable(non-TLD label) ∧ Latin TLD — is caller-side (disarm does not model registrable boundaries). Exposed on the Rust and Python surfaces; the other bindings expose only .suspicious today and are tracked separately (#549).

Documentation

  • Clarified that is_suspicious_hostname's suspicious flag is a maximally conservative screen (an any-character confusable test flags essentially every non-Latin hostname), not a precise verdict, and moved whole-script confusables in THREAT_MODEL.md from out of scope to a defined mechanism with its stated irreducible false-positive class. Completed the HostnameAnalysis field table in the predicates docs.
  • Upgrading guide + stability-contract clarifications (#546, #547, #548). Added docs/upgrading.md (a new top-level nav section, distinct from Migration) with the cumulative table of public renames since 0.9 and a !!! danger note on the is_safe_hostnameis_suspicious_hostname boolean-polarity inversion. Restated SECURITY.md's supported-version window as a self-maintaining rule (was the stale 0.6.x). Extended the semver data-change clause in docs/RUST_API.md to name the security surfaces (is_suspicious_hostname, normalize_confusables, …), recorded the bundled Unicode/UTS#39 data versions in docs/provenance.md (+ provenance headers on the two confusables tables), and clarified that "removed in 1.0" refers to the RELEASING.md commercial-support milestone, not the next release.
  • Surfaced disarm's measured BitAbuse recovery in the coverage docs (#543). Re-ran the adversarial-eval harness against the full corpus on v0.12.0 (325,580 rows) — strip_obfuscation now recovers 65.3% word-level (up from 64.1% on 0.6.3) with 81.7% of non-ASCII perturbation occurrences folded — and added disarm's own row to the coverage spectrum in docs/security/adversarial-defense.md and THREAT_MODEL.md (previously only the ~35% class baseline and ~96% ceiling appeared, inviting readers to transfer ~35% onto disarm). The word-level metric is defined inline with line-exact (5.8%) stated alongside, and the BitAbuse figure is explicitly separated from the near-identical TR39-space XMR = 0.634. Retired the divergent pre-harness baseline in the benchmark README in favour of the committed report, and documented a manual pre-release refresh cadence.