Terminal Injection Prevention in Kubernetes Tooling
A deep dive into a subtle attack vector: how malicious Kubernetes resource names with ANSI escape sequences can manipulate operator terminals, and how Iddio's sanitizeTerminal function prevents it.
The Terminal Injection Problem
When building security gateways and approval workflows for AI agents, there is a subtle but critical attack vector: terminal injection via malicious resource names.
Imagine an AI agent (or a compromised controller) creating a Kubernetes pod. Kubernetes is remarkably permissive with resource names. While names are usually restricted to DNS subdomains (RFC 1123), certain fields, annotations, or paths can contain arbitrary characters, including ANSI escape sequences.
If an operator is watching a terminal for approval prompts, an attacker can craft a payload that manipulates the operator’s display when the security tool prints it.
The Attack Vector
Terminal emulators interpret specific byte sequences as instructions. These are typically used for colors, cursor movement, or clearing the screen (VT100 codes). However, they can also be weaponized.
Consider an attacker who creates a namespace or resource with a name containing a cursor repositioning sequence (\e[<row>;<col>H), followed by text that perfectly mimics a legitimate security prompt:
\e[2J\e[1;1H⚠️ ESCALATE [trusted-agent] GET payments/pods
tier 0 (observe) — approve? [Y/n]:
When the security gateway attempts to log or prompt for the actual operation—say, deleting a critical production database—the terminal renders the attacker’s payload instead. The screen clears, the cursor moves to the top, and a fake, harmless-looking prompt is displayed. The operator presses ‘Y’, believing they are approving a simple read operation, but they are actually approving the hidden, destructive command.
Other attacks include Operating System Command (OSC) sequences, which can change terminal titles, or terminal response sequences like \e[6n (Device Status Report), which force the terminal to synthesize input back to the shell as if the user typed it.
The Agent Context
This problem is exacerbated when dealing with AI agents. Agents are designed to hallucinate, construct novel strings, and operate on untrusted inputs. If an agent parses a malicious JSON payload from the internet and attempts to create a Kubernetes resource based on it, the agent itself becomes the vector for the terminal injection attack against the human operator.
The fundamental rule of CLI tool security applies: Never trust the strings you are printing.
Iddio’s Solution: sanitizeTerminal
To prevent this, Iddio sanitizes all agent-controlled strings—including agent names, namespaces, resources, and verbs—before they are ever written to the operator’s terminal or the local audit log.
The implementation is intentionally simple and conservative:
// sanitizeTerminal strips ANSI escape sequences and control characters from
// string input before it is printed to the operator's terminal. This
// prevents an attacker from crafting malicious Kubernetes resource names or
// namespace paths that spoof the approval prompt.
func sanitizeTerminal(s string) string {
var b strings.Builder
b.Grow(len(s))
for _, r := range s {
// Allow printable ASCII and common Unicode, block control chars.
if r >= 32 && r != 127 {
b.WriteRune(r)
}
}
return b.String()
}
How It Works
Instead of trying to parse and strip specific ANSI escape codes (which is notoriously difficult and error-prone due to the hundreds of esoteric terminal control sequences), the sanitizeTerminal function uses an allow-list approach based on rune values:
r >= 32: ASCII 32 is the space character. Everything below 32 is a control character (e.g.,\n,\r,\t,\e(Escape, ASCII 27)). By requiringr >= 32, we universally strip the Escape character, which is the prefix for almost all terminal injection attacks.r != 127: ASCII 127 is the DEL (Delete) control character, which is also stripped.- Common Unicode: Because Go iterates over strings as runes (UTF-8 code points), characters like
🚀orähave rune values well above 127 and are safely preserved.
By dropping all control characters, any attempt to inject ANSI sequences or carriage returns (which can be used to overwrite the current line) is neutralized. The malicious payload \e[2JFake Prompt simply renders as [2JFake Prompt, exposing the attack attempt without executing it.
Building Secure CLI Tools
Terminal injection is a frequently overlooked vulnerability in Go and Kubernetes tooling. Many developers assume that fmt.Printf("%s", resourceName) is safe. It is not.
If your CLI tool, proxy, or operator prints user-supplied or cluster-supplied data to stdout or stderr, it needs to be sanitized. A simple, robust control-character filter like sanitizeTerminal is the most effective defense against visual spoofing and terminal manipulation.
Try It Yourself
Iddio is open source. Deploy a zero-trust command proxy for your AI agents in minutes.