Formula Injection in Chi with Jwt Tokens
Formula Injection in Chi with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Formula Injection occurs when user-controlled data is interpreted as a formula by spreadsheet or document processing libraries, causing unintended evaluation. When this vulnerability intersects with JWT token handling in Chi, a web framework for the Elixir ecosystem, the risk pattern changes because tokens often contain structured payloads that may be transformed or logged.
In Chi, developers commonly parse and validate JWT tokens using libraries such as joken or guardian. If a token’s payload (e.g., claims such as email, roles, or session identifiers) is later embedded into generated spreadsheets, CSVs, or documents without proper sanitization, an attacker-controlled claim can be interpreted as a formula. For example, an email claim like =cmd|' /C calc'!A0 stored in a user profile and later written into an exported Excel file could trigger arbitrary command execution on the server when the file is opened.
This becomes a JWT-specific concern because tokens are often base64-encoded JSON with a signature, and developers may mistakenly trust the decoded payload without validating or escaping its contents. If a Chi application decodes a JWT, extracts a field such as name, and uses it directly in a spreadsheet generation routine (e.g., via scrivener_ecto or elixir_excel), unescaped equals signs or structured injection patterns can lead to formula injection. The attack surface is not the token signature verification itself, but the downstream handling of token claims in report generation, export features, or administrative dashboards where data from the token is rendered into files that are later consumed by end users.
Additionally, attackers may attempt to inject formulas through token metadata if a Chi API accepts query parameters that influence which token claims are included in exported data. For instance, a parameter such as ?fields=name,email that maps directly to JWT payload fields could be abused to include malicious payloads if the application does not strictly whitelist allowed fields. The intersection of JWT usage and document generation increases the importance of validating, encoding, and sanitizing any data originating from the token before it reaches spreadsheet or document processing code.
Real-world patterns include using JWT claims in CSV exports where a leading equals sign triggers formula evaluation in spreadsheet software, or embedding tokens’ session identifiers into HTML exports that are later opened in Office applications. Because tokens are designed for secure transport and authentication, developers may overlook the need to treat their contents as untrusted input when used in reporting or export workflows.
Jwt Tokens-Specific Remediation in Chi — concrete code fixes
Remediation focuses on strict validation, output encoding, and separation of concerns between authentication and data rendering. Never directly embed JWT payload fields into files or documents without sanitization. Use allowlists for expected fields and encode values according to the target format (e.g., CSV, Excel, HTML).
Example 1: Validating and sanitizing JWT claims before export in Chi
defmodule MyApp.ReportGenerator do
import NimbleCSV.RFC4180, only: [encode_row: 1]
@allowed_fields ~w(email name role)a
def build_csv(claims) do
# Whitelist only safe fields from the JWT claims
sanitized = @allowed_fields
|> Enum.map(&Map.get(claims, &1, ""))
|> Enum.map(&sanitize_cell/1)
# Generate safe CSV rows
[ ["Email", "Name", "Role"] | [sanitized] ]
|> NimbleCSV.RFC4180.encode_to_iodata()
end
defp sanitize_cell(value) when is_binary(value) do
# Remove leading equals signs and trim dangerous characters
cleaned = String.replace_prefix(value, "=", "'")
String.trim(cleaned)
end
defp sanitize_cell(_), do: ""
end
Example 2: Secure JWT verification and controlled data exposure in Chi router
defmodule MyAppWeb.ApiController do
use MyAppWeb, :controller
import MyAppWeb.Auth, only: [current_user: 1]
# Only expose explicitly allowed fields from the JWT
def export_user_data(conn, _params) do
claims = current_user(conn)
|> Map.take(["email", "name", "role"])
csv_data = MyApp.ReportGenerator.build_csv(claims)
send_resp(conn, 200, csv_data)
end
end
Example 3: Using Joken for safe token validation and claim extraction
defmodule MyApp.Auth do
use Joken.Config
def current_user(conn) do
with [token] <- Plug.Conn.get_req_header(conn, "authorization"),
{:ok, claims} <- verify_and_validate(token) do
claims
else
_ -> %{}
end
end
defp verify_and_validate(token) do
# Configure validator with your secret and expected claims
default_claims()
|> Joken.Config.add_claim("email", & &1, &is_binary/1)
|> Joken.Config.add_claim("role", & &1, &is_binary/1)
|> Joken.verify_and_validate(token, Joken.Signer.create("HS256", "your-secret"))
end
end
Example 4: Escaping formulas in Excel generation with XlsxWriter
defmodule MyApp.ExcelReport do
use XlsxWriter.Workbook
def generate(claims) do
workbook = XlsxWriter.Workbook.new("report.xlsx")
worksheet = XlsxWriter.Workbook.add_worksheet(workbook)
email = Map.get(claims, "email", "")
# Prefix single quotes to prevent formula interpretation
safe_email = if String.starts_with?(email, "="), do: "'" <> email, else: email
worksheet.write_string(0, 0, safe_email)
XlsxWriter.Workbook.close(workbook)
end
end
Example 5: Enforcing field allowlists in Chi pipeline
defmodule MyAppWeb.Plugs.SanitizeJwtClaims do
import Plug.Conn
@allowed ~w(sub email role iat exp)a
def init(opts), do: opts
def call(conn, _opts) do
case get_session(conn, :jwt_claims) do
nil -> conn
claims ->
sanitized = Enum.reduce(@allowed, %{}, fn key, acc ->
if Map.has_key?(claims, key) do
Map.put(acc, key, sanitize_value(claims[key]))
else
acc
end
end)
assign(conn, :safe_claims, sanitized)
end
end
defp sanitize_value(value) when is_binary(value) do
String.replace_prefix(value, "=", "'")
end
defp sanitize_value(value), do: value
end