Security and hardening¶
Added in version 0.13.
What HARP exposes, what it records and what to restrict, for an instance in front of production traffic. This page summarises and links: each topic has its own page, which stays the reference.
HARP sits on the path of your API calls, so it sees and records what they carry, and it trusts whoever writes its configuration and its rules. It does not terminate TLS and does not decide who may call your endpoints: put it behind infrastructure that does.
Checklist¶
Keep the dashboard login (the default), or bind it with
dashboard.host: 127.0.0.1: Dashboard.Use PostgreSQL, not SQLite, and read the startup warnings: Development-only settings.
Firewall every port you do not publish on purpose,
/healthzandmetrics_portincluded: Ports and exposure.Name the query parameters that carry secrets: What is recorded.
Set
proxy.trusted_proxiesif a load balancer is in front: Callers and request handling.Pass secrets by environment variable, never on the command line: Secrets in configuration.
Mind the access log: Logs.
Do not set
HARP_DEBUG: Errors and debug mode.Only load rules you wrote: Rules.
Ports and exposure¶
Every port HARP opens (each proxy.endpoints[].port, dashboard.port and dashboard.metrics_port) binds all
interfaces ([::]), so that a published Docker port works. Only dashboard.host narrows the dashboard port, see
Dashboard. For the others, restrict at the network level: a firewall or security group, a
Kubernetes NetworkPolicy, or, with Docker, publish only the ports you need, on loopback if they are for the host
(-p 127.0.0.1:9100:9100). See Run.
/healthz answers 200 on every port, without authentication, and is not configurable. It reveals only that HARP
runs, and it is what the Docker health check calls.
Port |
Authentication |
Notes |
|---|---|---|
Endpoint ports (proxy) |
None |
HARP forwards what it receives. Whatever secures your upstream (API keys, mTLS) is the caller’s concern. |
|
Login by default |
Also serves |
|
None, by design |
Off by default. Serves only |
Dashboard¶
The dashboard shows everything that goes through the proxy: reaching it is reaching your traffic.
A login is required by default. Without
dashboard.auth, HARP generates a password for theadminuser at each start and logs it once. Set your own users withdashboard.auth(basic authentication, passwords kept as hashes). See the authentication section of Dashboard.dashboard.auth.type: noneserves the dashboard to anyone who can reach the port. Keep it for a workstation: HARP warns about it at every start.dashboard.hostsets the address the dashboard listens on. It defaults to[::];127.0.0.1serves it on loopback only (reach it through an SSH tunnel or a reverse proxy on the host).Dashboard responses carry
X-Content-Type-Options,X-Frame-Optionsand aContent-Security-Policy, and captured message bodies are served as sandboxed attachments. Responses also sendReferrer-Policy: same-origin. See the security headers section of Dashboard.Writes are guarded against cross-site requests. A
PUT,POSTorDELETEon the dashboard is refused with403when the browser flags it cross-site (Sec-Fetch-Site) or itsOriginis not the dashboard’s, and with415unless it sendsContent-Type: application/json. Scripts calling the API must send that content type.
The dashboard speaks plain HTTP and sends the login with every request: terminate TLS in front of it (TLS).
Development-only settings¶
At startup HARP logs a WARNING for every setting that is fine on a laptop and unsafe in production: SQLite as
storage.url, dashboard.auth.type: none and HARP_DEBUG. Treat such a warning in a production log as a defect
of the deployment.
What is recorded¶
The storage application records every transaction: request and response summaries,
headers and bodies (up to proxy.max_recorded_body_size). They are shown in the dashboard and kept in the database,
its backups and the HTTP cache.
Credentials in headers are redacted before storage:
Authorization,Proxy-Authorization,CookieandSet-Cookie, also in the cache. The request sent upstream is untouched. Query parameters are not redacted unless you name them, and neither are paths and bodies. Rules choose what is redacted: see Redaction, and ADR-0009 for the reasoning. Only new traffic is redacted: purge what older versions recorded.To keep the headers or bodies of an endpoint out of storage, a rule sets a marker (
skip-request-storageand friends): see Transaction Markers.Client tags (
x-harp-*request headers) are set by the caller, without authentication.proxy.tagsbounds them (20 tags, names up to 64 characters, values up to 256, by default); strip them at your gateway for untrusted callers. See Client tags.Notifications carry the URL of the remote request without its query string. See Notifications.
The access log is another record, see Logs.
Protect the database like the traffic it holds: restrict access to PostgreSQL, encrypt its volumes and backups, and let the janitor delete old transactions (60 days by default).
Logs¶
HARP’s own logs avoid secrets: the generated dashboard password is shown once, and Sentry events never include the command line or local variables. Still:
The server access log (logger
hypercorn.access,INFOby default, set byLOGGING_HTTP) writes the request line with its full target, query string included, plus theRefererandUser-Agentheaders, for every request on every port. Redaction does not cover it. RaiseLOGGING_HTTPtoWARNINGto silence it, or keep the log where you keep secrets. See Logging.At
DEBUGlevel HARP logs more, request URLs included: do not run production atDEBUG.LOGGING_SQLlogs the SQL statements, which can carry recorded data.
Secrets in configuration¶
Settings that hold secrets (storage.url with its database password, the notification webhooks) are better out of
files you commit. Pass them as HARP_* environment variables (HARP_STORAGE__URL) fed by your secret store or a
protected file, not with --set, which shows in the process list and in shell history. See
Configuration.
Callers and request handling¶
Client address. HARP strips the
X-Forwarded-For,ForwardedandX-Real-IPheaders a client sends and appends the peer address upstream. Behind a load balancer, list its ranges inproxy.trusted_proxies(empty by default), or the upstream sees the load balancer as the client. List only proxies you operate. See Forwarding headers and trusted proxies.Request framing. A request carrying both
Content-LengthandTransfer-Encodingis refused with400and the connection is closed, before anything goes upstream.Slow clients. A client silent for longer than
proxy.client_read_timeout, or uploading underproxy.min_request_body_rate, gets408and its request is not forwarded. See Proxy.Paths. A path that could leave the subtree of the endpoint URL (dot segments in any encoding, control bytes) gets
400and nothing is sent upstream. See the path guard in Proxy.
Metrics¶
Prometheus metrics are served on /metrics on the dashboard port, under the dashboard login.
dashboard.metrics_port serves them on a dedicated port without authentication: leave it unset, or restrict the
port outside HARP. The proxy ports never answer a metrics path: /.prometheus/metrics is proxied upstream like any other path. See Prometheus.
Errors and debug mode¶
JSON error responses carry the error name and status, and the traceback goes to the log. With HARP_DEBUG they
include the traceback, values and file paths: never set it in production. See Debug Mode.
Rules¶
Rules are Python scripts that run inside the proxy process, with its privileges. They are
no sandbox: load only rules written by people allowed to administer the proxy. A failing script does not take the proxy
down: on_request fails closed (the client gets 500 and the request is not forwarded), the other events fail
open, and a script over its time_budget is abandoned. See Execution.
TLS¶
HARP serves plain HTTP on all its ports. Terminate TLS in front of it (load balancer, ingress, sidecar) for the proxy
ports and the dashboard, and use https URLs for the endpoints’ upstreams.