Edit Page

Other Security Plugins

RESTHeart
Prefer not to run it yourself? Ulabase is this, hosted, with sign-up, payments and an MCP server already on. Free to start. Get started →

Introduction

RESTHeart provides some general purpose security plugins that help hardening the security.

OriginVetoer

OriginVetoer protects from CSRF attacks by forbidding requests whose Origin header is not whitelisted.

It can configured as follows:

originVetoer:
  enabled: true # <---- default is false
  whitelist:
    - https://restheart.org
    - http://localhost
  # when true, requests with no Origin header (non-browser clients) are allowed;
  # browser requests are still validated against the whitelist.
  # Since 9.6 the default changed to true; set to false to restore pre-9.6 behavior.
  allow-missing-origin: true
  # optional list of paths for whose the Origin header
  # is not checked. values can be absolute paths
  # or patterns like /{var}/path/to/resource/*
  # ignore-paths:
  #   - /{tenant}/bucket.files/{id}/binary
  #   - /coll/docid

Per-request whitelist override (since 9.5)

In multi-tenant deployments, one RESTHeart instance may serve many independent services, each with its own frontend origin(s) — a single static whitelist can’t express "allow https://acme.example.com for service A, https://foo.dev for service B."

A plugin (typically an interceptor running at REQUEST_BEFORE_AUTH, before `OriginVetoer’s check) can attach a request param that replaces the static whitelist for that request only:

request.attachParam("override-origin-whitelist", List.of("https://acme.example.com"));
  • If override-origin-whitelist is not attached: OriginVetoer behaves exactly as with static configuration — no behavior change for single-tenant deployments.

  • If attached: it fully replaces the static whitelist for that request (exact-match only — whitelist-patterns is not consulted when an override is present).

  • If attached as an empty list: the request is denied (fail closed) — this is different from not attaching the param at all.

Per-request allow-missing-origin override (since 9.6)

The allow-missing-origin setting can also be overridden per request, allowing multi-tenant deployments to control whether non-browser clients are allowed on a per-service basis:

request.attachParam("override-origin-allow-missing", true);
  • If override-origin-allow-missing is not attached: the static allow-missing-origin config value is used.

  • If attached as true: requests without an Origin header are allowed for this request, even if the static config is false.

  • If attached as false: requests without an Origin header are denied for this request, even if the static config is true.

Denial reason in the response body (since 9.9)

By default a request vetoed by OriginVetoer gets a plain 403 Forbidden with an empty body. Since 9.9.0, OriginVetoer attaches a specific reason for the denial, which RESTHeart returns as the message field of a JSON response body:

{ "message": "Origin https://evil.example.com not allowed" }

or, when the Origin header itself is missing and allow-missing-origin is false:

{ "message": "Origin header is required" }

See Explaining a denial for how this mechanism works and how to use it in a custom Authorizer.

filterOperatorsBlacklist

A global blacklist for MongoDb query operators in the filter and keys query parameters.

filterOperatorsBlacklist:
  blacklist: [ "$where", "$function", "$accumulator" ]
  enabled: true

With this default configuration, the requests GET /coll?filter={"$where": "…​."}, GET /coll?filter={"$expr": {"$function": {…​}}} and GET /coll?keys={"x": {"$function": {…​}}} are forbidden with 403: the check looks inside nested documents and arrays too. keys is a projection, and since MongoDB 4.4 a projection accepts aggregation expressions, so it is checked like the filter (since 9.11.1).

updatePipelineSecurityChecker

A PATCH whose body is an array is an update with an aggregation pipeline, which MongoDB evaluates on the server. This interceptor checks the pipeline against the aggregationSecurity blacklists, the same that guard the aggregations declared on a collection, and refuses with 403 a pipeline that uses a blacklisted operator, such as $function, or a blacklisted stage, such as $lookup.

It is enabled by default (since 9.11.1). Disable it only where aggregationSecurity is disabled too:

updatePipelineSecurityChecker:
  enabled: false

Brute Force Attack Guard

bruteForceAttackGuard defends from brute force password cracking attacks by returning 429 Too Many Requests when more than max-failed-attempts requests with wrong credentials are received in last 10 seconds from the same ip

bruteForceAttackGuard:
  enabled: false
  # max number of failed attempts in 10 seconds sliding window
  # before returning 429 Too Many Requests
  max-failed-attempts: 5
  # if true, the source ip is obtained from X-Forwarded-For header
  # this requires that header being set by the proxy, dangerous otherwise
  trust-x-forwarded-for: false
  # when X-Forwarded-For has multiple values,
  # take into account the n-th from last element
  # e.g. with [x.x.x.x, y.y.y.y., z.z.z.z, k.k.k.k]
  # 0 -> k.k.k.k
  # 2 -> y.y.y.y
  x-forwarded-for-value-from-last-element: 0

When RESTHeart is behind a reverse proxy, the header X-Forwarded-For should be used in place of the remote ip (which in this case is the reverse proxy’s one).

If several tiers of reverse proxies are in place, x-forwarded-for-value-from-last-element option allows to select the correct value.

If the server is reached through more than one entry, each with its own chain of proxies, the client is at a different position on each. Since 9.9.2 x-forwarded-for-overrides sets the position per entry: the first override whose predicate matches decides, the other requests use x-forwarded-for-value-from-last-element.

bruteForceAttackGuard:
  trust-x-forwarded-for: true
  x-forwarded-for-value-from-last-element: 0   # straight to the load balancer
  x-forwarded-for-overrides:
    - predicate: regex[pattern='.*\.example\.app', value='%{i,Host}', full-match=true]
      value-from-last-element: 1               # through a CDN, then the load balancer

The predicate must tell the entries apart by something a client cannot forge, like a host that only one entry forwards.

A blocked source is logged on one line that starts with Brute force guard:, when it gets blocked and when it is released. Since 9.9.2, with the emails provider configured, notify-email also mails an alarm, at most one per source every notify-cooldown-minutes (60 by default).

Root Role Guard

rootRoleGuard forbids accounts handled by mongoAclAuthorizer to gain the root-role defined by the mongoAclAuthorizer.

Note
rootRoleGuard is enabled by default.

The default authorizer mongoAclAuthorizer enables the root-role: admin by default.

Warning
clients with root-role can execute any request.

Although a the root-role is very useful at testing and developing time, it is a security risk to leave it enabled, this is why the security hardening doc page suggests to disable it by setting the configuration option mongoAclAuthorizer.root-role: null.

In any case, an account handled by the mongoRealAuthenticator (that handles accounts with roles in a MongoDb collection) should never gain the root-role via a MongoService write requests as PATCH /users/foo {"roles": ["admin"]}.

The default configuration follows:

rootRoleGuard:
  enabled: true