Files
RustProxy/docs/SECURITY.md
T
2026-10-11 17:10:04 +02:00

226 lines
14 KiB
Markdown

# Security model and limitations
This is a development source release with no executed Rust integration results in
the packaging environment. Do not expose it as a production security boundary until
the actual build, protocol tests, deployment review and an appropriate security
assessment have passed.
## Administration
Administration binds to loopback HTTP by default. Settings can enable a dedicated
administration HTTPS listener, choose the application-managed certificate or a
certificate from the inventory, persist a non-loopback bind and allow explicit DNS
Host/Origin names. Remote persisted binding is accepted only with administration TLS.
The normal installer, one-line bootstrap and reference service keep the plain-HTTP admin listener
on loopback by default. An explicit startup `--allow-remote-admin` can expose plain HTTP on a
non-loopback address; that path should remain behind a trusted firewall/private network boundary and
should be replaced by Administration HTTPS for direct remote use.
Passwords use salted Argon2id; password work has admission limits and runs off the
network I/O path. Setup requires local filesystem authority or the one-time random
capability. No default administrator/password is created.
Sessions have HttpOnly/SameSite=Strict cookies, idle and absolute expiries and
credential-generation revocation. Mutations require scheme-aware Host/Origin checks,
the request marker and a CSRF token. Local CLI reset changes the hash and invalidates
all existing sessions without editing SQLite manually. Administration HTTPS marks the
session cookie Secure; HTTP cannot. The admin certificate and bind changes are
restart-required. A dedicated metrics credential is separate from browser sessions.
The data directory and SQLite files are private. The local OS user with data-directory
access is a recovery authority and can create short-lived CLI sessions. Protect that
account, filesystem and backup copies; browser authentication does not defend against
a compromised process owner. `.env` is trusted shell code, never untrusted input.
## Backend and routing boundary
Only configured HTTP/HTTPS origins may be dialed. Absolute-form client targets and
CONNECT cannot turn the proxy into an unrestricted forward proxy. ACL is evaluated
before new TCP/TLS connections and before reuse of an existing upstream connection.
Host and route restrictions combine; route allow cannot override host deny.
Forwarded headers are rebuilt from socket identity by default. An explicit
trusted-proxy CIDR policy can accept a bounded existing chain and derive the client
IP for ACL/affinity; see INTEGRATIONS_040.md. Routing still uses the actual local
destination, never any forwarded address or host.
Private/loopback destinations require explicit approval. DNS answers pass the same
policy and optional per-backend CIDR restriction; an unsafe answer fails the entire
resolution. Validated results are used without a second DNS lookup. Special-address
blocks include common metadata/link-local targets and normalize mapped IPv6.
Listener self-target restrictions prevent simple loops and control-plane access by
default. A privileged administrator can deliberately set `accept_local_listener_risk=true`
on one backend; private/loopback destinations additionally require `allow_private=true`.
This is intended for consciously accepted local-listener use (including port 9090) and
can create a request loop or expose administration through a public vhost. It does not
relax metadata/link-local/special-address filtering or CIDR restrictions. Keep network
egress and frontend ACLs as defense in depth.
No backend `Location` redirect is followed internally. HTTPS transport encryption remains
available, but backend trust/hostname verification is off by default. Set `tls_verify: true`
for origins where certificate-chain and hostname authentication is required. With
verification disabled, a network attacker able to intercept that route may impersonate
the origin. Address/DNS restrictions still apply; the local-listener exception exists only
when explicitly accepted as above. Operator-supplied backend configuration is privileged
configuration, not a field that an unauthenticated caller can edit.
## TLS and tunnels
Private key files are size-bounded, regular files, owner-only on POSIX and opened
without following final-component symlinks. Named files cannot escape the certificate
directory by using path separators. Keep the entire directory tree private as well.
Keys and certificate name coverage are validated before publication. Generated
self-signed certificates are development material, not automatic public trust.
Existing TLS sessions/tunnels are not revoked by changing a certificate or ACL.
Policies apply to new requests and handshakes. Restart/stop the relevant process for
immediate termination until a scoped administrative close operation is implemented.
WebSocket payloads remain opaque byte streams. The proxy validates upgrade metadata,
not application-level authorization or message semantics. Frame/message limits on a
backend remain the backend's responsibility. Proxy buffers, tunnel/connection counts
and inactivity timers bound proxy-side resource usage.
Web Server PHP-FPM integration never forwards the client `Proxy` header as CGI `HTTP_PROXY`
(httpoxy protection), regardless of optional Web Service hardening. FastCGI response framing and
hop-by-hop headers are discarded and RustProxy owns downstream framing. PHP-FPM endpoints are
limited to Unix sockets or loopback TCP. Optional custom FastCGI parameters are validated static
operator values and cannot replace core CGI, `HTTP_*`, `PHP_VALUE` or `PHP_ADMIN_VALUE`; php.ini
overrides use a dedicated validated `PHP_VALUE` map. Configurable request-body, response-header
and stderr bounds keep the PHP connector resource usage explicit.
## Information and resource controls
Access logging does not collect credential headers, query values, cookies, bodies
or WebSocket payloads. Hostnames and paths are logged; never put secrets in them. Do not enable third-party verbose tracing or application logging without
reviewing their output separately. Access rings, viewer slots, auth work, updates,
DNS workers and request/connection admission are bounded. A slow log viewer does not
create an unbounded delivery queue.
Backups include TLS private keys and credential/session state. Restore matching
SQLite and certificate data together, then revoke restored sessions. Never commit
`.env`, the data directory, backups or generated certificates to a public repository.
## Not a completed security feature
Docker socket access controls, service-account metrics access,
2FA, roles, provider secret storage, certificate
expiry automation, a full special-address registry policy and independent fuzz/load
assessment remain roadmap work. Feature availability is not evidence of a completed
security audit or a performance guarantee.
## Cache, response transforms and ports (0.1.0)
Cache is enabled by default in 0.8.1 and ACLs precede lookup. Cookie (outside the
configured static-asset exception), Authorization, Range, unsafe preconditions and
request/response `no-store` remain hard shared-cache exclusions. Conditional validators
can be satisfied from a fresh object or revalidated against the origin. Response
`no-cache` with a usable validator may be retained only for mandatory validation before reuse. Origin privacy is
checked before custom header removal, and generated request IDs are not cached. Bounded
fill permits, object/entry/byte limits and generation checks protect cancellation,
update and purge paths. Purge is authenticated, CSRF-protected and audited, not a
public PURGE/BAN capability.
Compression is opt-in, with authenticated/private/no-transform exclusions. Response
rules cannot overwrite protected framing/identity fields. For compatibility with 0.26.6,
custom `Age` rules remain accepted. Response-side `Age` is deferred to the final wire stage,
after RustProxy's cache freshness/revalidation logic, so it cannot alter stored age, TTL or
cache eligibility. Server hiding is cosmetic and
must not be considered an access-control or vulnerability mitigation substitute.
Standard public 80/443 ports are supported without globally forbidding remote
origins using those ports. Local listener targets remain guarded. Review SETTINGS.md
for interface-enumeration and restart semantics. A Linux service can grant only
CAP_NET_BIND_SERVICE to a dedicated unprivileged account; the application does not
acquire capabilities itself. Public listener controls do not implicitly expose the separate admin
API. The provided systemd unit is an unexecuted example, not an audit result.
Language packs are trusted build inputs with size/type/metadata bounds. The browser
renders strings as text and falls back per key. Translations are not executable
plugins, and no directory browsing or runtime arbitrary file loading is exposed.
## 0.2.0 frontend and pool boundaries
An explicit vhost binding is checked using the accepted stream's real local IP,
port and TLS state. Host, SNI and forwarded headers cannot assert a different local
address. A shared wildcard TCP listener does not grant every vhost access on every
accepted IP. Legacy routing does not inherit additional frontend sockets. Conflicting
hostname ownership is rejected, including disabled declarations.
Binding a private IP is not an authorization mechanism or firewall. NAT, port
forwarding or another proxy can make that destination reachable. Configure network
restrictions and application authentication as well; no trusted-proxy identity or
Keepalived/VRRP service is introduced. New unavailable IP bindings fail startup.
Health probes share literal/DNS/allowlist/loop protections, including actual old
listeners and desired new listeners, and cannot follow redirects to arbitrary
origins. They are bounded and do not use user cookies or credentials. Probes can
still hit an operator-selected GET path; select a side-effect-free health endpoint.
Affinity uses opaque random tokens but is not authentication, CSRF protection or
session replication. Tokens survive compatible edits, not process restarts. Cookie
mode bypasses cache; the dedicated cookie is stripped before origin forwarding and
cannot be overridden by an origin Set-Cookie. No failed request, especially a write,
is replayed on another member to obtain a successful response.
Dedicated per-vhost security headers also cover matching-host denials/errors and
cache hits. HSTS is removed on plaintext; includeSubDomains/preload remain explicit
opt-ins. Preload directives do not enroll a domain in a browser list. Arbitrary CSP
and Permissions-Policy values are encoding-validated, not semantically audited.
Old configurations retain origin headers unless an explicit policy overrides them.
## Certificate uploads and custom pages (0.3.0)
Certificate upload is an authenticated local-control-plane mutation protected by
Origin and CSRF validation, body/per-file bounds and two-worker admission. PEM pairs
are parsed, key-matched and checked against supplied DNS names. Private immutable
files use generated simple names, mode 600 and the existing no-symlink/directory
lock protections. Public API responses/config/audit contain metadata, not key PEM.
JavaScript clears file controls after use, but does not claim secure erasure of
browser memory. Only configuration publication activates an inventory selection.
No new listener or public certificate-upload endpoint is created.
Custom HTML is fully trusted administrator-authored content served on the chosen
public vhost. It may contain executable HTML/scripts there; it is never previewed
or inserted as HTML in the admin DOM. Do not upload untrusted markup. The fixed
welcome page interpolates only the software version, not Host, URL or user input.
HTML is bounded to 32 KiB per page and by the overall 256 KiB configuration limit.
Responses use no-store; old representation encoding/length/cookies are discarded
when replacing an origin error. Authentication challenges, Allow and Retry-After
are retained, then administrator response/security rules are applied.
ACLs run before static/fallback/direct-IP pages. Even a custom status 200 for a
blocked request never authorizes an upstream fetch. Such overrides can confuse
clients and monitoring; retaining the original 401/403/5xx status is recommended.
Direct-IP HTTPS still needs a completed TLS handshake and a suitable certificate;
an HTTP welcome page cannot remove a TLS certificate-name error.
## Integration boundaries in 0.4.0
Tinyauth runs after host/path ACLs and before any cached/static/origin response or
WebSocket upgrade. Incoming identity headers are stripped, including after generic
header rules, and only configured fields from a successful verifier are copied.
The verifier gets no request body or admin session cookie. Its DNS/private-network
and TLS checks match the origin safety boundary. Authentication failure cannot be
turned into a 200 by custom pages. Denials and protected responses are no-store;
there is no auth-result or protected-content shared cache. Existing upgraded
sessions are not reauthenticated continuously.
Only configured login-origin redirects are accepted. Do not expose the fake-auth
helper or treat its test cookie as an actual login. H3 uses the same routing,
auth, request limits and response policies; 0-RTT is disabled. Transport resource
limits do not replace rate limiting, firewalls or independent security/conformance
testing. H3 is uncompiled/unexecuted in this packaging environment.
Diagnostic response fields are hidden by default, but origin secrets still need
explicit response rules. In 0.5.0 the dedicated controls expose request ID and
revision independently; the obsolete hide_mock_origin field is not accepted. X-Mock-Origin can be
removed using ordinary response-header rules. Scope labels on listeners never
enforce network restrictions. See ACME_RFC2136.md for private TSIG/account storage
and the separate backup requirement for manual DNS-01 issuance.
## User-Agent blocklists
RustProxy can apply an optional global User-Agent blocklist and an additional per-vhost blocklist. Matching is case-insensitive; plain text matches a substring and `*` is a wildcard. Policies are bounded and disabled by default. They are evaluated before opening an upstream; a match returns HTTP 403. Requests without a User-Agent are not denied by this rule.
User-Agent is client-controlled and easy to spoof, so this is an operational noise/blocking control rather than an authentication or identity boundary. Keep ACLs, authentication and rate limits as the authoritative controls.