Skip to content
Rather not run it yourself? OpenRMM Cloud hosts it for you.

Security Hardening and Release Rollout

This document describes the controls currently implemented. Production defaults are strict; compatibility modes are explicit rollout/lab choices.

Normal CI verifies module checksums, builds/cross-builds release targets, tests Go code, validates installers and nginx/Compose configuration, and runs frontend tests/build. make security-check runs go mod verify plus blocking govulncheck; Dependabot covers Go modules, Docker images and GitHub Actions.

The release workflow triggers on a semantic v* tag. It requires the tagged commit to be on main, runs race/vet/test/migration/installer/deployment/frontend gates, and uses the tag as the agent VERSION. A signed local build likewise requires an explicit semantic version; relying on Makefile’s development default is rejected by release targets:

Terminal window
make signer
bin/rmm-sign -gen-key
RMM_SIGNING_KEY='<PRIVATE_KEY>' \
RMM_UPDATE_PUBLIC_KEY='<BASE64_PUBLIC_KEY>' \
make VERSION=v0.4.5 dist-signed

The private key and age identities are release/operations secrets and must never be committed. require-release-config checks that the private signing key derives the configured public update key.

CI builds agents for Windows/Linux/macOS amd64+arm64, CGO-free remote sidecars for Windows/Linux amd64+arm64, and macOS sidecars with CGO on native macOS runners. The GitHub release contains:

  • agent and sidecar binaries;
  • install.sh and install.ps1;
  • SHA256SUMS covering binaries, installers and the SBOM;
  • detached Ed25519 signatures for every agent/sidecar and SHA256SUMS;
  • SBOM.spdx.json generated by Syft;
  • GitHub build-provenance attestations for release files.

Signature files use:

rmm-ed25519-sha256-v1 <base64-signature>

rmm-sign signs the SHA-256 digest of the selected file. The production installer verifies the signed manifest, selected artifact checksum, and selected artifact signature before installation. Runtime self-update verifies detached artifact signatures.

Local make dist-signed produces binaries, checksums and Ed25519 signatures; it does not generate the CI SBOM or GitHub provenance. The release workflow also publishes the signed /dl set as the openrmm-artifacts image; the server and console images come from images.yml (see deploy/README-production.md).

The release does not Authenticode-sign Windows executables. The macOS remote-desktop bundle is code-signed with a persistent identity when the repository holds one (APPLE_SIGNING_P12), and ad-hoc otherwise; the persistent identity is what lets a sidecar update keep its Screen Recording and Accessibility grants. It is not a Developer ID: nothing is notarized or stapled. OpenRMM Ed25519 signatures protect the installer/self-update trust path but are not recognized as OS vendor trust. SmartScreen, Gatekeeper, enterprise application control and macOS TCC can still require operator policy/approval.

RMM_UPDATE_VERIFY controls the running agent:

Mode Behavior
strict Refuse missing, malformed, invalid, or untrusted agent/sidecar signatures. Production installer default.
warn Log verification failure and continue. Transitional rollout only.
off Skip signature enforcement. Explicit lab/debug use only.

For an existing pre-strict fleet:

  1. Generate/protect one signing keypair and publish signed agent plus sidecar artifacts.
  2. In warn, confirm logs report successful signature verification.
  3. Reinstall/update agents with the matching RMM_UPDATE_PUBLIC_KEY available.
  4. Move services to strict and remove unsigned artifacts.

In strict mode a failed verification leaves the running agent/sidecar untouched. Publish a ticket-aware sidecar before the corresponding agent. During that controlled rollout only, RMM_ALLOW_LEGACY_AGENT_RELAY=true permits a cached pre-ticket agent sidecar; viewers never receive the fallback. Return it to false after rollout.

--update-public-key/-UpdatePublicKey and RMM_UPDATE_PUBLIC_KEY accept a set of keys (comma/whitespace separated); an update is trusted if it verifies under any of them. This lets the signing key be rotated by pinning the new key next to the old and dropping the old one once the fleet has converged, with no reinstall. See 18-update-signing-runbook.

The current installers require:

  • an https:// agents origin;
  • OpenSSL 3.x on the endpoint for Ed25519 operations;
  • root/Administrator;
  • a fresh or still-valid enrollment token;
  • --update-public-key/-UpdatePublicKey.

Linux/macOS:

Terminal window
curl -fsSL https://agents.rmm.example.com/install.sh | sudo bash -s -- \
--server https://agents.rmm.example.com \
--token '<ENROLL_TOKEN>' \
--update-public-key '<BASE64_ED25519_PUBLIC_KEY>' \
--require-tls

Windows (elevated PowerShell):

Terminal window
& ([scriptblock]::Create((iwr -UseBasicParsing https://agents.rmm.example.com/install.ps1))) `
-Server https://agents.rmm.example.com `
-Token '<ENROLL_TOKEN>' `
-UpdatePublicKey '<BASE64_ED25519_PUBLIC_KEY>' `
-RequireTls

Production mode already writes -require-tls -update-verify strict into the service. The explicit TLS switch documents caller intent and rejects a non-HTTPS URL before work begins. The install is transactional, rejects downgrade, enrolls once before service creation, and rolls back unless the new agent reports a healthy authenticated connection.

--lab-mode/-LabMode is the only path for HTTP or an omitted update key. With no key it configures verification off; with a key it remains strict. Lab mode is only for an isolated disposable environment and must not appear in production instructions.

Normal reinstall preserves identity. After revocation, use a fresh token plus --reset-identity/-ResetIdentity. Installers themselves execute before the agent trust path exists, so obtain them from a trusted release or independently verify the signed release manifest before piping to a shell.

Production Compose binds agents/console/MCP host listeners to loopback and expects a trusted TLS edge. Source ACLs are applied at that edge; UFW rules against the loopback backend ports do not replace edge ACLs. The edge must preserve WebSockets and safely set forwarded host/proto/client address.

Provision database/admin/settings keys with deploy/ops/provision-secrets.sh. Compose consumes secret files through *_FILE; env files do not evaluate commands such as $(openssl rand ...). RMM_SETTINGS_ENCRYPTION_KEY must be stable, at least 32 characters, backed up separately, and present in production to decrypt DB-backed AI, MeshCentral and TOTP values.

The production frontend is an npm/esbuild bundle in openrmm/dist; dependencies and fonts are self-hosted. Both HTML and nginx apply an enforcing CSP. It allows same-origin scripts/styles/fonts/connections, blocks objects and framing, and does not use unsafe-eval. style-src-attr 'unsafe-inline' remains for the current UI’s inline style attributes. This is no longer a report-only Babel/CDN staging configuration.

The bearer session is kept in sessionStorage, not localStorage; non-secret UI preferences still use localStorage. CSP reduces script injection exposure but does not make a bearer safe if same-origin script execution is compromised.

  • Device revocation immediately fences new control/relay attachment, disconnects the live control socket, ends native sessions and requires fresh enrollment.
  • Credential rotation/cancellation require agent 0.4.5+, are single-flight, and use durable pending/cancellation state. Suspected compromise requires revoke and re-enroll, not rotation.
  • Native sessions use separate one-time viewer and agent tickets stored only as hashes and carried in the WebSocket handshake. The viewer is principal-bound.
  • TOTP setup/rotation stores an encrypted pending candidate. An active factor stays active until the candidate code is confirmed and promoted atomically.
  • scripts and remote terminal run as root/SYSTEM without sandboxing;
  • native media is not end-to-end encrypted; relay and TLS edge are trusted;
  • one server replica is supported; hub, relay and scheduler are not distributed;
  • /readyz checks only the database and /metrics (token-gated) is a small snapshot; there is no tracing or built-in backup alerting;
  • container vulnerability scanning is not a blocking release step.