Files
RustProxy/docs/SECURITY_AUDIT_0.31.0.md
T
2026-10-10 20:56:36 +02:00

10 KiB

RustProxy 0.31.0 - API, panel and HTTP cache-maintenance security review

Scope: source snapshot supplied for the v0.31.0 maintenance follow-up, reviewed 2026-10-10. This is a code/contract review with offline regressions, not a penetration test or proof of production safety. The supplied archive has no .git directory; the review does not establish parity with remote master.

Interfaces and trust boundaries

Surface Exposure Authentication and mutation rules Assessment
Public administration bootstrap (/, login JS/CSS, /api/auth/status, setup/login POST) Login/public administration listener Host/origin validation; request marker for unsafe bootstrap actions; one-time setup capability and login throttling Source-reviewed; live browser flow pending
Private panel HTML, JS, documentation, OpenAPI Administration listener Administrator session; Cache-Control: no-store; CSP, frame and MIME protections Source and anonymous-response test reviewed; runtime pending
/api/status, /api/system-info, /api/traffic, /api/cache, logs, config, preferences, audit Administration listener Administrator cookie session; sensitive reads not on anonymous allowlist Source and regression tests added; runtime pending
/api/events, live-log SSE Administration listener Session on subscribe and repeated validity checks; bounded viewers; heartbeat/disconnect cleanup Source-reviewed; reconnect/load pending
State-changing admin APIs (including cache/config/preferences) Administration listener Valid session, Origin/Host, X-RustProxy-Request: 1, CSRF token; audit on cache commands Source and regression tests added; runtime pending
Certificate API (/api/certificates/{id}/reload, deploy) Administration listener Narrow-scoped Key ID + Bearer grant; no general read access; HTTPS required remotely; origin validation when supplied Source-reviewed; token rotation/revocation live tests pending
Public BAN/PURGE Configured public vhost listener No admin session: separate explicit IP/CIDR allowlist (empty by default), matched listener/vhost/ACL and optional rate limit Hardened below; reverse/HTTP tests pending
Separate Prometheus metrics exporter Distinct optional listener Configured IP CIDRs and bearer-token policy; not the admin session Unchanged; deployment exposure audit pending

Findings addressed in this follow-up

  1. E0716 / compilation defect (high functional impact). error() accepts &'static str; a reference to format!(...) was a temporary. Invalid selector errors now log the detailed reason server-side and return a fixed, non-sensitive message with status 422. This removes the reported lifetime violation; the Rust compiler must still verify the entire crate.
  2. Cache protocol-scope confusion (medium). Path-only BAN/PURGE selectors could affect representations of the same host/path across both HTTP and HTTPS. Targets are now built with the trusted external scheme, validated to http/https, and the matched Host/vhost. IPv6 literal Hosts are bracketed for the URL parser. Other tenants, schemes and hosts remain outside the selector.
  3. Unbounded blocking maintenance work (medium availability risk). Cache invalidation may scan hot/cold entries. Both public HTTP and authenticated admin cache purge/selective invalidation share two maintenance slots and execute scans with spawn_blocking. Bursts receive 429 instead of piling up unbounded blocking work on Tokio core threads. The permit stays with the worker after a client disconnects. This is a concurrency bound, not a per-client rate quota or a hard time limit for disk work.
  4. Ambiguous maintenance input (medium). Duplicate TTL/confirmation or Content-Length headers, transfer-encoded bodies, nonempty bodies and upgrade requests are rejected; Content-Length: 0 remains compatible with CLI clients. TTL is limited to 1..604800 s. Broad BAN / requires the explicit confirmation header; PURGE cannot use BAN-only controls.
  5. Audit ordering (low/medium). Removal of an administrator-created BAN rule now records the audit entry before mutating state. Unavailable audit storage blocks the mutation rather than allowing an unaudited removal.
  6. Operator safety/usability (low). An EN/PL help popover beside the HTTP allowlist gives exact curl syntax, IP-only authentication semantics and reverse-proxy identity requirements. No new global config variable is added.

Existing protections checked in source

  • Admin router's exact anonymous method/path allowlist contains only the expected authentication bootstrap APIs; private docs and assets remain session-protected. Request guard rejects duplicate Host/Origin and checks browser Sec-Fetch-Site cross-site signals.
  • Mutation guard uses an Origin/Host match, per-session CSRF comparison and X-RustProxy-Request marker; cookie sessions are HttpOnly/SameSite=Strict and use Secure under HTTPS/trusted forwarded protocol.
  • Secure response policy includes CSP, X-Frame-Options: DENY, X-Content-Type-Options: nosniff, referrer restrictions and no-store for private material. A static scan checked 27 first-party browser JS files for direct unsafe HTML injection and dynamic eval/Function sinks; this scan cannot establish that the entire DOM is XSS-free.
  • IP trust comes from the transport peer or a validated forwarding chain only when the peer is configured trusted. An untrusted client-supplied XFF does not independently grant BAN/PURGE permission. The cache allowlist rejects /0, invalid CIDRs, duplicates and more than 32 entries. Requests are never forwarded upstream.
  • Certificate API tokens have explicit operation/ID grants and cannot use the general admin read API; Bearer grant operations are intentionally separate from cookie + CSRF authentication.

HTTP maintenance CLI (allowlisted client only)

# One URL, including the query string: 200 with JSON result.
curl -i -X PURGE 'https://site.example/assets/app.js?v=3'

# Lazily ban previously cached objects under this path: 202 with JSON result.
curl -i -X BAN 'https://site.example/assets/'

# Entire host: explicit opt-in confirmation header.
curl -i -X BAN 'https://site.example/' -H 'X-RustProxy-Confirm-Wide: 1'

# Optional BAN expiration (30 seconds).
curl -i -X BAN 'https://site.example/assets/' -H 'X-RustProxy-Ban-Ttl: 30'

Use Settings -> Cache -> HTTP BAN / PURGE IP allowlist to grant the caller IP/CIDR. This HTTP interface does not accept admin cookies/tokens as an alternative to the allowlist. The domain, Host header, external scheme and listener must select the intended vhost. Behind a reverse proxy, forward the non-standard methods untouched and replace, rather than append untrusted, XFF/Forwarded headers at the edge. Never add the entire reverse-proxy subnet as a maintenance client merely because it is trusted for forwarding.

Release security test plan (requires compiled RustProxy)

  1. On the deployment host, run cargo fmt --all -- --check, ./dev.sh check, ./dev.sh test, and ./dev.sh release. Address any compiler/regression errors before installation. cargo audit or an equivalent advisory scan requires an up-to-date advisory DB and was not run in this offline environment.
  2. Start a disposable instance with a populated cache under both HTTP and HTTPS for the same vhost/URL. Confirm PURGE returns 200 and drops the exact query variant only; BAN returns 202 and invalidates only older entries on the URL prefix. Confirm the other scheme and vhost stay hot.
  3. From a denied IP, expect 403 on BAN and PURGE. With an empty allowlist, all clients must be denied. Send duplicate TTL/confirmation/Content-Length, TE/body and BAN / without confirmation: expect 400. On a trusted-proxy hop with missing real client identity, expect 403. Test both IPv4 and IPv6.
  4. From an allowed IP, send 10 concurrent broad maintenance requests; confirm that only two workers run simultaneously and excess return 429. Verify server CPU/IO and audit entries while the cold tier is populated.
  5. Send GET requests without admin cookies to /api/config, /api/status, /api/system-info, /api/events, /api/logs, /docs/openapi.json and /assets/app.js; expect 401 and protective headers. With valid cookie but missing/bad Origin, marker or CSRF on PUT/POST/DELETE, expect 403. Test logout and password/session invalidation, live SSE reconnect/expiry.
  6. Test the Certificate API with no grant, a valid certificate-specific grant, revoked token, HTTP remote peer, and misuse against /api/config/logs. Check errors for token disclosure and audit coverage.
  7. Test actual browser startup, CSP console, output escaping and form save with malicious HTML strings; inspect reverse-proxy Host/Origin forwarding. Prefer HTTPS administration with a strict allowed_hosts list.

Residual risk / non-claims

  • Not executed in this environment: Cargo format/check/tests/release, actual Rust listener requests, HTTP/2/3, fuzzing, penetration tests, session revocation under load, proxy deployment configuration, dependency CVE scanning and long-running cold-cache resource measurements.
  • Proxy trust configuration is security-critical. A misconfigured edge that accepts forged forwarded client addresses can let external users impersonate an allowlisted IP. Restrict direct listener reachability and sanitize headers.
  • A public IP allowlist is not user authentication; anyone using an allowed IP/network can issue destructive maintenance commands. Use narrow CIDRs and optionally enforce additional controls on the edge (mTLS, firewall).
  • The two-worker concurrency bound mitigates bursts, not all denial-of-service scenarios: configured cache size and storage latency still determine scan cost. Per-IP quotas are not provided here.
  • Proxy protocol path, certificate verification, arbitrary upstream code, all application content and third-party packages are not certified safe by this audit. Production acceptance remains open until the runtime test plan is completed.