Skip to content

Security

Security considerations and hardening guidance for production mroki deployments.

See Configuration for the full environment variable reference.


API Key Authentication

Every request to the mroki API must include a valid API key via the Authorization header:

Authorization: Bearer <key>

The key is configured with the MROKI_APP_API_KEY environment variable and must be at least 16 characters. Keys are compared using crypto/subtle.ConstantTimeCompare, preventing timing side-channel attacks.

Generating a strong key:

bash
# 32-byte random key, base64-encoded (44 characters)
openssl rand -base64 32

Unauthenticated or invalid requests receive an RFC 7807 error response with HTTP 401.


Field Redaction

mroki replaces sensitive field values with [REDACTED] before storage. The following fields are redacted by default:

  • headers.Authorization
  • headers.Cookie
  • headers.Set-Cookie
  • headers.X-Api-Key

Fields use gjson path notation with a headers. or body. prefix (e.g. body.user.password).

Redacted fields are automatically excluded from diff computation so they don't produce false positives.

Adding fields per gate (API mode):

bash
# Add extra redacted fields to an existing gate
curl -X PATCH /gates/{id} \
  -d '{"redacted_fields": ["headers.X-Internal-Token", "body.secret"]}'

Adding fields in standalone proxy mode:

bash
# Comma-separated list — adds to the default set
MROKI_APP_REDACTED_FIELDS=headers.X-Internal-Token,body.user.password

Rate Limiting

The API enforces a token-bucket rate limit of 1000 requests per minute per IP (configurable via MROKI_APP_RATE_LIMIT). When exceeded, the API responds with:

  • HTTP 429 Too Many Requests
  • Retry-After header indicating when to retry

Metrics Endpoint

When MROKI_APP_METRICS_ENABLED=true (the default), both components expose a Prometheus /metrics endpoint without authentication, mirroring the health probes:

  • mroki-apiGET /metrics on the API port (MROKI_APP_PORT), outside the API-key middleware chain.
  • mroki-proxyGET /metrics on the admin port (MROKI_APP_ADMIN_PORT), isolated from proxied traffic.

The endpoint exposes operational data, including per-gate series labelled with gate UUIDs and latency distributions. Treat it as internal:

  • Keep the API and proxy admin ports on a trusted network (private subnet, same Kubernetes pod) and do not expose /metrics publicly. Scope it to your Prometheus scrapers via firewall rules or a NetworkPolicy.
  • If you front the API with a reverse proxy, do not route external traffic to /metrics.
  • Set MROKI_APP_METRICS_ENABLED=false to disable the endpoint entirely where it is not needed.

TLS / Network Security

mroki does not terminate TLS itself. Use a reverse proxy or load balancer (nginx, Caddy, cloud LB) to terminate HTTPS in front of the API.

Example nginx snippet:

nginx
server {
    listen 443 ssl;
    server_name mroki.example.com;

    ssl_certificate     /etc/ssl/certs/mroki.crt;
    ssl_certificate_key /etc/ssl/private/mroki.key;

    location / {
        proxy_pass http://127.0.0.1:8090;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

The internal network between the proxy and the API should be trusted (e.g. same Kubernetes pod, private subnet) or secured with mutual TLS (mTLS). Proxy-to-API mTLS is planned but not yet implemented.


Security Headers

The API emits standard security response headers on every response — including unauthenticated endpoints such as /health and /metrics, and error responses. The following headers are always sent:

HeaderValuePurpose
X-Content-Type-OptionsnosniffStops browsers from MIME-sniffing responses away from the declared Content-Type
X-Frame-OptionsDENYPrevents the responses from being framed (clickjacking protection)
Referrer-Policyno-referrerPrevents the Referer header from leaking API URLs to third parties

HSTS (Strict-Transport-Security)

Strict-Transport-Security is off by default because mroki does not terminate TLS. Enabling it while the API is reachable over plain HTTP can make clients unreachable, so only turn it on once a TLS-terminating reverse proxy is confirmed in front of the API. mroki never auto-detects TLS from the request; the header is emitted solely based on the config toggle.

bash
# Only enable behind TLS termination
MROKI_APP_HSTS_ENABLED=true
# max-age advertised to browsers (Go duration, default 8760h = 365d)
MROKI_APP_HSTS_MAX_AGE=8760h

When enabled, the API sends:

Strict-Transport-Security: max-age=31536000; includeSubDomains

MROKI_APP_HSTS_MAX_AGE must be positive when HSTS is enabled, otherwise the API rejects the configuration at startup.

Hub (static server) security headers

The hub is a static Vue SPA served by Caddy (build/package/mroki-hub/Caddyfile). It emits security response headers on every SPA response (the /health endpoint is left untouched):

HeaderValuePurpose
X-Content-Type-OptionsnosniffStops browsers from MIME-sniffing responses away from the declared Content-Type
X-Frame-OptionsDENYPrevents the hub from being framed (clickjacking protection), paired with CSP frame-ancestors 'none'
Referrer-Policyno-referrerPrevents the Referer header from leaking URLs to third parties
Content-Security-Policysee belowRestricts the origins the SPA may load resources from and connect to

The Content-Security-Policy is:

default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self' {$MROKI_APP_API_BASE_URL}; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'
  • style-src 'unsafe-inline' is required because Vue/reka-ui emit runtime inline style attributes for positioning.
  • connect-src is widened at container start with the deploy-time API origin (MROKI_APP_API_BASE_URL) so the SPA's fetch/XHR calls to mroki-api are allowed. If unset, it falls back to connect-src 'self'.

Unlike the API, the hub emits Strict-Transport-Security: max-age=31536000; includeSubDomains unconditionally. This is safe because browsers ignore HSTS over plain HTTP and only honor it over HTTPS — it assumes TLS is terminated by an upstream reverse proxy (the hub, like the rest of mroki, does not terminate TLS itself).


Database Security

bash
# Use a strong, randomly generated password
POSTGRES_PASSWORD=$(openssl rand -base64 32)

# Enable SSL for the database connection
MROKI_APP_DATABASE_URL=postgres://user:pass@host:5432/mroki?sslmode=require

# Restrict network access in pg_hba.conf
host    mroki    mroki    10.0.0.0/8    scram-sha-256

Additional recommendations:

  • Create a dedicated database user with only the permissions mroki requires (SELECT, INSERT, UPDATE, DELETE on its tables)
  • Store database credentials in a secrets manager (Kubernetes Secrets, AWS Secrets Manager, etc.)

CORS

Cross-origin requests are controlled via MROKI_APP_CORS_ORIGINS. Set it to a comma-separated list of allowed origins:

bash
MROKI_APP_CORS_ORIGINS=https://hub.example.com,https://admin.example.com

When configured, the API sets:

Access-Control-Allow-Origin: <configured origin>
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

If MROKI_APP_CORS_ORIGINS is empty or unset, CORS is disabled entirely — no Access-Control-* headers are sent.

The wildcard origin * is not permitted. Because the API always allows the Authorization header, a wildcard origin would let any site drive authenticated cross-origin requests. This misconfiguration is rejected at startup with a clear error — set an explicit list of trusted origins (e.g. https://hub.example.com) instead.


Production Checklist

  • Strong API key — at least 16 characters, randomly generated (openssl rand -base64 32)
  • TLS termination — place a reverse proxy or load balancer in front of the API
  • Enable HSTS — once TLS termination is confirmed, set MROKI_APP_HSTS_ENABLED=true (see Security Headers)
  • Database SSL — append ?sslmode=require to MROKI_APP_DATABASE_URL
  • Restrict network access — firewall the API and database; deploy the proxy in an isolated network
  • Configure CORS origins — set MROKI_APP_CORS_ORIGINS to only the domains that need access
  • Restrict the metrics endpoint/metrics is unauthenticated; firewall it to your scrapers or disable it with MROKI_APP_METRICS_ENABLED=false where unused
  • Enable field redaction — review default redacted fields; add application-specific fields per gate or via MROKI_APP_REDACTED_FIELDS
  • Monitor logs — watch for 401/429 responses and unusual traffic patterns