Clickjacking in Hanami with Firestore
Clickjacking in Hanami with Firestore — 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 invisible or overlaying page. In a Hanami application that uses Google Cloud Firestore as a backend, this can occur when pages render data from Firestore without enforcing frame isolation. An attacker can embed the Hanami page inside an <iframe> and overlay interactive controls, causing a logged-in user to inadvertently perform Firestore-backed actions such as updating records or invoking server-side methods.
Hanami follows the model–view–controller pattern and typically renders views with embedded partials. If these views include forms or state-changing buttons without explicit frame-busting headers or sandboxing, they remain vulnerable even when Firestore data is correctly retrieved. The Firestore integration is not directly responsible for clickjacking, but the way Hanami templates bind Firestore documents to UI elements can amplify the impact: a user may click what appears to be a benign element, while a hidden form submits a PATCH request that updates Firestore documents based on the user’s permissions.
Because Firestore security rules operate at the authentication and authorization level, they do not prevent clickjacking. A user with valid credentials can still be socially engineered into performing unwanted writes. The risk is especially relevant when Hanami exposes Firestore collections through REST-like routes or when server-rendered forms include document IDs in hidden fields. Without anti-CSRF tokens and without explicit X-Frame-Options or Content-Security-Policy frame-ancestors directives, an attacker can craft a page that loads the Hanami endpoint and captures interactions.
Real-world examples align with common OWASP categories such as A05:2021 — Security Misconfiguration and A08:2021 — Software and Data Integrity Failures. For instance, a Hanami route like /projects/:id/update_status that accepts PATCH requests and directly maps parameters to Firestore document fields becomes a target if embedded maliciously. Attack patterns involving multiple chained UI actions (e.g., toggle visibility, then submit) are feasible when clickjacking vectors are not mitigated at the rendering layer.
Firestore-Specific Remediation in Hanami — concrete code fixes
Remediation centers on breaking the ability of external pages to control interactions within your Hanami views and ensuring that Firestore-bound actions require explicit intent. The following practices and code examples assume you use the google-cloud-firestore gem within a Hanami controller and view layer.
1. Anti-clickjacking headers
Configure your web server or Rack middleware to send X-Frame-Options and Content-Security-Policy headers. In a Hanami app, you can add these in application.rb or via middleware configuration.
# config/initializers/security_headers.rb
module MyApp
class Application < Hanami::Application
configure do |config|
config.middleware.use Rack::Protection::FrameOptions
end
end
end
Alternatively, set headers explicitly per route or globally to restrict framing:
# config/initializers/csp.rb
Rack::Protection::FrameOptions.override_action = ->(env) { [200, { 'Content-Security-Policy' => "frame-ancestors 'self'" }, []] }
2. Embed Firestore reads safely and avoid hidden state
When displaying Firestore data, avoid placing mutable actions inside iframes or partial templates that could be overlaid. Use explicit tokens and ensure Firestore document reads are tied to the current user context.
# app/controllers/projects/show.rb
require "google/cloud/firestore"
class Projects::Show
include Hanami::Action
def initialize(firestore_client: nil)
@firestore = firestore_client || Google::Cloud::Firestore.new
end
def call(params)
project = @firestore.doc("projects/#{params[:id]}").get
halt 404, 'Not found' unless project.exists?
@project_data = {
id: project.id,
name: project[:name],
status: project[:status]
}
# Render a view that uses @project_data directly, without hidden form fields for sensitive operations
view.render("projects/show", @project_data)
end
end
3. Require anti-CSRF tokens for state-changing requests
Ensure that any POST, PATCH, or DELETE originating from Firestore-backed views includes a per-session or per-request token. Hanami provides built-in protection that you should enable.
# app/controllers/projects/update.rb
class Projects::Update
include Hanami::Action
def call(params)
# Hanami verifies authenticity token automatically when config.session_options[:skip] is not used
project = Google::Cloud::Firestore.new.doc("projects/#{params[:id]}").get
halt 403, 'Forbidden' unless project.exists?
# Safe update using only permitted params
updated = project.update(params.fetch(:project, {}).slice(:status, :owner))
flash[:success] = 'Project updated'
redirect_to routes.projects_path
end
end
4. Avoid exposing Firestore document IDs in URLs and UI without checks
When Firestore document IDs are reflected in UI elements, ensure they are scoped to the current user and validated server-side to prevent direct object-level authorization bypasses (BOLA/IDOR) that could be leveraged inside a clickjacking context.
# app/views/projects/show.html.erb
<form action="<%= routes.projects_update_path(project_id: @project_data[:id]) %>" method="post">
<input name="_csrf_token" value="<%= session_csrf_token %>">
<button type="submit">Update Status</button>
</form>