Server Side Template Injection in Adonisjs with Mutual Tls
Server Side Template Injection in Adonisjs with Mutual Tls
Server Side Template Injection (SSTI) in AdonisJS occurs when an attacker can control template input that is later rendered without proper escaping, enabling arbitrary code execution within the template context. AdonisJS uses its own edge-based view engine, and like other template engines, it can be vulnerable if dynamic values are injected into templates via unsanitized parameters or compromised template includes. SSTI in this framework typically maps to the OWASP API Top 10 A03:2021 — Injection, and can lead to remote code execution, data exfiltration, or host compromise.
Mutual Transport Layer Security (mTLS) adds client certificate authentication on top of standard TLS, ensuring both the client and server cryptographically prove their identities. While mTLS strengthens channel-level authentication and helps prevent unauthorized clients from reaching API endpoints, it does not inherently protect against application-layer vulnerabilities such as SSTI. A common exposure pattern arises when mTLS-protected endpoints accept user-controlled input (e.g., query parameters, JSON fields, or headers) and pass that input directly to edge templates. Even with verified client certificates, an authenticated client—possibly compromised or malicious—can supply payloads designed to exploit template parsing behavior.
Consider an AdonisJS route that uses mTLS and renders a dynamic template based on an identifier supplied by the client:
// routes.ts
import Route from '@ioc:Adonis/Core/Route'
Route.get('/profile/:username', async ({ params, view }) => {
return view.render('profile', { username: params.username })
})
If the username parameter is interpolated unsafely within an edge template like resources/views/profile.edge:
@if(username)
Welcome, ${username}!
@endif
an attacker could supply ${return Eval('globalThis.process')} (or framework-specific equivalents) to achieve code execution. The mTLS handshake completes successfully because the client presents a valid certificate, but the application fails to sanitize input before template rendering. Because mTLS is often used in high-trust environments (e.g., microservice mesh or API gateways), developers may mistakenly assume that all traffic behind mTLS is safe, inadvertently increasing the attack surface for SSTI.
Additionally, if the application uses partials or includes that incorporate user input to determine template paths, an attacker might leverage path traversal or template injection to read arbitrary files or execute functions. The interplay of mTLS assurance and insufficient input validation creates a scenario where trust in the channel does not equate to safety in the logic layer. Detection by middleBrick’s checks includes identifying unescaped user data flowing into view rendering paths and verifying that no client-supplied values dictate template structure or execution flow.
Mutual Tls-Specific Remediation in Adonisjs
Remediation focuses on strict input validation, output encoding, and architectural separation between authentication and rendering logic. Even with mTLS enforcing client identity, user-controlled data must never directly influence template structure or code execution. Apply the following concrete fixes in AdonisJS.
1. Validate and sanitize all client-supplied input
Treat data from authenticated mTLS clients as untrusted. Use schema validation to enforce strict formats for identifiers, paths, and parameters. For example, usernames should match an alphanumeric pattern and avoid special characters used in template syntax.
// app/Validators/ProfileValidator.ts
import { schema } from '@ioc:Adonis/Core/Validator'
export const profileSchema = schema.create({
username: schema.string({}, [rules.alpha(), rules.minLength(3), rules.maxLength(50)])
})
Apply the validator in your route handler:
// routes.ts
import Route from '@ioc:Adonis/Core/Route'
import ProfileValidator from 'App/Validators/ProfileValidator'
Route.get('/profile/:username', async ({ params, request, view }) => {
const payload = await request.validate({ schema: ProfileValidator })
return view.render('profile', { username: payload.username })
})
2. Use safe rendering practices and context-aware escaping
Edge templates should escape dynamic content by default. Avoid embedding raw user input into JavaScript strings or HTML attributes without proper encoding.
@if(username)
Welcome, ${username.escape()}!
@endif
For complex objects, prefer explicit serialization on the server rather than passing raw data to templates.
3. Avoid dynamic template selection based on client input
Do not allow client data to determine which template file to render. Use a fixed mapping or whitelist if variations are necessary.
// routes.ts — unsafe
Route.get('/view/:template', async ({ params, view }) => {
return view.render(params.template)
})
// routes.ts — safer
Route.get('/view/dashboard', async ({ view }) => {
return view.render('templates/dashboard')
})
4. Enforce mTLS without relaxing business logic checks
mTLS should be treated as authentication, not authorization or input safety. Continue applying the same validation and encoding rules you would use for non-mTLS endpoints.
// Example TLS setup in start/server.ts (conceptual)
import { defineConfig } from '@adonisjs/core/app'
export default defineConfig({
https: {
certEnabled: true,
requestCert: true,
rejectUnauthorized: true
}
})
5. Monitor and test with security tooling
Use scanners like middleBrick to detect SSTI indicators even behind mTLS. Its checks examine data flows between authenticated requests and rendering paths, ensuring that verified identity does not mask injection risks.