Header Injection in Chi with Jwt Tokens
Header Injection in Chi with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Header Injection in the context of Chi and JWT tokens occurs when untrusted input is allowed to influence HTTP response headers after a JWT has been validated or used for routing. Chi is a lightweight HTTP router for Clojure, and while it does not manipulate JWTs natively, developers commonly integrate JWT parsing directly into Chi routes. If user-controlled data—such as request headers, query parameters, or JSON body fields—is interpolated into values that later set headers, an attacker can inject arbitrary header lines like X-Content-Type-Options or Set-Cookie. This can bypass intended security policies or split responses, leading to response smuggling or header manipulation.
When JWT tokens are involved, the risk often arises from logging, error reporting, or dynamic header assignment based on claims. For example, if a route reads a sub or origin claim and uses it to construct a custom header without strict validation, an attacker who supplies a malicious JWT with crafted claims can control those headers. Consider a handler that builds a tracing header from a JWT claim:
(defn trace-handler [request]
(let [token (some-extract-token request)
claims (jwt/decode-token token public-key {:alg "RS256"})
user-id (:sub claims)]
{:status 200
:headers {"X-User-Id" user-id}
:body "ok"}))
If user-id contains newline or carriage return characters supplied via the JWT, Chi may emit multiple headers under the same name, enabling an attacker to inject a Location: or Set-Cookie: header that follows the intended headers. This specific combination—Chi’s route-driven header assembly plus JWT-derived data—creates a pathway for Header Injection that may not be apparent when JWTs are treated as opaque identifiers.
Moreover, error paths that expose JWT validation failures can also leak information or enable injection. For instance, returning the raw token or a malformed claim in a header during debugging increases the attack surface. Even if the token itself is cryptographically valid, the way its claims are used to set headers matters. Attackers do not need to break the signature; they only need to supply a JWT whose claims influence header values in an uncontrolled way.
Because Chi does not enforce header naming conventions or sanitize values derived from JWT claims, developers must treat all such values as untrusted. The vulnerability is not in JWT parsing, but in the integration pattern where dynamic header construction lacks canonicalization and strict allow-listing. This aligns with broader API risks around input validation and improper handling of untrusted data, which are routinely covered in middleBrick scans that test authentication, input validation, and header-related checks in parallel.
Jwt Tokens-Specific Remediation in Chi — concrete code fixes
To remediate Header Injection when using JWT tokens in Chi, focus on strict validation, output encoding, and separation of concerns. Never directly place JWT claims into HTTP headers without normalization and allow-listing. Instead, map claims to known, safe header values or sanitize newlines and control characters.
1) Validate and sanitize claim-derived header values:
(defn sanitize-header-value [value]
;; Remove or replace newlines and carriage returns
(clojure.string/replace value #"[\r\n]" ""))
(defn safe-handler [request]
(let [token (some-extract-token request)
claims (jwt/decode-token token public-key {:alg "RS256"})
user-id (:sub claims)]
{:status 200
:headers {"X-User-Id" (sanitize-header-value user-id)}
:body "ok"}))
2) Use a controlled mapping instead of dynamic claim-to-header passthrough:
(def allowed-header-mappings #{:x-request-id :trace-id})
(defn header-from-claim [claim-key]
(when (allowed-header-mappings claim-key)
(case claim-key
:x-request-id "X-Request-Id"
:trace-id "X-Trace-Id")))
(defn mapped-handler [request]
(let [token (some-extract-token request)
claims (jwt/decode-token token public-key {:alg "RS256"})
header-name (header-from-claim :x-request-id)]
(if header-name
{:status 200
:headers {header-name "fixed-or-validated-value"}
:body "ok"}
{:status 400 :body "unsupported claim"})))
3) Reject tokens with unexpected characters in claims used for headers, and log safely without exposing raw tokens in headers:
(defn validate-no-newline [value field]
(when (re-find #"[\r\n]" value)
(throw (ex-info (str "Invalid newline in " field)
{:field field :value value}))))
(defn validated-handler [request]
(let [token (some-extract-token request)
claims (jwt/decode-token token public-key {:alg "RS256"})
user-id (:sub claims)]
(validate-no-newline user-id "sub")
{:status 200
:headers {"X-Safe-User-Id" (sanitize-header-value user-id)}
:body "ok"}))
These patterns emphasize defense in depth: canonicalize input, avoid concatenation that enables line splitting, and prefer static mappings over dynamic header names derived from JWT claims. When combined with middleBrick’s checks for input validation and header-related behavior, these practices reduce the likelihood of Header Injection in Chi services that rely on JWT tokens.