Security Hardening and Release Rollout
This document describes the controls currently implemented. Production defaults are strict; compatibility modes are explicit rollout/lab choices.
CI and release gates
Section titled “CI and release gates”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:
make signerbin/rmm-sign -gen-key
RMM_SIGNING_KEY='<PRIVATE_KEY>' \RMM_UPDATE_PUBLIC_KEY='<BASE64_PUBLIC_KEY>' \make VERSION=v0.4.5 dist-signedThe 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.
Release artifacts
Section titled “Release artifacts”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.shandinstall.ps1;SHA256SUMScovering binaries, installers and the SBOM;- detached Ed25519 signatures for every agent/sidecar and
SHA256SUMS; SBOM.spdx.jsongenerated 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).
Platform signing limitations
Section titled “Platform signing limitations”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.
Update verification modes
Section titled “Update verification modes”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:
- Generate/protect one signing keypair and publish signed agent plus sidecar artifacts.
- In
warn, confirm logs report successful signature verification. - Reinstall/update agents with the matching
RMM_UPDATE_PUBLIC_KEYavailable. - Move services to
strictand 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.
Production installation
Section titled “Production installation”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:
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-tlsWindows (elevated PowerShell):
& ([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>' ` -RequireTlsProduction 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.
TLS edge and secrets
Section titled “TLS edge and secrets”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.
Frontend CSP
Section titled “Frontend CSP”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.
Credentials and relay
Section titled “Credentials and relay”- 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.
Remaining hardening
Section titled “Remaining hardening”- 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;
/readyzchecks 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.
