-
Notifications
You must be signed in to change notification settings - Fork 0
Security
Security is woven into every layer of MuxCore. Most self-hosted media software treats security as an afterthought — local-only access, no audit trail, hardcoded API keys. MuxCore builds authentication, authorization, encryption, and audit logging into the platform from day one.
Think of MuxCore security like a building with multiple layers:
- Front door (Authentication) — Who are you? Prove it with a password, API token, or SSO.
- Room keys (Authorization) — What are you allowed to do? Admins can do anything. Viewers can only browse.
- Security cameras (Audit Logging) — Everything is recorded: who did what, when, from where.
- Locks on internal doors (Call Policy) — Even modules need permission to talk to each other.
- Encrypted hallways (TLS) — Data in transit is always encrypted between machines.
Authentication is module-driven — the core doesn't authenticate users directly. You install an auth module that connects to your existing identity system:
| Auth Module | How It Works |
|---|---|
| Local Accounts | Username + password + optional 2FA, stored locally |
| API Tokens | Scoped, revocable tokens for programmatic access |
| OAuth/OIDC | Authentik, Authelia, Keycloak, Google, GitHub |
| LDAP / Active Directory | Enterprise directory integration |
| Plex Auth | Use your existing Plex account |
Multiple auth modules can be active simultaneously. Your family uses Plex auth. Your scripts use API tokens. Your admin uses OIDC.
{
"name": "readonly-monitor",
"scopes": ["events:read", "status:read"],
"expires": "2027-01-01T00:00:00Z"
}Tokens are scoped and revocable. A monitoring dashboard gets read-only access. A download automation script gets download permissions only.
Once authenticated, every action is checked against role-based permissions:
| Role | What They Can Do |
|---|---|
| Admin | Full system access — manage users, modules, settings |
| Manager | Manage media — approve requests, edit metadata, delete |
| User | Request media, view library, manage their own requests |
| Viewer | View library only — no requests, no changes |
| Module | Scoped to the module's declared needs |
policies:
- role: user
can: [media.request, media.view]
on: [movies, tv, music]
- role: manager
can: [media.approve, media.delete, media.edit]
on: [movies, tv, music, books]
- role: admin
can: ["*"]
on: ["*"]Modules need permission to talk to each other. A downloader shouldn't be able to call the user management module. The Call Policy system enforces this:
// Module declares who it is
ctx = contracts.WithCallerID(ctx, "downloader-qbittorrent")
// Mesh client checks: "can downloader-qbittorrent call GetUsers on auth-local?"
result, err := meshv1.NewModuleMeshClient(conn).Call(ctx, &meshv1.CallRequest{TargetModule: "auth-local", "GetUsers", payload)
// → Denied. Downloaders can't access user management.When no call policy module is registered, all inter-module calls are allowed (open mode, suitable for single-node setups).
Every significant action is recorded:
[2026-05-26 14:32:01] user:ender action:media.request resource:"The Terminator" trace:abc123
[2026-05-26 14:32:05] system action:workflow.started resource:movie-request trace:abc123
[2026-05-26 14:32:45] module:downloader-qbittorrent action:download.started resource:torrent:xyz trace:abc123
[2026-05-26 15:45:12] module:media-movies action:library.item.added resource:"The Terminator" trace:abc123
Audit entries include:
- Who — user ID, system, or module ID
- What — the action performed
- On what — the resource affected
- When — timestamp
- Trace ID — links related actions across services
- PrevEntryHash — SHA-256 of the previous entry, forming a tamper-evident chain
- Signature — optional HMAC-SHA256 for cryptographic verification
Core ships with a built-in JSONL file logger. Set audit.path in muxcore.json or MUXCORE_AUDIT_PATH environment variable to enable it:
{
"audit": {
"path": "/var/log/muxcore/audit.jsonl"
}
}When no path is configured, the audit logger is a no-op — all Log/Query/Export calls return immediately with zero overhead. This means audit logging is safe to wire by default and costs nothing until you turn it on.
For production, swap in a module-based audit logger (e.g., database-backed with search indexing) by implementing the AuditLogger contract.
TLS is configured with certificate and key files. Without TLS, the server runs in plaintext (development only):
{
"server": {
"cert_file": "/etc/ssl/cert.pem",
"key_file": "/etc/ssl/key.pem"
}
}Environment variables: MUXCORE_TLS_CERT, MUXCORE_TLS_KEY
gRPC communication between nodes uses TLS:
{
"grpc": {
"cert_file": "/etc/ssl/grpc-cert.pem",
"key_file": "/etc/ssl/grpc-key.pem",
"mtls_enabled": true,
"ca_cert_file": "/etc/ssl/ca.pem"
}
}Plaintext mode logs a warning — it works but is not recommended for production. Mutual TLS (mTLS) verifies both ends of the connection, preventing unauthorized nodes from joining the mesh.
Environment variables: MUXCORE_GRPC_TLS_CERT, MUXCORE_GRPC_TLS_KEY, MUXCORE_GRPC_MTLS_ENABLED, MUXCORE_GRPC_MTLS_CA
Nodes must present a pre-shared token to join the cluster:
{
"grpc": {
"join_token": "my-secret-cluster-token"
}
}Or: MUXCORE_CLUSTER_JOIN_TOKEN=my-sec...ken
Storage overlay modules handle encryption transparently. Data is encrypted before writing to any storage backend:
Module writes data → Encryption overlay encrypts → Storage backend stores ciphertext
Module reads data ← Encryption overlay decrypts ← Storage backend returns ciphertext
The EncryptionProvider contract supports envelope encryption with automatic key rotation:
ep.Encrypt(ctx, plaintext) // produces self-describing ciphertext
ep.Decrypt(ctx, ciphertext) // reads key metadata, selects correct key
ep.RotateKey(ctx) // new key for future encryptions, old key still works for existing dataAPI keys and passwords are never in environment variables or config files. A secrets module handles them:
secrets.Get(ctx, "jackett_api_key") // not os.Getenv("JACKETT_API_KEY")Supported backends: environment variables (development), Vault (production), encrypted files.
Sensitive data (emails, tokens, IP addresses) is automatically stripped from logs and audit trails:
safe := redaction.Redact(ctx, data, []string{"email", "token", "ip_address"})
// {"user_id": "abc123", "email": "***REDACTED***", "action": "login"}External modules will run as separate processes with limited OS permissions:
- Docker/container isolation — recommended deployment pattern
- gVisor/Firecracker — optional microVM isolation for untrusted modules
- Declared permissions — modules declare what they need; core enforces
| Phase | Features |
|---|---|
| MVP (Current) | Local accounts auth, API tokens, RBAC, TLS encryption, audit logging, call policy |
| Phase 2 | OIDC/SSO, LDAP, mTLS enforcement, module permission declarations |
| Phase 3 | Sandbox policies, network segmentation, SIEM integration |
- Deployment — secure deployment patterns
- Contracts Reference — AuthProvider, Authorizer, Encryption interfaces