Help us shape the future of Go! Take the 2026 Go Developer Survey

untrusted

package
v0.16.0 Latest Latest
Warning

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

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

Documentation

Overview

Package untrusted carries strings that originate from OUTSIDE agentsync's trust boundary — fetched marketplace/plugin metadata (ids, versions, marketplace names) and native-config-derived plugin names — and that must be sanitized before they reach a terminal.

The Text type and why it is the enforcement

A bare `string` field gives a future print site no way to know its value is hostile-controlled: PR #100 / issue #93 hardened ~24 print sites by wrapping each in Sanitize, but nothing stopped the *next* Fprintf from printing a fetched id raw and silently reintroducing the escape-injection class. Text closes that gap structurally:

  • Text implements fmt.Stringer, and its String() returns the SANITIZED value. Because fmt's %s/%v/%q invoke String() for any value implementing fmt.Stringer, a Text printed through fmt.Fprint* / fmt.Sprintf is sanitized BY DEFAULT. A new print site of an untrusted field therefore cannot reintroduce the #93 class by accident — the type sanitizes itself.

  • The only way to obtain the raw, UNSANITIZED bytes is the explicit, deliberately-alarming Unverified() method. Every call is a greppable acknowledgement that the caller is bypassing display sanitization; it is for non-display logic only (filesystem paths, map keys, comparisons, re-serialization), never for building terminal output.

Text is a defined string type (not a struct) on purpose: it serializes transparently through encoding/json and go-toml/v2 (a named string kind, so `omitempty` still elides an empty value and round-trip fidelity is preserved) and keeps the `==`/`!=`/`<` operators and map-key usability the canonical model relies on. The residual that a defined string type permits — a deliberate `string(t)` conversion to defeat the Stringer, then a raw print — is the same shape of *accepted residual* documented for secrets.Resolved (the lint fence there is likewise defeatable by a deliberate two-step). No innocent print produces it; Unverified() is the blessed, named unwrap.

WRAPPED-ERROR HAZARD: sanitizing a path operand (`%s` + Wrap) beside a `%w` of the raw os error is NOT enough — a *fs.PathError's own Error() re-embeds the raw, unsanitized path right after your sanitized copy. Unwrap to the inner errno first (see secrets.pathlessErr; promote it here on a second call site) so the chain survives without the raw path.

Machine vs terminal contract

MarshalText/JSON is NOT overridden: a Text marshals to its RAW value. That is deliberate — the `--json` surfaces are a machine contract where the consumer owns escaping, exactly as the per-site comments in the CLI already state. Raw in JSON, sanitized on the terminal, with no extra plumbing, falls out of the named-string design for free.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Join

func Join(ts []Text, sep string) string

Join concatenates the SANITIZED form of each Text with sep between elements — the Text-slice analogue of strings.Join, for building one display string from several untrusted values (e.g. the comma-separated plugin list in the status/doctor "undeclared native plugins" note). Each element is sanitized via String(), so the result is safe to print directly; do NOT wrap it in a second Sanitize. sep is trusted caller-supplied text and is emitted verbatim.

func Sanitize

func Sanitize(s string) string

Sanitize strips control characters and deceptive format runes from a string so untrusted text — a fetched marketplace plugin's id, a plugin-supplied component name — can be rendered to a terminal without smuggling escape sequences or spoofed/hidden text through it. It removes:

  • C0 controls (0x00–0x1F, which includes ESC 0x1B, CR, LF, TAB, BEL, and backspace), DEL (0x7F), and C1 controls (0x80–0x9F). Neutralizing the ESC/CSI introducer is the security-relevant part: a name like "\x1b[31mX" renders as the inert literal "[31mX" rather than recoloring the terminal, clearing the screen, or spoofing subsequent rows.
  • The explicit Unicode bidirectional formatting controls — the embedding/ override set U+202A–U+202E (LRE/RLE/PDF/LRO/RLO) and the isolate set U+2066–U+2069 (LRI/RLI/FSI/PDI). These are the "Trojan Source" (CVE-2021-42574) class: U+202E and friends can visually reorder a plugin id so it reads as a trusted name while its bytes say otherwise.
  • Zero-width / invisible format runes — U+200B–U+200D (ZWSP/ZWNJ/ZWJ) and U+FEFF (zero-width no-break space / BOM) — which can hide characters or invisibly pad a name.

Printable text passes through unchanged, including non-ASCII letters and ordinary spaces. This deliberately leaves *implicit* bidi alone: ordinary right-to-left scripts (Arabic, Hebrew) and CJK get their direction from the letters themselves, not from the explicit override controls, so a legitimate non-Latin name survives byte-for-byte — only the explicit formatting controls an attacker would inject are removed. Apply Sanitize at the display boundary, before width/Pad calculation, so a stripped rune never throws off column alignment. (Text.String() applies it; ui.Sanitize delegates here for the composite/string-built display sites that don't hold a Text.)

Scanning is rune-level (not byte-level): byte-level stripping of the 0x80–0x9F range would corrupt legitimate multibyte UTF-8 (a CJK rune's continuation bytes live there). Invalid UTF-8 is normalized rather than passed verbatim — a malformed byte decodes to U+FFFD and is re-emitted as U+FFFD, so a raw 0x80–0x9F byte (a C1 CSI introducer on an 8-bit terminal) can never survive.

What Sanitize does NOT do: it does not normalize display *width*. Combining marks and wide/ambiguous-width runes still skew rune-counting alignment (see ui.Pad); that is a purely cosmetic limitation, not a spoofing vector this function targets.

Types

type Text

type Text string

Text is an untrusted string: marketplace/plugin/native metadata that must be sanitized before terminal display. Print it directly (its String() sanitizes) or reach Sanitize-applied text via String(); use Unverified() only for non-display logic. See the package doc for the full contract.

func Wrap

func Wrap(s string) Text

Wrap tags s as untrusted. Use it at the boundary where a plain string crossing in from outside the trust boundary (a fetched manifest field decoded into a plain string, an adapter-reported component name) first becomes Text.

func (Text) Empty

func (t Text) Empty() bool

Empty reports whether the underlying value is the empty string. Provided so callers read `t.Empty()` instead of comparing against a Text("") literal.

func (Text) String

func (t Text) String() string

String returns the value sanitized for terminal display (control + deceptive format runes stripped). It satisfies fmt.Stringer, so fmt's %s/%v/%q sanitize a Text automatically — the safe-by-default property the type exists for.

func (Text) Unverified

func (t Text) Unverified() string

Unverified returns the RAW, UNSANITIZED bytes. The name is intentionally alarming: every call is an explicit acknowledgement that display sanitization is being bypassed. Use it ONLY for non-display logic (filesystem paths, map keys, comparisons, re-serialization) — never to build terminal output. To render a Text, print it directly or call String().

Jump to

Keyboard shortcuts

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