Server Side Template Injection in Fiber with Firestore
Server Side Template Injection in Fiber with Firestore — how this specific combination creates or exposes the vulnerability
Server Side Template Injection (SSTI) occurs when user-controlled data is inserted into a template function that causes arbitrary code execution on the server. In a Fiber application using Firestore, this typically arises when dynamic values from Firestore documents or request parameters are passed into template rendering without proper escaping or sandboxing. For example, if a handler retrieves a user document from Firestore and passes fields such as username or bio into a template context, an attacker who can influence document content (for example, via compromised admin tooling or exported data) may inject template directives.
Consider a route that renders a profile page using Firestore data:
import { render } from "https://deno.land/x/[email protected]/mod.ts";
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.6.1/firebase-app.js";
import { getFirestore, doc, getDoc } from "https://www.gcstatic.com/firebasejs/9.6.1/firebase-firestore.js";
const app = initializeApp({ /* config */ });
const db = getFirestore(app);
app.get("/profile/:uid", async (c) => {
const uid = c.params.uid;
const docSnap = await getDoc(doc(db, "users", uid));
if (!docSnap.exists()) {
return c.status(404).send("Not found");
}
const data = docSnap.data();
// Potentially unsafe: data fields used directly in template
return await render(`Hello #{data.displayName}`, data);
});
If data.displayName contains a template expression such as #{2*2} or a more dangerous payload compatible with the template engine (Pug, in this case), the server may evaluate it. The combination of Fiber’s lightweight routing, Firestore’s flexible document model, and dynamic template rendering increases risk when input validation and output escaping are omitted. Attackers can leverage this to read server-side files, execute commands, or probe internal services via SSRF if the template engine permits arbitrary function calls.
Additionally, because Firestore documents can contain nested maps and arrays, developers might recursively merge entire documents into the template context. This expands the attack surface: nested fields may include values that, when rendered by an unsafe template engine, trigger logic branches or filters that lead to code execution. MiddleBrick’s scans detect such patterns by correlating OpenAPI specifications with runtime behavior, noting places where Firestore document fields enter template rendering without sanitization.
Firestore-Specific Remediation in Fiber — concrete code fixes
To mitigate SSTI when using Fiber with Firestore, ensure that template rendering contexts are strictly controlled and that Firestore data is treated as untrusted input. Prefer explicit allowlists for which fields are passed to the template, and use escaping functions provided by the template engine. Below are concrete, secure patterns.
1. Restrict template context to selected safe fields
Instead of passing the entire Firestore document, explicitly pick safe scalar values:
import { render } from "https://deno.land/x/[email protected]/mod.ts";
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.6.1/firebase-app.js";
import { getFirestore, doc, getDoc } from "https://www.gstatic.com/firebasejs/9.6.1/firebase-firestore.js";
const app = initializeApp({ /* config */ });
const db = getFirestore(app);
app.get("/profile/:uid", async (c) => {
const uid = c.params.uid;
const docSnap = await getDoc(doc(db, "users", uid));
if (!docSnap.exists()) {
return c.status(404).send("Not found");
}
const data = docSnap.data();
// Safe: only explicitly allowed fields are used
const context = {
displayName: data.displayName || "",
email: data.email || "",
};
return await render("Hello #{displayName}", context);
});
2. Use template engine auto-escaping and avoid eval-like features
Configure your template engine to escape dynamic content. For Pug, ensure you use != only for trusted HTML and #{} for text (which auto-escapes). Avoid filters or includes that enable code execution. If you must include dynamic partials, validate the partial name against an allowlist.
3. Validate and sanitize Firestore data before rendering
Apply schema validation (for example with Zod) to Firestore documents and reject documents containing unexpected keys or executable patterns:
import { z } from "https://deno.land/x/[email protected]/mod.ts";
const UserProfileSchema = z.object({
displayName: z.string().min(1).max(120),
email: z.string().email(),
// Do not include fields that templates should not use
});
type UserProfile = z.infer<typeof UserProfileSchema>;
app.get("/profile/:uid", async (c) => {
const uid = c.params.uid;
const docSnap = await getDoc(doc(db, "users", uid));
if (!docSnap.exists()) {
return c.status(404).send("Not found");
}
const parsed = UserProfileSchema.safeParse(docSnap.data());
if (!parsed.success) {
return c.status(400).send("Invalid document");
}
const safe = parsed.data;
return await render("Hello #{displayName}", { displayName: safe.displayName });
});
4. Disable dangerous template features
If your template engine supports functions, filters, or includes, disable them or restrict them to a safe subset. For Pug, avoid custom filters and do not enable pretty or compileDebug in production-like environments when handling untrusted data.
By combining strict field selection, schema validation, and safe rendering defaults, you reduce the risk that Firestore data can influence template behavior in harmful ways. MiddleBrick’s checks highlight where Firestore document fields enter template contexts without these safeguards.