Clickjacking in Nestjs with Cockroachdb
Clickjacking in Nestjs with Cockroachdb — how this specific combination creates or exposes the vulnerability
Clickjacking is a client-side UI redress attack where an attacker tricks a user into interacting with a hidden or disguised element inside an embedded frame. In a NestJS application backed by CockroachDB, the vulnerability typically arises not from CockroachDB itself, but from missing HTTP response headers and insecure rendering logic that allow the app to be framed. When a NestJS app serves HTML or API responses without anti-clickjacking protections, and those responses reference data stored in CockroachDB (e.g., user-specific content rendered via templates or API-driven UI widgets), an attacker can embed the app in an <iframe> and manipulate user actions.
Consider a NestJS controller that fetches user profile data from CockroachDB and renders a page with sensitive actions such as changing email or payment settings:
@Controller('profile')
export class ProfileController {
constructor(private profileService: ProfileService) {}
@Get()
async renderProfile(@Req() req) {
const profile = await this.profileService.findByUserId(req.user.id);
return { profile };
}
}
If this endpoint returns HTML without a Content-Security-Policy (CSP) frame-ancestors directive or an X-Frame-Options header, and the UI includes state-changing forms (e.g., POST to update email), an attacker can load the page in an invisible iframe and coerce the user into submitting requests while logged into CockroachDB-backed session state. Because CockroachDB is often used in distributed, high-availability setups, the same NestJS app may serve requests from multiple regions; clickjacking risk remains consistent as long as the headers and CSP rules are absent, regardless of the database topology.
Another scenario involves API-driven NestJS frontend rendering (e.g., Server-Side Rendering or API calls from a frontend framework) that pulls sensitive UI state from CockroachDB. If the API endpoints lack proper authentication and authorization checks (BOLA/IDOR), and responses are embeddable, an attacker can craft a malicious page that embeds the API responses and uses CSS/JS to overlay invisible controls. The NestJS app’s reliance on CockroachDB for session or user data does not inherently cause clickjacking, but without CSP frame-ancestors and X-Frame-Options, the app remains susceptible when rendering views that perform privileged actions.
Cockroachdb-Specific Remediation in Nestjs — concrete code fixes
Remediation focuses on HTTP headers and secure rendering practices in NestJS, independent of CockroachDB’s storage mechanics. The following patterns assume you use NestJS controllers and services that may query CockroachDB via an ORM or raw client.
1. Set X-Frame-Options header
Prevent embedding of sensitive pages by adding the X-Frame-Options header. In NestJS, use an interceptor:
@Injectable()
export class XFrameOptionsInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler) {
const ctx = context.switchToHttp();
const response = ctx.getResponse();
response.setHeader('X-Frame-Options', 'DENY');
return next.handle();
}
}
// app.controller.ts
@Controller()
@UseInterceptors(XFrameOptionsInterceptor)
export class AppController {
constructor(private profileService: ProfileService) {}
// routes
}
2. Enforce Content-Security-Policy frame-ancestors
Use CSP frame-ancestors to control which origins can embed your pages. For maximum safety, disallow all framing:
@Injectable()
export class SecurityHeadersInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler) {
const ctx = context.switchToHttp();
const response = ctx.getResponse();
response.setHeader(
'Content-Security-Policy',
"default-src 'self'; frame-ancestors 'none'; script-src 'self'; style-src 'self';",
);
return next.handle();
}
}
If you need to allow specific trusted domains (e.g., a dashboard), replace 'none' with a space-separated list of URLs:
response.setHeader(
'Content-Security-Policy',
"default-src 'self'; frame-ancestors 'self' https://trusted.example.com; script-src 'self'; style-src 'self';",
)
3. Secure forms and state-changing requests
Ensure POST/PUT/DELETE endpoints validate the Origin or Referer header as an additional defense-in-depth. In NestJS, create a guard or middleware:
@Injectable()
export class CsrfGuard implements CanActivate {
canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
const origin = request.headers.origin;
const referer = request.headers.referer;
const allowedOrigin = 'https://yourdomain.com';
if (!origin || origin !== allowedOrigin) {
throw new ForbiddenException('Invalid origin');
}
return true;
}
}
// Usage on state-changing routes
@Controller('profile')
export class ProfileController {
constructor(private profileService: ProfileService) {}
@UseGuards(CsrfGuard)
@Post('update-email')
async updateEmail(@Body() dto: UpdateEmailDto, @Req() req) {
return this.profileService.updateEmail(req.user.id, dto.email);
}
}
4. CockroachDB query safety
When your NestJS service queries CockroachDB, use parameterized queries to avoid injection that could indirectly affect rendering logic. Example using the CockroachDB Node.js driver:
const { Client } = require('pg'); // CockroachDB wire compatible
const client = new Client({
connectionString: process.env.DATABASE_URL,
});
await client.connect();
// Safe parameterized query
const res = await client.query('SELECT id, name FROM users WHERE id = $1', [userId]);
await client.end();
Ensure that any dynamic values used in SQL are passed as parameters, not interpolated, to prevent data leaks that could be exploited in clickjacking scenarios (e.g., leaking CSRF tokens via SQL-injected UI).