MEDIUM clickjackinghapifirestore

Clickjacking in Hapi with Firestore

Clickjacking in Hapi with Firestore — how this specific combination creates or exposes the vulnerability

Clickjacking is a client-side vulnerability where an attacker tricks a user into interacting with a hidden or disguised UI element inside an iframe. When Hapi serves endpoints that render Firestore-backed data without anti-clickjacking protections, an attacker can embed these endpoints in malicious pages, leading to unauthorized actions or data exposure. The risk is elevated when endpoints return sensitive Firestore documents (e.g., user profile or permission data) and rely on cookies for session management without additional safeguards.

Hapi does not set X-Frame-Options or Content-Security-Policy (CSP) frame-ancestors by default, which means browsers may render responses inside frames. If a route streams Firestore data directly into HTML (e.g., server-side rendering) or exposes JSON that an attacker embeds via iframe, the browser may display or act on that content within the attacker’s page. For example, a Hapi route that returns a Firestore document containing user roles could be framed to trick a user into changing settings or escalating privileges unknowingly.

Firestore security rules alone do not prevent clickjacking; they enforce read/write permissions but do not control framing. An attacker can still load a legitimate Hapi endpoint in a frame if the browser permits it. This becomes critical when combined with authenticated sessions, where cookies are sent automatically. Even if the Firestore rules restrict access, the UI interaction within the frame may trigger unintended operations because the user’s session is active.

Consider a Hapi route that renders a Firestore document in an HTML page without frame restrictions. An attacker creates a page with a transparent iframe pointing to that route. If the user is logged in, the browser loads the data and may display editable fields or buttons, which the attacker overlays with invisible controls. The user’s clicks on the visible page actually activate elements inside the hidden frame, potentially modifying Firestore data if the route processes state changes.

middleBrick can detect this class of issue by scanning unauthenticated attack surfaces and identifying missing anti-clickjacking headers. Its checks include security headers related to framing and CSP, which map to OWASP API Top 10 and broader web security standards. While middleBrick does not fix headers, its findings provide remediation guidance to help developers enforce proper framing policies.

Firestore-Specific Remediation in Hapi

To remediate clickjacking in Hapi when serving Firestore-backed content, enforce frame restriction headers and validate data exposure in routes. Use the hapi-cookie and @hapi/inert plugins for rendering, and ensure Firestore reads respect security rules and session context. Below are concrete patterns for Hapi routes with Firestore integration.

Set Security Headers in Hapi

Add a server extension to inject X-Frame-Options and Content-Security-Policy headers on every response. This prevents the browser from rendering responses in iframe or frame contexts.

const Hapi = require('@hapi/hapi');

const init = async () => {
  const server = Hapi.server({ port: 4000, host: 'localhost' });

  server.ext('onPreResponse', (request, h) => {
    const response = request.response;
    if (response.variety === 'view') {
      response.header('X-Frame-Options', 'DENY');
      response.header('Content-Security-Policy', "frame-ancestors 'none'");
    }
    return h.continue;
  });

  server.route({
    method: 'GET',
    path: '/profile',
    handler: async (request, h) => {
      // Assume Firestore initialized elsewhere
      return h.view('profile', { user: request.auth.credentials.user });
    }
  });

  await server.start();
};

init();

Secure Firestore Route with Authentication and CSP

When returning Firestore documents via Hapi routes, ensure authentication and set strict CSP headers to limit framing and resource embedding. This example fetches a user document and returns JSON with protective headers.

const Hapi = require('@hapi/hapi');
const { initializeApp } = require('firebase-admin/app');
const { getFirestore } = require('firebase-admin/firestore');

initializeApp();
const db = getFirestore();

const init = async () => {
  const server = Hapi.server({ port: 4000, host: 'localhost' });

  server.ext('onPreResponse', (request, h) => {
    const response = request.response;
    if (response.variety === 'view' || response.variety === 'plain') {
      response.header('X-Frame-Options', 'SAMEORIGIN');
      response.header('Content-Security-Policy', "frame-ancestors 'self'");
    }
    return h.continue;
  });

  server.route({
    method: 'GET',
    path: '/user/{id}',
    handler: async (request, h) => {
      const doc = await db.collection('users').doc(request.params.id).get();
      if (!doc.exists) {
        return h.response({ error: 'Not found' }).code(404);
      }
      // Return only safe fields; avoid exposing sensitive data in frames
      return doc.data();
    }
  });

  await server.start();
};

init();

Use View Engine Safely with Firestore

If rendering server-side views, ensure templates do not embed untrusted input and headers are applied. Combine Firestore reads with view context sanitization.

const Hapi = require('@hapi/hapi');
const Inert = require('@hapi/inert');
const { initializeApp } = require('firebase-admin/app');
const { getFirestore } = require('firebase-admin/firestore');

initializeApp();
const db = getFirestore();

const init = async () => {
  const server = Hapi.server({ port: 4000, host: 'localhost' });

  await server.register(Inert);

  server.ext('onPreResponse', (request, h) => {
    const response = request.response;
    if (response.variety === 'view') {
      response.header('X-Frame-Options', 'DENY');
      response.header('Content-Security-Policy', "frame-ancestors 'none'");
    }
    return h.continue;
  });

  server.route({
    method: 'GET',
    path: '/dashboard',
    handler: async (request, h) => {
      const snapshot = await db.collection('widgets').get();
      const widgets = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
      return h.view('dashboard', { widgets: widgets });
    }
  });

  await server.start();
};

init();

These patterns ensure that Hapi responses are not framed inadvertently and that Firestore data exposure is controlled. Security headers should be tested to confirm they are present and correct. middleBrick’s scans can validate these headers and highlight missing protections as part of its findings.

Frequently Asked Questions

Does Firestore security rules prevent clickjacking?
No. Firestore security rules enforce data access permissions but do not control browser framing. Clickjacking is a client-side issue that requires HTTP headers like X-Frame-Options or CSP frame-ancestors to prevent embedding in iframes.
Can middleBrick fix clickjacking vulnerabilities?
middleBrick detects and reports missing anti-clickjacking headers and related misconfigurations. It provides findings with remediation guidance, but it does not automatically fix or patch the endpoints.