-
Notifications
You must be signed in to change notification settings - Fork 12
Attack Surface
This page is the "what does an attacker see, and what do we do about it" map. Useful for security reviews, threat modelling, and answering "have you thought about X" questions in tender responses.
For the catalogue of defensive features (how to enable rate limiting, where the audit log lives, etc.), see the in-repo docs/security.md. This page is the inverse: where the holes are if any of those defences fail.
In a typical deployment:
| Surface | Exposed to | Auth |
|---|---|---|
https://<host>/ (login UI) |
Public internet (or Knocknoc-gated) | None at the HTTP layer; OIDC starts here |
/auth/oidc/login, /auth/oidc/callback
|
Public | Owned by the OIDC handshake |
/api/* |
Public (cookie or API key) | OIDC session cookie or Authorization: Bearer rgu_...
|
/ws/<session_id> |
Public | OIDC session cookie or one-shot WebSocket ticket |
/share/<token> |
Public | Share token (HMAC, time-bounded) |
/static/*, /guac/*
|
Public | Static assets, no auth |
| guacd TCP port (4822) | Loopback only | TLS, no other auth (rustguac is the only thing that should reach it) |
| Xvnc displays (6000-6099) | Loopback only | None (one-time per-session display number) |
| Docker socket | Local Unix socket | Filesystem permissions (docker group) |
| Vault HTTPS | rustguac to Vault | AppRole + token |
An exposed login page is a probe target. Attackers don't even need to break OIDC; they can fingerprint the application, look up known CVEs, or run automated credential-stuffing against any auth that ends up in a redirect chain.
Mitigations in rustguac:
- OIDC handles authentication; we don't do password forms ourselves, so there's nothing to credential-stuff.
-
tower_governorrate-limits/auth/*per-IP. - The
groupsclaim coming back from the IdP is bounded (64 entries x 256 bytes) so a malformed/hostile IdP can't bloat theseen_groupstable. - WebSocket Origin/Host check (CSWSH) on
/ws/*rejects cross-origin upgrade attempts.
Mitigations outside rustguac:
-
Knocknoc integration gates
/behind identity + MFA at the HAProxy layer. The login page returns 403 to anyone who hasn't authenticated to Knocknoc first. - Reverse proxy WAF rules (HAProxy ACLs, Cloudflare, etc.) for known bad patterns.
Share tokens let third parties join a session without an account. The blast radius is one session. We've audited this surface a few times; the shape that's fallen out:
- Tokens are HMAC-signed, with the session ID baked in.
- Each token has a TTL, an issuer (the user who minted it), and a role (read-only viewer or full collaborator).
- Every shadow-token use is logged in
token_audit_logwith the connecting IP. Mint events were already logged; v1.6.1 added the use-event so a leaked token is visible every time it's redeemed within its TTL. - Per-entry
allow_sharingflag (admin-controlled) gates whether the Share button appears at all.
A leaked share token is bad but bounded: the session is one user's, the entry has a known target, the audit log shows where the token was redeemed from. Rotate the entry's password and revoke the session.
API keys are SHA-256-hashed, IP-allowlisted, expiry-bounded, and role-scoped (viewer / operator / poweruser / admin). Created via rustguac add-token (CLI) or the admin UI.
Watch for:
- API keys committed to git or CI logs. We redact them in our own logs (
{redacted}placeholder for any string starting withrgu_); your CI / external systems are on you. - Old keys still in the database. The admin UI shows last-used timestamps; rotate or delete keys that haven't been used in a while.
Connection entries live in Vault at <base>/shared/<folder>/<entry> or <base>/instance/<name>/<folder>/<entry>. A path-traversal bug here would let an authenticated user read entries outside their allowed folder.
What's enforced:
-
vault.rs:validate_name()rejects any name containing/,\, control chars, or anything that isn't[a-zA-Z0-9._-]. Names are 1-64 chars. -
vault.rs:validate_path()does the same per-segment for folder paths. -
.config,.,..are reserved. - v1.5.4 closed an audit finding here; the validation is unit-tested for the obvious traversal patterns.
Recordings are stored as <recording_path>/<session_id>.guac with companion .meta (JSON metadata) and .thumb (JPEG) files. They contain the full session: keystrokes, screen captures, file transfers.
Access controls:
- Recordings list is
poweruser-scoped (added in v1.5.4; viewers don't see them). - File mode is
0o640and ownership matches the rustguac user. - Recordings can contain credentials typed into RDP/SSH login screens. Treat them as sensitive.
Per-user Docker containers run with nosuid,nodev mount options. They get a generated random password per session (or chpasswd-updated on container reuse). Container names are deterministic (rustguac-vdi-<username>) so a single user can't spawn arbitrary container counts.
Watch for:
- BYO VDI images. If you let users pick
container_image, you're trusting whatever's in those images. The[vdi].allowed_imagesconfig gates this to a list. - Docker socket access. The
rustguacuser is in thedockergroup, which is essentially root-equivalent on the host. Accept this in your threat model or run rustguac in a separate VM.
Long sessions can produce huge recordings. We added rotation in v1.0.x: per-entry max_recordings, global max_disk percent threshold, time-based cleanup interval. Configure in [recording] section. If recordings are filling disk, audit the per-entry caps.
We trust the OIDC IdP to assert identity and groups. If your IdP is compromised, you're in trouble regardless of rustguac. We do clamp the groups claim to 64 x 256 bytes to protect against an IdP returning unbounded data.
- guacd. We bundle it from upstream and treat it as a trusted process. Bugs in guacd's RDP/VNC/SSH parsers would be guacd CVEs, not rustguac CVEs.
- The host OS. rustguac runs as a non-privileged system user but doesn't sandbox itself further. Container or VM isolation is your job.
-
The reverse proxy. rustguac trusts
X-Forwarded-Forand similar headers fromtrusted_proxies(configurable). If you don't list them, headers are ignored. Don't add a public IP to that list. - Vault. Connection credentials are written to Vault at admin time and read on connect. We don't cache them in the SQLite DB.
Security issues should go to the project maintainers privately first. The repo's SECURITY.md (if present) has the contact details. We'd rather hear from you under embargo than read it on a CVE feed.
- Knocknoc integration
- In-repo
docs/security.mdfor the defensive feature catalogue
Background
Deployment
Security
Help