Log Injection in Chi with Firestore
Log Injection in Chi with Firestore — how this specific combination creates or exposes the vulnerability
Log injection occurs when untrusted input is written directly into log entries, allowing an attacker to forge log lines, obscure true events, or trigger log-based attacks such as log forging or log poisoning. In a Chi application that uses Firestore as a backend, this risk arises when request data—such as user IDs, query parameters, or custom identifiers—are interpolated into log messages without sanitization before being written to Firestore documents or queried as log entries.
Chi is a lightweight, composable router for Clojure web applications. When you log request context (e.g., for debugging or audit trails) and store those logs in Firestore, each log entry may include user-controlled fields. If these fields contain newline characters or structured payload markers, an attacker can inject additional log lines or metadata. For example, a crafted query parameter containing a newline and fabricated key-value pairs can result in multiple log entries when the Firestore document is later read, breaking log integrity and complicating incident response.
Because Firestore stores documents as JSON, newline characters in string fields are preserved. When log consumers parse these documents line-by-line or assume one document equals one log event, injected newlines cause misalignment. This can lead to misinterpretation by monitoring tools or SIEMs that rely on deterministic log structure. Additionally, if log entries include Firestore document IDs or paths that reflect user input, an attacker may manipulate these values to reference sensitive documents or trigger path traversal in log viewers that render links to documents.
Consider a Chi handler that logs a map containing user input before writing to Firestore:
(defn log-and-store [db request-params]
(let [doc-id (str "audit-" (:user-id request-params))
entry {:timestamp (java.time.Instant/now)
:user-id (:user-id request-params)
:action (:action request-params)
:details (:details request-params)}]
;; Potential log injection if :details contains newlines or structured text
(println "AUDIT" entry)
(-> db
(firestore/collection "audit_logs")
(firestore/document doc-id)
(firestore/create! entry))
{:status 200}))
If :details contains a newline followed by JSON-like text, the printed log line may appear as a single entry while the Firestore document preserves the raw string. Downstream log parsers that split on newlines will treat the injected text as a separate event. Moreover, if :user-id or the generated document ID is used in log paths without validation, an attacker can influence document references, potentially aiding in reconnaissance or malicious linking.
In summary, the combination of Chi for request routing, Firestore for log persistence, and insufficient input validation or encoding creates conditions where log injection can undermine log reliability and observability. Mitigations focus on sanitizing and encoding user-controlled data before it reaches structured logging and Firestore writes.
Firestore-Specific Remediation in Chi — concrete code fixes
To prevent log injection when using Chi and Firestore, treat all user-controlled data as untrusted. Apply canonicalization and encoding before logging and before storing in Firestore. Below are concrete, idiomatic examples that demonstrate safe practices.
1. Sanitize and structure log data explicitly
Instead of interpolating raw maps into log strings, construct a deterministic log object and encode potentially dangerous characters. For Firestore, store separate fields rather than embedding newlines in a single string field.
(defn sanitize-input [s]
;; Remove or replace control characters that can break log parsing
(if (string? s)
(-> s
(str/replace #"\r\n" " ")
(str/replace #"\n" " ")
(str/replace #"\r" " "))
s))
(defn safe-log-and-store [db request-params]
(let [user-id (sanitize-input (:user-id request-params))
action (sanitize-input (:action request-params))
details (sanitize-input (:details request-params))
entry {:timestamp (java.time.Instant/now)
:user-id user-id
:action action
:details details}]
;; Safe: structured Firestore document; no injected newlines in single fields
(-> db
(firestore/collection "audit_logs")
(firestore/document (str "audit-" user-id))
(firestore/create! entry))
;; Safe: single-line log output
(println (pr-str entry))
{:status 200}))
2. Use Firestore transaction or batched writes with normalized data
When logging multiple related events, use batched writes and ensure each field is normalized. This prevents partial writes and keeps logs atomic from the perspective of log consumers.
(defn batch-audit-logs [db entries]
(let [batch (.batch db)]
(doseq [entry entries]
(let [doc-ref (-> db
(firestore/collection "audit_logs")
(firestore/document (str "audit-" (:user-id entry))))]
(.set batch doc-ref entry)))
(.commit batch)))
;; Example usage with sanitized entries
(let [entries [(safe-make-entry params1)
(safe-make-entry params2)]]
(batch-audit-logs db entries))
3. Encode newlines when you must store multiline details
If you need to preserve multiline details, store them in a structured format (e.g., base64 or JSON string) and decode only when necessary. Avoid raw newlines in string fields that may be read line-by-line by log processors.
(defn encode-details [details]
(when details
(Base64/getEncoder (encodeToString (str/utf8-bytes details)))))
(defn stored-entry [request-params]
{:timestamp (java.time.Instant/now)
:user-id (sanitize-input (:user-id request-params))
:action (sanitize-input (:action request-params))
:details (encode-details (:details request-params))})
By normalizing inputs, structuring log data, and avoiding raw newlines in string fields, you reduce the risk of log injection while maintaining compatibility with Firestore and Chi-based services.