Skip to main content

Security Hardening

This guide covers how to harden a Symphony deployment beyond the defaults. Symphony ships with secure defaults for most settings, but production deployments should review each area described in this guide and apply the recommendations that match their environment.

HTTP Security Headers

The bundled nginx reverse proxy sets security headers on all responses. If you use your own reverse proxy, configure equivalent headers.

Headers Set by the Bundled Proxy

HeaderValuePurpose
Strict-Transport-Securitymax-age=31536000; includeSubDomainsForces browsers to use HTTPS for one year
X-Content-Type-OptionsnosniffPrevents browsers from guessing Content-Type
X-Frame-OptionsSAMEORIGINBlocks embedding in third-party iframes
Content-Security-PolicySee Content Security PolicyControls which resources the browser may load

Content Security Policy

The default CSP restricts the browser to loading resources from the same origin, with targeted exceptions for Symphony's runtime requirements:

default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline' blob:;
style-src 'self' 'unsafe-inline';
font-src 'self' data:;
img-src 'self' data: https:;
connect-src 'self' wss: https://<oidc-host>;
frame-src 'self';
frame-ancestors 'self';
object-src 'none';
base-uri 'self';
form-action 'self'

The connect-src directive automatically includes the OIDC provider's origin when an external provider is configured. This allows the browser to reach the provider's .well-known/openid-configuration, token, and userinfo endpoints. No manual configuration is needed for bundled or standard deployments.

Why 'unsafe-eval' is required for scripts: Extensions provide UI components as JSX source code. Symphony compiles these in the browser using Babel, which uses new Function() internally. Removing 'unsafe-eval' would prevent extension UIs from rendering.

Why blob: is required for scripts: Extensions can share code between page resources by registering a text/symphony-module resource. Symphony transpiles each module in the browser, wraps the generated JavaScript in a Blob, and rewrites @symphony/extension/<ext>/<module> imports to point at the resulting blob: URL. Removing blob: would make the module mechanism unusable and break any extension that imports shared helpers between its pages.

Why 'unsafe-inline' is required for styles: The Material UI framework injects CSS at runtime using <style> elements. This is standard behaviour for CSS-in-JS libraries and cannot be avoided without a nonce-based approach, which is not currently supported.

tip

Extensions can override response headers for their own API endpoints. This is by design—it allows extensions to set custom Content-Disposition, caching, and CORS headers for their services. If you need to restrict this, apply header overrides at the reverse proxy layer.

Custom Reverse Proxy Configuration

If you run your own nginx (or another reverse proxy) instead of the bundled one, add these headers to your server block:

# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;

The Content-Security-Policy header is set by the Symphony API server and does not need to be configured in the reverse proxy.

warning

Do not remove 'unsafe-eval' or blob: from script-src, or 'unsafe-inline' from style-src. Doing so will break the Symphony UI and extension rendering.

TLS Configuration

All production deployments should use TLS for every communication path:

  • HTTPS for the UI and REST API (reverse proxy termination)
  • WSS for browser WebSocket connections (reverse proxy termination)
  • NATS TLS for native extension and CLI connections

See TLS and Certificates for setup instructions. Key recommendations:

  • Use certificates from a trusted CA (not self-signed) in production
  • Enable NATS TLS when extensions connect over untrusted networks
  • Automate certificate renewal with certbot or cert-manager
  • Use TLS 1.2 or later (ssl_protocols TLSv1.2 TLSv1.3)
  • Restrict cipher suites to strong algorithms (ssl_ciphers HIGH:!aNULL:!MD5)

Process Isolation (Linux)

The bundled systemd service file applies OS-level hardening:

NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/symphony
PrivateTmp=true

These settings ensure the Symphony process:

  • Cannot gain new privileges through setuid binaries or capabilities
  • Cannot write to /usr or /etc (only /var/lib/symphony is writable)
  • Cannot access home directories
  • Has its own private /tmp
tip

If you write your own systemd unit file, include these directives. They are automatically applied when using the packaged service.

Network Exposure

Minimise the attack surface by exposing only the ports required for your deployment:

PortProtocolPurposeExpose externally?
443TCPHTTPS (via reverse proxy)Yes
4222TCPNATS (extensions and CLI)Only if extensions run on remote hosts
80TCPHTTP redirect to HTTPSYes (redirect only)
8080TCPSymphony HTTP (internal)No—proxied via 443
9222TCPWebSocket (internal)No—proxied via 443

For Docker Compose deployments, the bundled configuration already limits port exposure. For Kubernetes, use NetworkPolicy resources to restrict pod-to-pod traffic:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: symphony
spec:
podSelector:
matchLabels:
app: symphony
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels:
app: ingress-controller
ports:
- port: 8080
- port: 9222
- from:
- podSelector:
matchLabels:
role: extension
ports:
- port: 4222

OIDC Provider Hardening

Symphony delegates authentication to an external OIDC provider. Ensure your provider is configured securely:

  • Require HTTPS for the issuer URL—Symphony rejects HTTP issuers
  • Use short-lived tokens—configure the OIDC provider to issue tokens with a reasonable lifetime (e.g., 1 hour)
  • Restrict redirect URIs—only register your Symphony instance's callback URL
  • Enable MFA—configure multi-factor authentication in your identity provider for administrative users

See OIDC Configuration for provider-specific setup guides.

Cryptographic Identity Protection

The symphony.config file contains cryptographic identity keys that form the root of trust for the deployment:

  • symphony.jwt, symphony.seed, symphony.signingseed
  • operator.jwt, operator.seed, operator.signingseed
  • storage.salt

These keys are generated during initial setup and cannot be changed afterward. If they are lost or compromised:

  • Lost: Existing user accounts, API keys, and extension tokens become unverifiable. You must re-run the setup wizard, and all users and extensions must re-authenticate.
  • Compromised: An attacker could forge account tokens. Rotate by re-running the setup wizard and re-issuing all credentials.
warning

Protect symphony.config with appropriate file permissions. On Linux, the file is owned by the symphony user and is not world-readable. Do not store it in version control or expose it in container logs.

See Backup and Recovery for guidance on safely backing up these keys.

RBAC Configuration

Symphony supports role-based access control with three built-in roles (symphony-admin, symphony-editor, symphony-viewer) and the ability to define custom roles with fine-grained subject-based permissions.

Key hardening steps:

  • Set an admin subject or group in symphony.config to exit bootstrap mode (which grants admin to all authenticated users)
  • Map OIDC groups to roles using the rbac.groups_claim configuration
  • Restrict extension visibility—roles can limit which extensions a user can see and use via subject-based permissions
  • Audit user accounts periodically via Administration > Users

See RBAC Configuration for details.

Checklist

Use this checklist to verify hardening for a production deployment:

  • TLS enabled on all communication paths (HTTPS, WSS, NATS)
  • Certificates from a trusted CA (not self-signed)
  • Automated certificate renewal configured
  • Security headers present (verify with browser DevTools or curl -I)
  • Ports 8080 and 9222 not directly exposed (proxied via 443)
  • Port 4222 restricted to extension/CLI networks
  • OIDC provider uses HTTPS and short-lived tokens
  • MFA enabled for administrative users in the OIDC provider
  • RBAC configured with explicit admin subject or group (not bootstrap mode)
  • symphony.config protected with appropriate file permissions
  • Backups configured and tested (see Backup and Recovery)

See Also