Clickjacking in Adonisjs with Dynamodb
Clickjacking in Adonisjs with Dynamodb — how this specific combination creates or exposes the vulnerability
Clickjacking is a client-side UI redress attack in which an attacker tricks a user into clicking or interacting with a transparent or opaque element embedded over a legitimate page. When an AdonisJS application uses DynamoDB as its data store and renders views that include embedded resources or dynamic UI components, misconfigured HTTP headers and insecure frame handling can expose endpoints to clickjacking.
AdonisJS does not enforce a default X-Frame-Options header for all responses. If routes rendering forms or dashboards—such as a DynamoDB-backed user settings page—do not explicitly set frame-prevention headers, browsers may render the page inside an <iframe> on a malicious site. Attackers can overlay invisible controls or CSS on top of the embedded content, causing unintended actions like updating a DynamoDB record via a crafted POST request initiated without the user’s knowledge.
DynamoDB itself does not introduce clickjacking, but the application logic that reads and writes to DynamoDB can be invoked through these forged interactions. For example, an authenticated request that calls a controller method to update user preferences in DynamoDB could be triggered by a hidden form submission from an embedded frame. If the endpoint relies solely on session cookies for authentication and lacks CSRF tokens or SameSite cookie attributes, the request is executable even when embedded. The risk is compounded when the UI includes inline JavaScript that dynamically constructs forms or modifies headers, which can be manipulated through CSS or script injection within the frame to alter the intended request parameters.
Moreover, if AdonisJS views include external widgets or iframes—such as analytics or third-party login buttons—that originate from untrusted sources, they can serve as vectors for embedding the application’s own pages. Developers might inadvertently expose administrative or data-modifying pages that interact with DynamoDB without considering frame context. The absence of Content Security Policy (CSP) frame-ancestors directives further allows arbitrary origins to embed the page, enabling attackers to design convincing phishing interfaces that overlay the legitimate application UI.
In a typical AdonisJS + DynamoDB stack, the following conditions create a exploitable clickjacking surface:
- Controllers returning HTML views that perform state changes (e.g., updating DynamoDB records) without anti-CSRF tokens.
- Missing or permissive X-Frame-Options or Content-Security-Policy frame-ancestors headers.
- Use of cookies for authentication without SameSite=Strict or Lax attributes.
- Dynamic rendering of forms or links that directly invoke DynamoDB write operations based on GET requests or poorly validated POST inputs.
Dynamodb-Specific Remediation in Adonisjs — concrete code fixes
Remediation focuses on server-side header enforcement, CSRF protection, and secure cookie settings within the AdonisJS application layer. DynamoDB operations remain unchanged, but the surrounding HTTP interface must prevent unauthorized embedding and interaction.
1. Enforce Frame-Defpendency Headers
Ensure every response that renders UI or performs state changes includes X-Frame-Options or Content-Security-Policy headers. In AdonisJS, you can use middleware to apply these headers globally or per route.
// start/kernel.ts
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class Kernel {
public async handle(ctx: HttpContextContract, next: () => Promise) {
// Apply globally, or skip for public APIs
ctx.response.headers.set('X-Frame-Options', 'DENY')
ctx.response.headers.set(
'Content-Security-Policy',
"frame-ancestors 'self'; frame-src 'none'"
)
await next()
}
}
For routes that must be embeddable (e.g., public dashboards), use a more restrictive policy instead of DENY:
ctx.response.headers.set('Content-Security-Policy', "frame-ancestors 'self' https://trusted.partner.com")
2. Secure Cookies for Authentication
Ensure session cookies used for DynamoDB-authenticated requests include SameSite and Secure attributes. In AdonisJS, configure this in start/session.ts.
import { SessionConfig } from '@ioc:Adonis/Addons/Session'
const sessionConfig: SessionConfig = {
driver: 'cookie',
cookie: {
name: 'app_session',
httpOnly: true,
secure: true, // send only over HTTPS
sameSite: 'strict', // or 'lax' for limited cross-site GETs
path: '/',
},
}
export default sessionConfig
3. CSRF Protection for State-Changing Requests
AdonisJS includes built-in CSRF protection for form submissions. Ensure it is enabled for routes that modify DynamoDB data. Validate origins and use anti-CSRF tokens in forms.
// In a controller method handling DynamoDB updates
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { schema, rules } from '@ioc:Adonis/Core/Validator')
import DynamoDB from '@aws-sdk/client-dynamodb'
export default class ProfileController {
public async updateProfile({ request, auth, response }: HttpContextContract) {
const bodySchema = schema.create({
userId: schema.string({ trim: true }),
theme: schema.string.optional(),
})
const validatedData = await request.validate({ schema: bodySchema })
// Ensure the authenticated user matches the request payload
const user = auth.user
if (user?.id !== validatedData.userId) {
return response.badRequest({ error: 'Unauthorized' })
}
const client = new DynamoDB({ region: 'us-east-1' })
const params = {
TableName: 'UserSettings',
Key: { userId: { S: validatedData.userId } },
UpdateExpression: 'SET theme = :theme',
ExpressionAttributeValues: {
':theme': { S: validatedData.theme },
},
}
await client.send(new DynamoDB.UpdateCommand(params))
return response.ok({ status: 'updated' })
}
}
This example shows a secure update flow: validated input, ownership check, and DynamoDB interaction. The accompanying CSRF token (handled automatically by AdonisJS for cookie-based sessions) prevents cross-origin forged requests.
4. Avoid Unsafe GET-Based Writes
Ensure DynamoDB mutations are performed via POST, PUT, or DELETE methods, not GET. AdonisJS route definitions should reflect this.
// routes.ts
Route.put('/profile/:userId', 'ProfileController.updateProfile')
.as('profile.update')
// Avoid:
// Route.get('/profile/:userId/update-theme', 'ProfileController.updateTheme')
5. Middleware to Detect and Block Embedding
Add lightweight logic to critical routes to reject requests that include an Referer or Origin header indicating an external site, if your application does not expect cross-origin interactions.
export default class AntiClickjackingMiddleware {
public async handle(ctx: HttpContextContract, next: () => Promise) {
const allowedOrigin = 'https://yourdomain.com'
const origin = ctx.request.header('origin')
const referer = ctx.request.header('referer')
if (origin && !origin.startsWith(allowedOrigin)) {
return ctx.response.unauthorized({ error: 'Invalid origin' })
}
if (referer && !referer.startsWith(allowedOrigin)) {
return ctx.response.unauthorized({ error: 'Invalid referer' })
}
await next()
}
}