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.
| Acknowledgement | within 3 working days |
| Initial assessment | within 10 working days |
| Fix for critical and high issues | aimed 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
| Area | Control |
|---|---|
| Network | Agents and connectors connect outbound only; per-installation keys stored as hashes; enrollment tokens with expiry and use limits. |
| Tenant isolation | Application checks plus PostgreSQL row-level security with composite tenant foreign keys (Multi-tenancy); cross-tenant fuzz test. |
| Authorization | Every route has an explicit access rule; an RBAC matrix test covers every route × caller. |
| Passwords, two-factor, sessions | On 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-in | Axis 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 rest | AES-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 streams | 60-second tenant-bound stream tokens (HS256, RMM_JWT_SECRET); product tokens never in URLs. |
| WebSockets | Browser-originated upgrades (with an Origin header) are refused. |
| Uploads | Browser → object storage with presigned PUT; size and SHA-256 verified on finalize; stored as application/octet-stream; files host sends a sandbox CSP. |
| Downloads | Presigned one-hour links minted at delivery; agents verify the SHA-256 before running anything; the connector verifies the pushed MSI. |
| Self-update | Releases signed with Ed25519 at publish; verified against the public key compiled into the build; unsigned builds refuse updates; watchdog rollback. |
| Command injection | Script 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. |
| Revocation | Decommissioning, deleting a connector or suspending a tenant closes connections and drops cached keys on every replica. |
| Headers | HSTS, strict CSP, DENY framing, nosniff, Referrer and Permissions policies. |
| Audit | Every mutating call and every job creation is audited in the same transaction; the log is append-only for the application role; secrets redacted. |
| Logs | Secrets 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.
| Concern | Control |
|---|---|
| Stolen product token | Five minutes, audience-bound (useless against the Hub or another product), ended sessions refused after the next pull. |
| Roles in tokens | None: a demotion or removal applies at the next pull plus the 30 s cache, whatever the token says. |
| Cross-site use of the cookie | SameSite=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 keys | Ed25519 (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 API | Separate listener never routed by Caddy (the path is also refused at the edge), per-product bearer tokens compared in constant time. |
| Sensitive actions | Step-up tokens: two minutes, bound to user and session, single use across Axis replicas. |
| Revocation lag | Disabling a user, removing a role or signing out reaches Axis within the sync interval (30 s) plus the principal cache (30 s). |
| Copy semantics | Users 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.ioand 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
nonrootbackend; Caddy as uid 10001).
The full review with evidence and file references is on Security review (1.0).