The request gate.
Place it in your endpoint before the router (and before Plug.Static if
asset requests should count towards client behaviour):
plug Limen.Plug,
otp_app: :my_app,
policy: MyApp.BotPolicy,
routes: [
{"/health", :off},
{"/assets", :track},
{"/login", MyApp.LoginPolicy, mode: :enforce}
]Pipeline
Every request goes through the same stages; the first to settle it wins:
- Identify the client: address, prefix and JA4 (see
Limen.Signal), and track its behaviour. - Trust: the policy's
trustrules, which can use the facts the application stated (seeLimen.put_facts/2), allow the request before anything else. - Ban: a banned prefix is denied, or sent to the maze (see
Limen.Maze) when its ban says so. - Limit: the policy's hard limits.
- Pass: a valid pass cookie (see
Limen.Challenge) allows the request right away, without collecting any other signal. - Collect the policy's signals.
- Rules:
allowanddenyrules, then scoring anddecide. SeeLimen.Policy. - Act: continue, or respond on the application's behalf:
403for denials,429withRetry-Afterfor throttling, the challenge page for challenged navigations, a slow maze page forGETrequests sent to the maze.
Requests under the challenge path (/__limen by default) are Limen's own
endpoints and never reach the application. They are served by the plug's
instance, and verify solutions with the keys of whichever instance issued
the challenge.
Requests under a trap path of the plug's instance (see Limen.Trap) are
settled at the :trap stage, before any route: the client is flagged and
sent to the maze, unless a trust rule of the policy covering the path
trusts it.
Each evaluation produces a Limen.Decision, emitted as a
[:limen, :decision] telemetry event, sampled into Limen.DecisionLog,
and stored in conn.private[:limen] (see Limen.decision/1).
The Limen instance is resolved when the plug is initialised (at compile
time in an endpoint); at runtime the plug reads it with a single
:persistent_term lookup per request.
In the router
In the endpoint, the plug sees every request but not your application's
session, so its policies cannot tell signed-in users apart. To gate with
facts about the user (see Limen.put_facts/2), put the plug in a router
pipeline, after whatever states the facts:
# router.ex
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :limen_facts
plug Limen.Plug, otp_app: :my_app, policy: MyApp.BotPolicy
...
end
defp limen_facts(conn, _opts),
do: Limen.put_facts(conn, signed_in: get_session(conn, :user_token) != nil)The router only runs a pipeline for paths it has a route for, so Limen's
own endpoints and trap paths never reach that plug. Keep one in the
endpoint, with policy: :off, to serve them and nothing else:
# endpoint.ex, before the router
plug Limen.Plug, otp_app: :my_app, policy: :offRequests that no gated pipeline handles (static files, APIs) are then
not evaluated at all; give them a pipeline of their own, or gate them in
the endpoint with :routes.
Options
:instance- the name of theLimeninstance to use.:otp_app- use the instance named after this application, as started by{Limen, otp_app: app}. One of:instanceor:otp_appis required.:policy- the policy module for requests no route matches, or:offor:trackto treat them like routes with those targets (below). Defaults toLimen.Policy.Default. Limen's endpoints and trap paths are served whatever the policy.:mode-:dry_runor:enforce, overriding the policy's and the instance's mode.:routes- per-path overrides, as{path, target}or{path, policy, opts}. A path matches itself and everything below it ("/login"matches/loginand/login/otp, not/loginx); the most specific path wins. Targets::off- Limen does nothing at all, for health checks and the like.:track- track client behaviour and enforce bans, nothing else; for static assets.- a policy module, with optional
mode:andinstance:(to use another instance for that path).
Dry-run
In dry-run mode the pipeline runs unchanged, including every state update
(counters, limits, bans) and telemetry event; only the final action is
skipped and the request continues. decision.enforced tells the two apart.
Bans created in dry-run mode are never enforced, not even on enforcing
routes.
The mode of a request is, from the most specific: its own, set by
Limen.Test.put_mode/2 in tests; the route's mode:; the plug's
:mode; the policy's; the instance's.