Chore: Add control-plane security audit stage

(cherry picked from commit ae636a2b24)
This commit is contained in:
Herbert Wolverson committed 2026-06-14 15:14:14 -05:00
1 parent d5601933e1
commit de92c5f0c5
2 files changed
+288 -5

No files matched your search

+100 -5
View File
@@ -1,14 +1,15 @@
---
name: libreqos_security_audit
description: Repo-local LibreQoS workflow for the Rust dependency step of security audit passes. Use when auditing LibreQoS before a release with cargo audit, cargo machete, cargo tree, CVE triage, and audit-file findings updates.
description: Repo-local LibreQoS workflow for release security audit passes. Use when auditing LibreQoS with cargo audit, cargo machete, cargo tree, CVE triage, network control-plane exposure review, and audit-file findings updates.
---
# LibreQoS Security Audit
Use this skill for the recurring Rust dependency step of LibreQoS security
audit passes in this repo. This skill does not cover the whole product security
audit; use additional focused checks for Python, web UI, packaging, live-host
configuration, secrets, authentication, and network-facing behavior.
Use this skill for recurring LibreQoS security audit passes in this repo. It
covers the Rust dependency baseline and the network control-plane exposure
review. Use additional focused checks for Python, packaging, live-host
configuration, secrets, and authentication flows outside the control-plane
scope.
## Scope
@@ -17,6 +18,11 @@ configuration, secrets, authentication, and network-facing behavior.
- Write results into the requested audit file. If no file is named, ask before creating one.
- Distinguish security findings from maintenance warnings. "Not maintained" is not a security issue by itself.
- Distinguish reachable vulnerabilities from unused-feature or irrelevant-feature advisories. A vulnerability in a feature LibreQoS does not use is not a security finding by itself.
- Treat an open listener as evidence, not a vulnerability by itself. Record a
security finding only when the listener exposes exploitability, credential or
session leakage, unauthenticated access beyond the intended demo/public mode,
authorization bypass, brute-force exposure, remotely triggerable panic/DoS, or
a control action reachable without the expected protection.
## Step 1: Rust Dependency Audit
@@ -116,6 +122,95 @@ The audit-file section must include:
advisories.
- No placeholders, no `...`, and no unresolved `<angle-bracket>` tokens.
## Step 2: Network Control-Plane Exposure Audit
Use this step when the audit turns to external threats over the LibreQoS control
plane.
### Scope Assumptions
- Assume the operator installed the Caddy / SSL / TLS option.
- Treat Linux, Ubuntu, kernel, and distribution package vulnerabilities as out
of scope because LibreQoS cannot fix them in-repo.
- Treat the control interface as in scope. Anything listening or reachable on
the control interface should be reviewed.
- Treat the two bridge interfaces, whether XDP or Linux bridge backed by eBPF,
as out of scope for this section.
- Include the sibling `../../lqos_api/` repo as read-only audit context when it
is present, because the API is exposed behind Caddy.
- Do not edit `../../lqos_api/` unless the user explicitly authorizes
cross-repo changes.
### Evidence To Gather
Review these surfaces first:
```text
src/rust/lqos_setup/src/ssl.rs
src/rust/lqos_setup/src/web.rs
src/rust/lqosd/src/node_manager/
docs/v2.0/https-caddy.md
docs/v2.0/api.md
../../lqos_api/src/
../../lqos_api/README.md
```
Use `rg` searches for listener addresses, routes, middleware, auth checks,
cookie settings, CORS, Caddy upstreams, and panic-prone request handling:
```text
rg "bind\\(|TcpListener|listen|reverse_proxy|Caddy|CorsLayer|very_permissive|allow_anonymous|SameSite|Cookie|x-bearer|route_layer|unwrap\\(" src/rust docs ../../lqos_api
```
For each reachable service or route, identify:
- listener address and port
- whether Caddy proxies it and whether the direct port remains reachable
- authentication and authorization mechanism
- unauthenticated routes and whether they expose control, data, or only health/docs
- state-changing routes and their protection
- cookie flags, CORS policy, CSRF/origin checks, and session behavior
- rate limits or backoff for login/API authentication attempts
- request paths where malformed unauthenticated input can panic
### Triage Rules
Count these as likely security findings when evidence supports them:
- a direct control-plane HTTP listener bypasses the expected Caddy/TLS path
for authenticated API traffic
- a route that changes state or exposes sensitive operational data is reachable
without the expected auth, except for the explicit public/demo read-only mode
- an auth or request middleware can panic on unauthenticated remote input
- credentialed CORS, cookie flags, or missing CSRF/origin checks let another
browser origin use an operator session
- login or bearer-token checks lack reasonable throttling for a network-exposed
control-plane service
Do not count these as findings by themselves:
- Caddy or LibreQoS listening on a port when the route is protected as intended
- Caddy serving HTTPS for the WebUI/API path
- public API documentation that exposes only endpoint shape and no secret or
state-changing capability
- `allow_anonymous` when the operator intentionally enabled the documented
public/demo read-only mode
- first-run setup exposure when the setup token and lifecycle are being audited
separately, unless the current section finds a concrete bypass
### Audit Section Checklist
The audit-file section must include:
- Heading: `Network control-plane audit`.
- Date, scope assumptions, and exact files or directories reviewed.
- Summary bullets separating findings from observations.
- One subsection per finding with the repo-relative path, short description,
exposure/threat, and recommended actions.
- A separate observations / not-findings subsection for open listeners or public
docs that are intentional and not vulnerable by themselves.
- No placeholders, no `...`, and no unresolved `<angle-bracket>` tokens.
## Validation
- Re-read the audit section before returning; remove placeholders and vague conclusions.
+188
View File
@@ -94,3 +94,191 @@ findings under this audit policy:
These should be tracked as modernization work, not listed as release security
findings unless a separate vulnerability is reported for them.
## Network control-plane audit
Date: 2026-05-15
Scope assumptions:
- The Caddy / SSL / TLS option is installed.
- Linux, Ubuntu, kernel, and distribution package vulnerabilities are out of
scope for this section.
- The control interface is in scope. The two bridge interfaces, whether XDP or
Linux bridge backed by eBPF, are out of scope for this section.
- An open listener is not a vulnerability by itself. This section looks for
exploitability, unauthorized access, credential exposure, authorization
bypass, or remotely triggerable failure on the control plane.
- The sibling `../../lqos_api/` repo is included because it is exposed behind
the managed Caddy configuration.
Files and directories reviewed:
- `docs/v2.0/https-caddy.md`
- `docs/v2.0/api.md`
- `src/rust/lqos_setup/src/ssl.rs`
- `src/rust/lqos_setup/src/web.rs`
- `src/rust/lqosd/src/node_manager/run.rs`
- `src/rust/lqosd/src/node_manager/auth.rs`
- `src/rust/lqosd/src/node_manager/local_api.rs`
- `src/rust/lqosd/src/node_manager/static_pages.rs`
- `src/rust/lqos_config/src/authentication.rs`
- `../../lqos_api/src/main.rs`
- `../../lqos_api/src/web.rs`
- `../../lqos_api/src/web_security.rs`
- `../../lqos_api/README.md`
### Summary
- The managed Caddy configuration disables the Caddy admin API, proxies the
WebUI to `127.0.0.1:9123`, and proxies `/api/v1/*` to `127.0.0.1:9122`.
- The WebUI runtime listener is configured to move to loopback for the Caddy
path. That loopback listener is not a finding.
- The sibling `lqos_api` service still binds directly to `:::9122`. If that
port is reachable on the control interface, authenticated API traffic can
bypass the Caddy/TLS path. Runtime reachability was not verified in this
code audit.
- Three control-plane findings and one reachability-unknown exposure are listed
below. Public API documentation and the explicit anonymous read-only demo mode
are recorded as observations, not findings by themselves.
### Findings
#### Reachability unknown: direct `lqos_api` listener can bypass the Caddy/TLS path
Path:
- `../../lqos_api/src/main.rs`
Short description:
`lqos_api` binds its HTTP server to `:::9122`. The managed Caddy configuration
proxies API traffic to `127.0.0.1:9122`, but the API process itself also remains
able to listen on all interfaces unless deployment firewalling blocks it.
Exposure / threat:
The API binds all interfaces while Caddy proxies the intended HTTPS path to
localhost. If port `9122` is reachable on the control interface, a client can
send the `x-bearer` credential over direct HTTP instead of the Caddy-protected
HTTPS path. This audit verified the code-level listener and Caddy upstream, but
did not verify runtime firewall or socket exposure on an installed host.
Recommended actions:
- Make the `lqos_api` listen address configurable and default it to
`127.0.0.1:9122` when the Caddy option is installed.
- Update the Caddy/setup integration and API documentation so remote operators
use only the HTTPS `/api/v1/` path.
- Add install-time firewall guidance or service hardening that blocks direct
control-interface access to `9122` when Caddy is enabled.
#### Malformed `x-bearer` header can panic API authentication
Path:
- `../../lqos_api/src/web_security.rs`
Short description:
The API authentication middleware calls `header.to_str().unwrap()` while
processing the unauthenticated `x-bearer` header.
Exposure / threat:
A remote client can send a malformed header value that fails UTF-8 conversion.
Authentication should reject that request, but the current code can panic while
handling unauthenticated input. Even if Axum/Tokio limits the blast radius to a
request task or connection, this is a remotely triggerable control-plane failure
path.
Recommended actions:
- Replace the `unwrap()` with explicit error handling that returns
`401 Unauthorized` for invalid or missing bearer headers.
- Keep malformed authentication input on the same path as other auth failures:
no panic, no stack trace, and no different response body that helps probing.
- After fixing, add a small test for a non-UTF-8 or otherwise invalid
`x-bearer` value.
#### WebUI and local API use credentialed permissive CORS
Paths:
- `src/rust/lqosd/src/node_manager/run.rs`
- `src/rust/lqosd/src/node_manager/static_pages.rs`
- `src/rust/lqosd/src/node_manager/local_api.rs`
- `src/rust/lqosd/src/node_manager/auth.rs`
Short description:
The WebUI and local API install `CorsLayer::very_permissive()`. In the
tower-http version locked by the workspace, that mirrors the request origin and
allows credentials. The WebUI session uses a `User-Token` cookie with
`SameSite=Lax`, but without `Secure` or `HttpOnly`.
Exposure / threat:
The reviewed code does not show a supported cross-origin WebUI client. For a
cookie-authenticated control-plane UI, reflecting arbitrary origins while
allowing credentials grants browser read access to origins outside the WebUI's
own origin whenever the browser sends the `User-Token` cookie. `SameSite=Lax`
limits common unrelated-site subresource requests, but this policy is still
broader than the reviewed WebUI needs. The local API and WebUI should not grant
credentialed CORS to arbitrary origins without a documented client need.
Recommended actions:
- Remove CORS from same-origin WebUI/local API routes unless a concrete
supported cross-origin client requires it.
- If cross-origin access is required, restrict allowed origins to configured
operator hosts and avoid credentialed wildcard or origin-mirroring behavior.
- Add origin or CSRF checks for state-changing browser routes.
- Set session cookies with `Secure` when served behind HTTPS and `HttpOnly`
unless browser JavaScript truly needs to read the cookie.
#### WebUI login lacks rate limiting
Paths:
- `src/rust/lqosd/src/node_manager/auth.rs`
- `src/rust/lqos_config/src/authentication.rs`
Short description:
The WebUI login path checks passwords with Argon2id for current hashes and
upgrades older SHA-256 hashes after successful login. The reviewed code did not
show per-IP, per-account, or global throttling for repeated failed login
attempts to the public `/doLogin` route.
Exposure / threat:
Under the Caddy setup, the login form is reachable through the control-plane
HTTPS entrypoint. An unauthenticated client can repeatedly submit passwords to
`/doLogin`; the server-side password hash is strong, but the reviewed code does
not throttle repeated failures before each verification attempt.
Recommended actions:
- Add rate limiting or exponential backoff for failed `/doLogin` attempts,
keyed by source address and username.
- Log repeated failures in a way operators can act on without logging submitted
passwords.
### Observations / not findings
- `src/rust/lqos_setup/src/ssl.rs` renders a managed Caddyfile with
`admin off`, WebUI upstream `127.0.0.1:9123`, and API upstream
`127.0.0.1:9122`.
- `docs/v2.0/https-caddy.md` documents moving the WebUI runtime listener to
`127.0.0.1:9123` when HTTPS is enabled.
- `../../lqos_api/src/web.rs` merges Swagger UI at `/api-docs`. This exposes
endpoint shape, not credentials or control actions, and is not counted as a
vulnerability by itself.
- `allow_anonymous` is an explicit read-only public/demo mode in the WebUI
authentication configuration. It is not counted as a finding when the operator
intentionally enables that mode.
- `src/rust/lqos_setup/src/web.rs` binds the setup web service to
`0.0.0.0:9123` and uses a setup token. Because this section assumes the
runtime Caddy option is already installed, first-run setup exposure is left
for a later setup/lifecycle audit unless a concrete bypass is found.