From 86c8d0b12c87c653bd65448f9d63e8bcd5bf54ed Mon Sep 17 00:00:00 2001 From: davotoula Date: Tue, 21 Jul 2026 14:57:55 +0100 Subject: [PATCH] fix: replace raw bidi/invisible control characters in source with \u escapes the files are now pure-ASCII and visually unambiguous: - BlossomPaymentSafetyTest: raw U+202E/U+202C test payload -> escapes - BlossomPaymentRequired: BIDI_OVERRIDES char array -> escapes - Sanitizer: RTL_OVERRIDES and ZERO_WIDTH regex classes -> escapes Verified by mutation: with BIDI_OVERRIDES stripping disabled, reasonBidiOverridesAreRemoved fails, proving the escaped payload still carries a real U+202E. Emoji ZWJ sequences in RichTextParserTest are intentionally untouched (functional joiners, not bidi controls). --- .../service/uploads/blossom/BlossomPaymentSafetyTest.kt | 6 +++--- .../amethyst/commons/moderation/notifications/Sanitizer.kt | 4 ++-- .../quartz/nipB7Blossom/BlossomPaymentRequired.kt | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomPaymentSafetyTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomPaymentSafetyTest.kt index d1564aeac8..fbaed1939b 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomPaymentSafetyTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomPaymentSafetyTest.kt @@ -215,9 +215,9 @@ class BlossomPaymentSafetyTest { @Test fun reasonBidiOverridesAreRemoved() { - val clean = challenge(invoice1000Sats, reason = "fee ‮reversed‬ text").sanitizedReason()!! - assertFalse(clean.contains('‮')) - assertFalse(clean.contains('‬')) + val clean = challenge(invoice1000Sats, reason = "fee \u202Ereversed\u202C text").sanitizedReason()!! + assertFalse(clean.contains('\u202E')) + assertFalse(clean.contains('\u202C')) } @Test diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/moderation/notifications/Sanitizer.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/moderation/notifications/Sanitizer.kt index d2fd4927fc..bd6cc3098b 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/moderation/notifications/Sanitizer.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/moderation/notifications/Sanitizer.kt @@ -21,8 +21,8 @@ package com.vitorpamplona.amethyst.commons.moderation.notifications private val CONTROL_CHARS = Regex("\\p{Cntrl}") -private val RTL_OVERRIDES = Regex("[‪-‮⁦-⁩]") -private val ZERO_WIDTH = Regex("[​-‍]") +private val RTL_OVERRIDES = Regex("[\u202A-\u202E\u2066-\u2069]") +private val ZERO_WIDTH = Regex("[\u200B-\u200D\uFEFF]") private val WHITESPACE = Regex("\\s+") private val URL_PATTERN = Regex("https?://\\S+") diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipB7Blossom/BlossomPaymentRequired.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipB7Blossom/BlossomPaymentRequired.kt index 6ffd47cb8c..d1adbc8938 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipB7Blossom/BlossomPaymentRequired.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipB7Blossom/BlossomPaymentRequired.kt @@ -74,7 +74,7 @@ data class BlossomPaymentRequired( const val MAX_REASON_LENGTH = 200 /** LRE/RLE/PDF/LRO/RLO and the isolate family — invisible, and they reorder what follows. */ - private val BIDI_OVERRIDES = charArrayOf('‪', '‫', '‬', '‭', '‮', '⁦', '⁧', '⁨', '⁩', '‏', '‎') + private val BIDI_OVERRIDES = charArrayOf('\u202A', '\u202B', '\u202C', '\u202D', '\u202E', '\u2066', '\u2067', '\u2068', '\u2069', '\u200F', '\u200E') private val WHITESPACE_RUN = Regex("\\s+")