Skip to main content

Security

Reporting a vulnerability​

Please do not open a public issue. Use GitHub's private vulnerability reporting on the repository (Security → Report a vulnerability) with the affected version, a description, reproduction steps and the expected impact.

Acknowledgementwithin 3 working days
Initial assessmentwithin 10 working days
Fix for critical and high issuesaimed within 30 days

Test only against installations you own. Supported: the latest 1.x minor release; security fixes ship as patch versions.

In scope: the backend, portal, agent, site connector, installers, the self-update chain, and the production deployment files in deploy/. Out of scope: findings that need an already compromised Windows machine with local administrator rights (the agent runs as SYSTEM by design), a tenant admin attacking their own tenant, volumetric denial of service, and scanner output without a demonstrated impact.

Threat model​

Attackers considered:

  • a user of one tenant who wants data or control in another tenant;
  • a lower role (viewer, technician) who wants more than the role grants;
  • anyone on the Internet who can reach the portal, the agent and connector endpoints, and the files host;
  • a standard Windows user on a managed PC who wants SYSTEM through the agent;
  • someone who obtains a copy of the database, a backup, a log file or a URL.

Trusted by design: the agent runs as SYSTEM and executes what the server sends; a tenant's package managers and script authors can run any code on that tenant's devices; a global admin controls everything.

Controls​

AreaControl
NetworkAgents and connectors connect outbound only; per-installation keys stored as hashes; enrollment tokens with expiry and use limits.
Tenant isolationApplication checks plus PostgreSQL row-level security with composite tenant foreign keys (Multi-tenancy); cross-tenant fuzz test.
AuthorizationEvery route has an explicit access rule; an RBAC matrix test covers every route × caller.
Passwords, two-factor, sessionsOn Entrosity Hub only: argon2id; 12–256 characters; common passwords refused; lockout and sign-in limits; TOTP with 10 recovery codes; rotating session cookie with reuse detection (below). Axis's database holds no credentials.
Sign-inAxis accepts only EdDSA product tokens signed with a key from the Hub's key set, issued by RMM_PLATFORM_URL for audience rmm, without a purpose (30 s clock leeway). Roles come from Axis's copy of the Hub's data, never from the token; sessions the Hub ended are refused; user state is re-checked every 30 s. Sensitive actions (deleting enrollment tokens) need a two-minute step-up token bound to the user and session and usable once on any replica; the Hub issues it for the password against the session cookie (same CSRF guard as product tokens, rate-limited like sign-in).
Secrets at restAES-256-GCM with RMM_MASTER_KEY (key ID stored for rotation): AD passwords, push tokens, queued e-mails. The Hub encrypts its TOTP seeds and e-mails with PLATFORM_MASTER_KEY.
Event streams60-second tenant-bound stream tokens (HS256, RMM_JWT_SECRET); product tokens never in URLs.
WebSocketsBrowser-originated upgrades (with an Origin header) are refused.
UploadsBrowser → object storage with presigned PUT; size and SHA-256 verified on finalize; stored as application/octet-stream; files host sends a sandbox CSP.
DownloadsPresigned one-hour links minted at delivery; agents verify the SHA-256 before running anything; the connector verifies the pushed MSI.
Self-updateReleases signed with Ed25519 at publish; verified against the public key compiled into the build; unsigned builds refuse updates; watchdog rollback.
Command injectionScript parameters arrive as environment variables and a JSON file, never interpolated; cmd metacharacters refused in cmd parameters and uninstall arguments; uninstall by name only uses machine-wide registry entries; shutdown.exe arguments never pass through a shell.
RevocationDecommissioning, deleting a connector or suspending a tenant closes connections and drops cached keys on every replica.
HeadersHSTS, strict CSP, DENY framing, nosniff, Referrer and Permissions policies.
AuditEvery mutating call and every job creation is audited in the same transaction; the log is append-only for the application role; secrets redacted.
LogsSecrets and presigned URL signatures are redacted.

Sign-in with Entrosity Hub​

Entrosity Hub holds the credentials and Axis holds none: it verifies tokens and reads roles from its copy of the Hub's data.

ConcernControl
Stolen product tokenFive minutes, audience-bound (useless against the Hub or another product), ended sessions refused after the next pull.
Roles in tokensNone: a demotion or removal applies at the next pull plus the 30 s cache, whatever the token says.
Cross-site use of the cookieSameSite=Strict plus a same-origin check (Sec-Fetch-Site / Origin) on every cookie route, because hub., files. and the old manage. and portal. hosts are the same site.
Signing keysEd25519 (PLATFORM_JWT_SIGNING_KEY), rotated with _OLD while both are published; Axis refetches the key set for an unknown kid at most once a minute and keeps the last good set.
Internal APISeparate listener never routed by Caddy (the path is also refused at the edge), per-product bearer tokens compared in constant time.
Sensitive actionsStep-up tokens: two minutes, bound to user and session, single use across Axis replicas.
Revocation lagDisabling a user, removing a role or signing out reaches Axis within the sync interval (30 s) plus the principal cache (30 s).
Copy semanticsUsers missing from the snapshot are marked deleted and organizations suspended; nothing is hard-deleted, so data and attribution survive a platform mistake.

Supply chain​

  • Every push and pull request runs govulncheck, gosec, pnpm audit, gitleaks and a Trivy scan of the production images (.github/workflows/security.yml), plus a weekly run.
  • Releases are blocked on fixable HIGH/CRITICAL image findings, pushed to ghcr.io and published with SPDX SBOMs.
  • Dependencies are pinned (go.sum, pnpm-lock.yaml, image digests, deploy/caddy/go.sum) and updated by Dependabot.
  • Images run as non-root (distroless nonroot backend; Caddy as uid 10001).

The full review with evidence and file references is on Security review (1.0).