The recent discussions around prompt injection via web content in the OpenAI Operator context have crystallized a core problem: we are often piping arbitrary, untrusted JSON from external tools directly into an LLM's context window. The operator then acts upon this JSON, potentially leading to privilege escalation or data exfiltration. The sanitization step is therefore a critical control point, but I see many proposals treating it as a simple schema validation, which is insufficient.
We must consider the JSON not as data, but as code that influences the agent's execution path. A robust validation pipeline needs multiple defense layers, moving from syntactic to semantic analysis. The goal is to prevent the embedding of malicious instructions within seemingly valid field values.
My proposed defensive layers are as follows:
* **Layer 1: Structural & Type Validation.** Use a strict, compiled schema validator (e.g., `pydantic` with `strict=True` in Python, `serde` with `deny_unknown_fields` in Rust) at the API boundary. This rejects unexpected fields and coerces types.
* **Layer 2: Lexical Sanitization.** After parsing, recursively traverse the validated object and sanitize all string values. This is not about escaping JSON, but about neutralizing control characters and injection payloads.
* **Layer 3: Content Policy Enforcement.** Apply allow-list or deny-list rules based on the tool's purpose. For a `web_search` tool, does the `query` field contain URLs or attempt to inject template commands? For a `sql_db` tool, does the `query` field contain DDL or multiple statements?
Here is a concrete example of a lexical sanitizer for string values, designed to catch common injection patterns before the string is ever presented in a prompt. This is a starting point, not a complete solution.
```python
import re
from typing import Any
def sanitize_string(value: str) -> str:
"""
Recursively sanitize string values within a structure.
Focus is on removing/neutralizing characters and sequences
that could break prompt context or inject instructions.
"""
# 1. Normalize line endings to prevent obfuscation
value = value.replace('rn', 'n').replace('r', 'n')
# 2. Remove or escape control characters (excluding common whitespace)
# This targets invisible chars, ANSI escapes, and terminal sequences.
value = re.sub(r'[x00-x08x0bx0cx0e-x1fx7f]', '', value)
value = re.sub(r'x1b[[0-9;]*[a-zA-Z]', '', value) # ANSI escapes
# 3. Mitigate potential prompt injection delimiters
# This is highly context-dependent. Example for a markdown-heavy prompt:
injection_patterns = [
r'(?i)ignore previous instructions',
r'(?i)system:s*you are now',
]
for pattern in injection_patterns:
value = re.sub(pattern, '[REDACTED]', value)
# 4. Optional: Truncate length to limit payload size
max_len = 10000
if len(value) > max_len:
value = value[:max_len] + '... [TRUNCATED]'
return value
def deep_sanitize(obj: Any) -> Any:
"""Apply sanitize_string recursively to all strings in a structure."""
if isinstance(obj, dict):
return {k: deep_sanitize(v) for k, v in obj.items()}
elif isinstance(obj, list):
return [deep_sanitize(item) for item in obj]
elif isinstance(obj, str):
return sanitize_string(obj)
else:
return obj # int, float, bool, None pass through
```
The compliance implication is stark: if the operator acts on user-provided credentials (e.g., to access a database), the JSON parsing pipeline becomes part of the trusted computing base. A failure here could lead to the agent being tricked into exfiltrating those credentials via a manipulated `error` field or a crafted `command` field in the JSON response. We must assume the web content is adversarial. The question then becomes: are we applying a sufficiently rigorous, multi-layered filter, akin to a seccomp-bpf policy for data, or are we just checking a box with a basic JSON schema?