Files
openclaw/apps/macos/Sources/OpenClaw/ExecApprovalCommandDisplaySanitizer.swift
Devin Robison 503b748a8e fix(exec-approvals): escape control characters in display sanitizers (#68198)
* fix(exec-approvals): escape control characters in display sanitizers

* docs(changelog): add exec approval control-char display sanitizer entry

* fix(exec-approvals): redact before escape, cover U+2028/U+2029 in display sanitizers

* fix(exec-approvals): strip invisibles before redaction and align forwarder test

* fix(exec-approvals): cover Zs bypass and preserve multi-line context on obfuscated secrets

* fix(exec-approvals): compare redaction outputs by content, not length

* fix(exec-approvals): suppress raw command on bypass; cover non-ASCII Zs in macOS sanitizer

* fix(exec-approvals): use position-bitmap bypass detection and bound input size

* style(exec-approvals): satisfy oxlint no-new-array-single-argument and SwiftFormat

* fix(exec-approvals): iterate by code point and redact before truncating
2026-04-17 15:59:08 -06:00

46 lines
1.5 KiB
Swift

import Foundation
enum ExecApprovalCommandDisplaySanitizer {
private static let invisibleCodePoints: Set<UInt32> = [
0x115F,
0x1160,
0x3164,
0xFFA0,
]
static func sanitize(_ text: String) -> String {
var sanitized = ""
sanitized.reserveCapacity(text.count)
for scalar in text.unicodeScalars {
if self.shouldEscape(scalar) {
sanitized.append(self.escape(scalar))
} else {
sanitized.append(String(scalar))
}
}
return sanitized
}
private static func shouldEscape(_ scalar: UnicodeScalar) -> Bool {
let category = scalar.properties.generalCategory
if category == .control
|| category == .format
|| category == .lineSeparator
|| category == .paragraphSeparator
{
return true
}
// Escape non-ASCII space separators (NBSP, narrow NBSP, ideographic space, etc.) so
// attackers cannot spoof token boundaries in the approval UI with spaces that render
// like a plain space but are handled differently by shells/parsers.
if category == .spaceSeparator, scalar.value != 0x20 {
return true
}
return self.invisibleCodePoints.contains(scalar.value)
}
private static func escape(_ scalar: UnicodeScalar) -> String {
"\\u{\(String(scalar.value, radix: 16, uppercase: true))}"
}
}