OWASP LLM05: Preventing Improper Output Handling at the CDN Layer
Large language models generate text that applications render directly to users, embed in API responses, or pass to downstream systems. When that generated text contains HTML tags, script payloads, SQL fragments, or system commands, applications that trust and render the output without sanitization create injection vectors identical to the ones the security community spent two decades learning to prevent. OWASP classifies this as LLM05: Improper Output Handling — a vulnerability category unique to AI-integrated applications where the model itself becomes the source of untrusted content. For teams running AI services behind a CDN security layer, the edge provides a critical interception point where WAF rules and response filters can catch malicious output before it reaches the client.
The Attack Surface: How LLM Output Becomes a Weapon
Traditional web security treats user input as untrusted and server output as trusted. AI applications break this assumption. The LLM's output is generated by a model that processes untrusted input — user prompts, retrieved documents, tool call results — and produces text that may contain executable content. An attacker who controls any part of the model's input context can attempt to manipulate the output.
The attack chain works through prompt injection. An attacker crafts input that causes the LLM to generate specific output patterns: a chat response containing <script> tags that execute when rendered as HTML, a code generation response containing SQL injection payloads that execute when the application runs the generated code, or an API response containing markdown that renders as clickable links pointing to phishing sites. The AI agent authorization layer can limit what the model does, but output sanitization addresses what the model says.
The CDN edge is uniquely positioned for output filtering because it processes every response before it reaches the client. Unlike application-level filtering that developers must implement correctly in every code path, edge functions apply sanitization rules uniformly across all response paths. A single misconfigured API endpoint at the origin cannot bypass edge-level filtering.
Response Content Type Classification
Effective output filtering requires understanding what the response contains and how the client will interpret it. The Content-Type header determines the sanitization strategy. HTML responses need tag stripping and attribute filtering. JSON API responses need string value encoding. Plain text responses need minimal processing but should still be scanned for PII patterns. Streaming responses require chunk-level processing.
| Metric | Edge Processing | Origin Processing | Improvement |
|---|---|---|---|
| Latency (p50) | 8–15 ms | 120–300 ms | 15–20x faster |
| Cache Hit Ratio | 85–95% | N/A | Reduces origin load |
| TLS Handshake | 1–5 ms (nearby PoP) | 50–150 ms | 10–30x faster |
| Bandwidth Cost | $0.01–0.04/GB | $0.05–0.12/GB | 3–5x cheaper |
| Availability | 99.99% (multi-PoP) | 99.9% (single DC) | 10x fewer outages |
The edge node classifies each response into one of four processing modes based on the Content-Type header and the response structure:
// CDN edge output classification
response_classification:
html_document:
content_type: "text/html"
sanitize: strip_unsafe_tags, encode_attributes, remove_event_handlers
scan: pii_patterns, credential_patterns
json_api:
content_type: "application/json"
sanitize: encode_html_in_strings, validate_structure
scan: pii_patterns, sql_fragments
sse_stream:
content_type: "text/event-stream"
sanitize: per_chunk_encoding, validate_event_format
scan: pii_patterns (rolling window)
plain_text:
content_type: "text/plain"
sanitize: none (served as-is)
scan: pii_patterns, credential_patterns
HTML Output Sanitization at the Edge
When the origin returns HTML content that includes LLM-generated text, the edge node must ensure that the generated content cannot break out of its intended rendering context. The primary risk is that the LLM generates text containing HTML tags that the browser interprets as markup rather than displaying as text.
The edge sanitizer maintains an allowlist of HTML elements and attributes that the application legitimately uses in LLM output sections. Elements like <p>, <strong>, <em>, and <code> may be allowed for formatted responses. Elements like <script>, <iframe>, <object>, <embed>, and <form> are always stripped. Event handler attributes (onclick, onerror, onload) are removed from any element. The architecture pattern enforces this at the response stream level, processing the HTML as it passes through the edge without buffering the complete response.
A critical subtlety is that HTML sanitization must be context-aware. Content inside a <script type="application/ld+json"> block follows JSON rules, not HTML rules. Content inside a <pre><code> block should preserve angle brackets as visible characters. The edge sanitizer tracks the current parsing context and applies appropriate rules for each section of the response, using a state machine that mirrors the browser's HTML parser.
JSON API Response Filtering
API responses in JSON format present a different challenge. The LLM's generated text lives inside JSON string values, which the client application may render as HTML, insert into DOM elements, or pass to other APIs. The edge cannot know how the client will use each string value, so it applies defensive encoding to all string values in the response.
The API acceleration layer at the edge parses the JSON response and processes each string value through an encoding pipeline. HTML entities are encoded (< becomes <), control characters are escaped, and null bytes are removed. This ensures that even if the client application naively inserts the string into HTML without encoding, the browser will display the content as text rather than interpreting it as markup.
For applications that legitimately include HTML in JSON API responses (such as a chat application that returns formatted messages), the edge can be configured to allow specific HTML patterns within designated JSON fields while sanitizing all other fields. This configuration maps JSON paths (like $.messages[*].content) to specific sanitization profiles, providing granular control over which fields receive which treatment.
PII and Sensitive Data Detection
LLMs can inadvertently leak sensitive information in their responses. A model trained on diverse internet data may reproduce email addresses, phone numbers, API keys, or internal system details when prompted in ways that trigger recall of training data. The edge node scans response content for patterns that match known sensitive data formats.
The monitoring system tracks PII detection events as security telemetry, creating an audit trail of potential data leakage. When the edge detects a potential PII pattern, it can take one of several actions: mask the detected pattern (replacing digits with asterisks), replace it with a placeholder token, log an alert without modifying the response, or block the response entirely and return an error. The action depends on the confidence level of the detection and the sensitivity classification of the matched pattern.
// PII detection patterns at CDN edge
pii_rules:
- pattern: '\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b'
type: email
action: mask // user@ex*****.com
confidence: high
- pattern: '\b\d{3}[-.]?\d{2}[-.]?\d{4}\b'
type: ssn
action: block // block entire response
confidence: high
- pattern: '\b4[0-9]{12}(?:[0-9]{3})?\b'
type: credit_card
action: mask // 4*** **** **** 1234
confidence: high
validate: luhn_check
- pattern: '(?:sk|pk)[-_](?:live|test)[-_][a-zA-Z0-9]{20,}'
type: api_key
action: mask
confidence: high
- pattern: '(?:password|secret|token)\s*[:=]\s*["\x27]?[^\s"]+["\x27]?'
type: credential
action: mask
confidence: medium
Streaming Response Handling
AI applications increasingly use Server-Sent Events to stream LLM responses token by token. This creates a challenge for output sanitization because the edge cannot wait for the complete response before filtering — the entire point of streaming is to deliver partial results immediately. The edge must process each chunk independently while maintaining enough context to detect patterns that span chunk boundaries.
The edge sanitizer maintains a sliding window buffer of the most recent 512 bytes of streamed content. When a new chunk arrives, it is concatenated with the buffer, processed through the sanitization rules, and the safe portion is forwarded to the client. The buffer retains any content that might be part of an incomplete pattern (such as the beginning of an HTML tag that has not yet been closed). This approach adds at most one chunk of latency to the stream while ensuring that multi-chunk attack payloads cannot bypass detection.
For Server-Sent Events specifically, each event is a self-contained unit with a data: prefix. The edge validates that each event conforms to the SSE format and that the data payload passes content sanitization. Malformed events — missing the data: prefix or containing raw binary data — are dropped and logged as potential attack attempts. The edge computing runtime handles this processing within the same response pipeline that handles compression and header modification.
Defense in Depth with Content Security Policy
Output sanitization at the edge is one layer of a defense-in-depth strategy. Content Security Policy headers provide a complementary layer that instructs the browser to restrict what content can execute, regardless of what the HTML contains. A strict CSP with script-src 'none' prevents any inline or external script execution, neutralizing XSS payloads even if they pass through the sanitization layer.
The combination of edge sanitization and strict CSP creates overlapping protection zones. The sanitizer removes known attack patterns from the response body. The CSP prevents execution of patterns that the sanitizer might miss. Zero trust principles dictate that neither layer should be considered sufficient alone — the sanitizer might have a bypass, and CSP alone cannot prevent non-script attacks like CSS injection or HTML form phishing.
Frame protection through the X-Frame-Options header or CSP's frame-ancestors directive prevents clickjacking attacks where an attacker frames the AI application and uses LLM-generated content to create convincing phishing interfaces. The edge enforces these headers on every response, ensuring that the application cannot be embedded in malicious contexts even if the LLM generates content designed to facilitate clickjacking.
Structural Validation for AI Responses
Beyond content sanitization, the edge can validate the structural integrity of AI responses. If the API specification defines that LLM responses should contain specific fields in a specific format, the edge can reject responses that deviate from the expected structure. This catches cases where prompt injection causes the model to generate output that breaks the application's expected response format.
The API gateway at the edge can validate responses against OpenAPI schemas. If the schema specifies that the response.content field should be a string of maximum 4096 characters containing only printable UTF-8 characters, the edge rejects responses that exceed this length or contain control characters. This structural validation catches prompt injection attacks that cause the model to dump system prompts, generate excessively long responses intended to exhaust client resources, or produce binary content embedded in text fields.
For applications using token-authenticated API endpoints, the edge can correlate the request's authorization scope with the expected response format. A request to a summary endpoint should return a short text response, not a multi-kilobyte JSON object containing user records. Scope-response correlation catches privilege escalation attacks where prompt injection causes the model to access and return data beyond the user's authorization level.
Monitoring and Incident Response
The log analysis system tracks every sanitization action as a structured event. Each event includes the rule that triggered, the content that was modified or blocked, the request context (endpoint, user agent, authentication state), and the response context (Content-Type, origin server, response code). This telemetry serves two purposes: real-time alerting on potential attacks and long-term analysis of LLM output quality.
Real-time alerting triggers when sanitization events exceed baseline rates. A sudden spike in HTML tag stripping events on an API endpoint that normally returns plain text suggests an active prompt injection attack. The incident response system can automatically escalate the sanitization level for the affected endpoint, switching from tag stripping to response blocking until the attack is investigated.
Long-term analysis reveals patterns in LLM output quality. If a particular prompt template consistently triggers PII detection, the template needs revision at the application level. The CDN edge provides this visibility without requiring the application team to instrument their own monitoring. The threat intelligence feed aggregates patterns across all protected applications, identifying new prompt injection techniques as they emerge in the wild.
Performance Considerations
Output sanitization must not significantly increase response latency. The edge sanitizer achieves this through several optimizations. The HTML parser operates in streaming mode, processing content as it arrives rather than buffering the complete response. Pattern matching uses compiled finite automata that process each byte exactly once. PII detection uses a bloom filter for fast negative matching, invoking the full regex engine only when the bloom filter indicates a potential match.
For non-streaming responses under 100 KB, the total sanitization overhead is 1 to 3 milliseconds. For streaming responses, the per-chunk overhead is under 0.5 milliseconds. These numbers are within the noise floor of typical CDN performance measurements and well below the variability of LLM inference latency, which typically ranges from 200 milliseconds to several seconds. The origin protection benefits far outweigh this minimal latency cost.
Caching interacts with output sanitization in an important way. Since LLM responses are typically unique per request, they bypass the CDN cache. However, the sanitization rules themselves are cached at the edge in compiled form, avoiding the cost of rule compilation on every request. Rule updates propagate to edge nodes within seconds through the cache invalidation infrastructure, ensuring that new sanitization rules take effect globally without delay.