Security Policy
August 14, 2026 · View on GitHub
Supported versions
| Version | Supported |
|---|---|
| Latest release | ✅ |
| Older releases | ❌ |
Reporting a vulnerability
Please do not open a public GitHub issue for security vulnerabilities.
Report security issues by emailing security@bytestrix.com with:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Any suggested fix (optional)
You will receive an acknowledgement within 48 hours and a full response within 7 days. We will coordinate a fix and disclosure timeline with you.
Scope
The following are in scope:
- Authentication bypass in the relay server
- Arbitrary command execution via the agent API
- Sensitive data leakage (secrets not redacted before leaving the VM)
- WebSocket message injection or spoofing
The following are out of scope:
- Attacks that require physical access to the VM
- Social engineering
- Vulnerabilities in third-party dependencies (report those upstream)
Security model
InfraCanvas (OSS) runs as a single binary on each machine. The dashboard, relay, and agent all live in the same process. The two surfaces that need protection:
- Exposed URL — by default the binary binds
127.0.0.1:7777and a bundledcloudflaredopens an outbound-only Cloudflare quick-tunnel that publishes a randomhttps://*.trycloudflare.comURL. Traffic is HTTPS-terminated at Cloudflare's edge; no inbound firewall rule is required. The URL is unguessable but not secret — pair it with the auth token below.--no-tunnelbinds0.0.0.0:7777directly (you allow the port in your cloud security group).--privatebinds127.0.0.1and you reach it through SSH tunnel. - UI auth token — every install generates a random 24-character token (saved in
/etc/infracanvas/config.env). Required as a query param on first load, then stored in an HTTP-only cookie. WebSocket calls require it too.
Once authenticated, the dashboard can:
- See the full topology of the host
- Read container logs
- Open a shell inside any container or on the host
- Run Docker / Kubernetes actions (restart, scale, update image)
Treat the URL+token like an SSH key for the box.
- Secret redaction — env vars whose names contain
SECRET,TOKEN,KEY,PASSWORD,CREDENTIAL,AUTH,PASSWDare replaced with[REDACTED]before they leave the discovery layer. - No persistent storage of graph state — graphs are recomputed every refresh; the relay does not persist snapshots between restarts. The one exception is Clusters (see below), which by design persists a credential locally so the connection survives a restart.
- Pair codes — generated by the agent for legacy shared-relay deployments. In the default local serve-mode the browser auto-pairs from loopback and pair codes are unused.
- Clusters (kubeconfig direct-connect) — an uploaded kubeconfig is written to
<state dir>/clusters/<id>.kubeconfigat0600, readable only by the user runninginfracanvas serve, and is never transmitted anywhere — it's read directly by the same process serving your dashboard. Deleting a cluster from the UI deletes this file. Anyone with shell access to the box as that user (or root) can read it, same as they already could read~/.kube/configor any other credential on the machine — this introduces no new privilege beyond what local shell access already grants. - Read-only mode.
--read-onlyblocks every mutating action, terminal/exec session, and — as of v0.18.1 — cluster connect/disconnect, at the relay before it reaches the agent or any credential-executing code path. Viewing topology and logs still works. Use this for public demos. - Per-cluster read-only (v0.19.0+). Independent of the global
--read-onlyflag, each Clusters connection has its own read-only toggle — set it when connecting, or flip it later — so you can run some clusters view-only while others stay fully interactive on the same dashboard. Enforced at the same relay layer as global read-only mode. - Permission preview (v0.19.0+). Before you commit to connecting a kubeconfig,
POST /api/clusters/preview(surfaced in the Add Cluster dialog) runs a handful ofSelfSubjectAccessReviewchecks against it — a standard Kubernetes API any authenticated credential can call about its own permissions — and shows what it can actually do (view, exec, restart, scale, read Secrets) before anything is persisted or connected. - Audit log (v0.19.0+). Every write action, terminal session, and read-only-blocked attempt is recorded to
<state dir>/audit.log(append-only,0600) and viewable from the dashboard's Audit tab orGET /api/audit. Entries are attributed by session/machine — OSS has one shared UI token authenticating the whole dashboard, no per-user login, so that's the honest ceiling on what can be logged here (the hosted SaaS product has real user accounts and its own per-user audit log). - Hub mode (
--join). Joining agents authenticate with a shared join token, but that token alone no longer proves which machine a connection is. As of v0.18.1, the first time a machine ID connects it's issued a random resume secret; every later reconnect claiming that same machine ID must present it, or the connection is rejected outright rather than silently swapped in over a session with browsers already attached./api/sessions(the machine roster) requires the dashboard's own UI token, not the shared join token. - Hardening (v0.19.1+). All token/secret comparisons (UI token, agent token, hub resume secret) are constant-time. Token generation fails closed instead of falling back to a hardcoded value if the system's secure random source ever errors. Every auth-checking endpoint has a per-IP rate limit (20 failed attempts/minute). WebSocket frames have a size cap to prevent a memory-exhaustion DoS. Every HTTP response carries CSP, X-Frame-Options, X-Content-Type-Options, and HSTS-when-TLS headers.