---
title: "Rails security middleware: quickstart · camada"
description: "Keep credential stuffers, scrapers and exploit scanners out of your Rails app: bundle add camada, two lines and one env var."
url: https://www.camada.app/docs/quickstart/rails
last_updated: 2026-10-30
---

Quickstart

# Rails quickstart

Package `camada`, on RubyGems.

## 1. Install

```sh
bundle add camada
```

## 2. Environment

The key is printed once when you create a project in the app. Set these as environment variables: export them in the shell that starts your app, or add them in your host's settings. Rack and Rails read no .env file (Rails needs the dotenv-rails gem for that).

shell

```sh
export CAMADA_KEY=<ingest_token>.<snap_token>
export CAMADA_INGEST_URL=http://localhost:8787   # dev only; defaults to production ingest
```

## 3. Wire it in

```ruby
# config.ru — Sinatra, Hanami, or any Rack app
require "camada"
use Camada::Rack      # first, so camada answers before routing
run App

# Rails — nothing to add: the Railtie inserts Camada::Rack at the top of the middleware stack
```

- One gem, one Rack middleware; the Railtie mounts it for Rails.
- Never request.ip: the middleware reads REMOTE_ADDR and applies your trusted-proxy rules itself.

## 4. Add the beacon to HTML pages

Optional but recommended. The beacon cuts false positives by showing which sessions are real browsers. Use the helper.

```ruby
"<html><head>#{Camada.script_tag(env)}</head>…"   # Sinatra: env; Rails: request.env
```

## 5. Send app outcomes

Login failures, signups and checkout declines are the signals no edge position sees. The identifier is HMAC-hashed in your process. How they catch [credential stuffing](https://www.camada.app/use-cases/credential-stuffing).

```ruby
Camada.track(env, "login_failed", user: email)   # Sinatra: env; Rails: request.env
```

Runtime

## What it does per request

1. Refreshes the snapshot off-path on a thread at the cadence your tenant config sets (ETag/304, gzip on the wire). A cold start fails open.
2. Resolves the client IP from REMOTE_ADDR per your trusted-proxy setting — never request.ip, which trusts X-Forwarded-For from anyone.
3. Your ordered custom rules first, then allow, block, challenge. A block gets a 403 with x-block-reason before your app runs; a challenge serves the proof-of-work page on a page load, and an API call reaches your app with an x-camada-challenge: 1 response header. The event still ships.
4. Serves /_cam/b.js (the beacon, first-party) and relays /_cam/fp posts onto the event batch with the resolved client IP.
5. Sets x-rid and the _sfp session cookie, and when the server closes the body ships one batched, redacted event carrying the matched route pattern (sinatra.route, Rails route_uri_pattern).

Limits

## Caveats at this position

- Enforcement at this position covers IP, path, user-agent and request-header conditions. ASN, country and TLS-fingerprint entries fail open in-app.
- The Rack env carries no wire header order, so the analyst reads no HEADER_ORDER signal from this tap and never scores its absence.
- The request that builds the engine is answered cold. Warm it in an initializer by waiting for the boot poll (the README shows the bounded loop).
- Threads do not survive a fork: the SDK compares Process.pid on every call and starts over in a forked worker. Puma cluster mode, Unicorn and Passenger need nothing added.
- CAMADA_SERVERLESS=1 switches to a per-request staleness check with no poll thread.

Kill switch: `CAMADA_DISABLED=1`, checked per request. All other variables are listed in the [docs index](https://www.camada.app/docs); the package's options and deployment notes are on the [Ruby page](https://www.camada.app/docs/ruby).

## Sitemap

[Every page on this site](https://www.camada.app/sitemap.md)
