Security Policy

August 14, 2026 · View on GitHub

Supported versions

VersionSupported
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:7777 and a bundled cloudflared opens an outbound-only Cloudflare quick-tunnel that publishes a random https://*.trycloudflare.com URL. 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-tunnel binds 0.0.0.0:7777 directly (you allow the port in your cloud security group). --private binds 127.0.0.1 and 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, PASSWD are 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>.kubeconfig at 0600, readable only by the user running infracanvas 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/config or any other credential on the machine — this introduces no new privilege beyond what local shell access already grants.
  • Read-only mode. --read-only blocks 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-only flag, 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 of SelfSubjectAccessReview checks 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 or GET /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.