textsafe

package
v0.86.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 2, 2026 License: MIT Imports: 3 Imported by: 0

Documentation

Overview

Package textsafe holds the one predicate for the characters that forge, reorder, or hide text: the C1 control block and the zero-width / bidi "invisible" set. It exists because the 2026-09-05 red-probe round (round 4) proved that every scrub, gate, and canonicalizer in the tree walked the C0 range and stopped there, so a C1 CSI byte or a bidi override rode through headers, log lines, storage keys, page titles, terminal transcripts, and identity canonicalizers untouched. Each sink combines its existing C0/DEL handling with the helpers here; the controlbytes and invisibleident analyzers credit a scrub only once its body reaches this set.

The set is deliberately narrow: combining marks and ordinary diacritics fall through. Only codepoints with no visible glyph of their own, whose sole effect is to rearrange or vanish the surrounding text, are named. It mirrors the list that framework/pagination first enumerated as isUnicodeInvisible.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ContainsInvisible

func ContainsInvisible(s string) bool

ContainsInvisible reports whether s carries any invisible/bidi codepoint. Refusal-style gates use it in place of stripping.

func ContainsUnsafe

func ContainsUnsafe(s string) bool

ContainsUnsafe reports whether s carries any C0/DEL/C1/invisible codepoint.

func HasControlBytes added in v0.86.0

func HasControlBytes(s string) bool

HasControlBytes reports whether s carries any ASCII C0 control byte (0x00–0x1F) or DEL (0x7F). It walks bytes, not runes: a valid UTF-8 sequence never contains a byte below 0x20, so the byte walk answers exactly "does a 7-bit control byte appear". This is deliberately NOT the rune-based IsUnsafe/ContainsUnsafe set above — C1 controls and the zero-width/bidi codepoints are out of scope; the callers it replaces gate header values, log fast paths, cache keys, and file names on the ASCII control range only.

Replaces the five byte-identical copies: cmd/repolint hasControlChar, core/handler needsHeaderSanitize, core/middleware containsCtrl, framework/ui hasCtl (screen_cache.go) and hasControlBytes (safety.go).

func IsC1

func IsC1(r rune) bool

IsC1 reports whether r is in the C1 control block (U+0080–U+009F). In UTF-8 each C1 control is a two-byte sequence >= 0x80, so a byte walker keyed on r < 0x20 never sees it; the 8-bit CSI (U+009B) and OSC (U+009D) forms drive terminal escapes exactly as ESC-[ does.

func IsInvisible

func IsInvisible(r rune) bool

IsInvisible reports whether r is a zero-width, joiner, or bidi control codepoint: no glyph of its own, present only to reorder or hide neighbouring text. Trojan-Source (the bidi overrides and isolates) and the zero-width smuggling set both live here.

func IsUnsafe

func IsUnsafe(r rune) bool

IsUnsafe reports whether r is a C0 control (U+0000–U+001F), DEL (U+007F), a C1 control, or an invisible/bidi codepoint: the full set no header, key, identity, log line, title, or terminal transcript built from data should carry.

func Recovered

func Recovered(v any) string

Recovered renders a recover() value for a log sink: fmt.Sprint, then every C0/DEL/C1/invisible codepoint removed, then truncated to 4 KiB. A panic value is whatever the panicking code held at the time, which on a request path is request bytes, so it gets the same scrub a request header does before it reaches the operator's terminal.

Every in-function recover-and-log site uses this instead of logging the raw value (the 2026-09-06 round found nine that did not).

func SanitizeControlBytes added in v0.86.0

func SanitizeControlBytes(s string) string

SanitizeControlBytes removes every C0 control byte and DEL from s, leaving all other bytes — including non-ASCII — untouched. The fast path returns s unchanged when it is already clean. Response header values, CORS tokens, route-group prefixes, and DSL literals pass through this so a CR/LF/NUL cannot smuggle a second header line or a forged log line downstream. It REMOVES the bytes rather than percent-encoding them; percent-encoding is ScrubControlBytes' contract, for values rendered into log lines.

Replaces the four body-identical copies: core/handler sanitizeHeaderValue, core/middleware stripCtrlBytes, framework/ routegroup stripPrefixCtrlBytes, framework/dsl stripDSLControlBytes.

func ScrubControlBytes added in v0.86.0

func ScrubControlBytes(s string) string

ScrubControlBytes percent-encodes every character that can forge, break, or reorder a rendered log line in a request-derived value, URL path, method, or a panic that embeds a request string: the C0 controls and DEL, the C1 controls (U+0080–U+009F — the 8-bit CSI 0x9B and OSC 0x9D drive terminal escapes exactly as ESC-[ does, NEL 0x85 breaks the line), and the zero-width/bidi set (RLO and friends visually rewrite the logged path). An attacker then can't forge a fake log entry, reorder one, or smuggle a terminal-control payload into an operator's tail/less session. slog's JSON handler escapes C0 for valid JSON but leaves C1/bidi runes raw (verified 2026-09-05: a raw C2 9B lands in the encoded line), and a JSON-escaped \r\n is still visible to text grep, with naive log shippers rendering the injected payload on its own line.

r.URL.Path is percent-DECODED, so %0d%0a / %c2%9b / %e2%80%ae in the raw request are a real CRLF / U+009B / U+202E by the time they reach any sink here. Stray non-UTF-8 bytes in 0x80..0x9F are the 8-bit C1 forms on the wire (a bare 0x9B from %9B) and are encoded like their rune counterparts; other invalid bytes pass through untouched.

Replaces the two byte-identical copies: core/middleware scrubControlBytes (logging.go) and battery/log scrubControlBytes (middleware.go), which had drifted into maintained parity.

func StripInvisible

func StripInvisible(s string) string

StripInvisible removes every invisible/bidi codepoint from s. It leaves C0/C1 controls in place for callers that scrub those separately; use StripUnsafe to remove the whole set at once.

func StripUnsafe

func StripUnsafe(s string) string

StripUnsafe removes every C0 control, DEL, C1 control, and invisible/bidi codepoint from s. The fast path returns s unchanged when it is already clean.

func Truncate added in v0.86.0

func Truncate(s string, max int) string

Truncate returns s capped at max bytes, ending in the " … (truncated)" marker when the cap bites so a consumer can see the entry was cut. When max is too small to fit the marker, s is cut at exactly max bytes with no marker. The cut is byte-aligned; callers pass multi-KiB caps where a split rune at the seam is cosmetic.

Replaces the three byte-identical copies: core/handler truncateLog, core/middleware truncate (recovery.go), battery/log truncateString. Recovered keeps its own inline spelling on purpose: its marker is "…(truncated)" (no leading space) and it backs off to a rune boundary, so it is not this function.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL