Server Side Template Injection in Adonisjs with Cockroachdb
Server Side Template Injection in Adonisjs with Cockroachdb — how this specific combination creates or exposes the vulnerability
Server Side Template Injection (SSTI) in AdonisJS with CockroachDB arises when an application renders user-controlled input as part of a template without proper escaping or sandboxing, and that template interacts with CockroachDB. AdonisJS uses Edge by default; if dynamic expressions embed raw user input into the template logic or queries, an attacker can influence control flow or data access patterns. Because CockroachDB is often accessed via an ORM or query builder, the risk is not direct database injection through templates, but rather template-driven logic that alters query construction or exposes sensitive data.
Consider an endpoint that renders a dashboard using a username supplied via query parameters. If the template conditionally includes sections based on that username without validation, an attacker may probe for template context leakage. For example, injecting payloads like ${''['constructor']['name']} can reveal whether the runtime exposes internal objects. When the template subsequently builds a CockroachDB query using the same input—such as filtering rows by a tenant identifier derived from the template context—the injected logic can change the query’s WHERE clause, leading to unauthorized data access across tenants.
In AdonisJS, templates are compiled and cached; if user input reaches the template layer and influences which database calls are made, the boundary between presentation and persistence blurs. CockroachDB’s SQL compatibility means typical SQL injection techniques apply at the query layer, but SSTI focuses on the template engine’s ability to execute arbitrary logic that affects which queries are sent. This is especially risky when dynamic imports or runtime expressions construct query strings based on template variables. Attack patterns include using template injection to enumerate usernames, trigger error messages that reveal schema details, or manipulate pagination to exfiltrate rows.
An example flow: a route accepts search, passes it to an Edge template that conditionally adds filters to a CockroachDB query. If search contains template syntax, it may execute unintended branches, causing the query builder to omit tenant filters. The resulting SQL issued to CockroachDB could return rows belonging to other users. This highlights the need to treat template input as untrusted and to enforce strict separation between template logic and data access layers.
middleBrick detects such risks by correlating runtime behavior with OpenAPI specs and unauthenticated probing. It flags where templates handle untrusted data that influences database interactions, providing remediation guidance that emphasizes input validation and context-aware escaping rather than attempting to fix the database layer directly.
Cockroachdb-Specific Remediation in Adonisjs — concrete code fixes
To prevent SSTI when using CockroachDB in AdonisJS, ensure user input never reaches the template engine in a way that can alter control flow or query construction. Use strict validation, context-aware escaping, and parameterized queries.
- Validate and sanitize all inputs before they reach views. Use AdonisJS schema validation to enforce type, length, and allowed characters.
- Prefer parameterized queries with the Lucid ORM or query builder instead of string interpolation.
- Apply output escaping for any data rendered in templates, using Edge’s built-in escaping.
Code examples
Unsafe pattern: Dynamic query construction influenced by template-tainted input
// routes.ts
import Route from '@ioc:Adonis/Core/Route'
Route.get('/search', async ({ request, view }) => {
const search = request.qs().search // user input
// Unsafe: using input to decide which columns to select
const column = search == 'name' ? 'username' : 'email'
const results = await User.query().select(column).where('tenant_id', currentTenantId)
await view.render('dashboard', { results, search })
})
// resources/views/dashboard.edge
@if(search === 'name')
@for(let row of results) {{ row.username }} @endfor
@else
@for(let row of results) {{ row.email }} @endfor
@endif
Safe pattern: Parameterized queries and strict context-aware rendering
// routes.ts
import Route from '@ioc:Adonis/Core/Route'
import { schema } from '@ioc:Adonis/Core/Validator'
const searchSchema = schema.create({
search: schema.string.optional({}, [ 'normalize_whitespace' ])
})
Route.get('/search', async ({ request, view }) => {
const payload = await request.validate({ schema: searchSchema })
const search = payload.search
// Always use parameterized queries; never interpolate user input into SQL strings
const query = User.query().where('tenant_id', currentTenantId)
if (search) {
query.whereILike('username', `%${search}%`)
}
const results = await query.exec()
// Safe: Edge auto-escapes by default in {{ }}
await view.render('dashboard', { results, search })
})
// resources/views/dashboard.edge
@if(search)
Results for {{ search }}
@endif
@for(let row of results)
- {{ row.username }} — {{ row.email }}
@endfor
Tenant isolation with CockroachDB: Use runtime-bound tenant identifiers and avoid dynamic database/table names.
// Always resolve tenant from authenticated session, not from user input
import { getTenantIdFromSession } from 'src/lib/auth'
Route.get('/profile', async ({ auth, view }) => {
const tenantId = getTenantIdFromSession(auth)
const profile = await User.query()
.where('tenant_id', tenantId)
.where('id', auth.user!.id)
.firstOrFail()
await view.render('profile', { profile })
})
// In your Edge template, never reconstruct table or column names from user input
// UNSAFE: `User.query().table(userSuppliedTable).where(...)`
// SAFE: use a static schema and tenant scoping