Capability matrix
Every operation CSM performs that needs privilege beyond reading its own files, or that writes outside its own directories, with the privilege it needs and the setting that stops it.
Print it on any host, before or after install:
csm privileges # table
csm privileges --json # machine-readable
The command prints a static inventory. It does not load CSM configuration or probe host privileges, so it can be used before installing the service. Output failures produce a nonzero exit status in text, JSON and Markdown formats.
How to read it
Needs is what the operation requires from the kernel. root means uid 0
rather than one capability: the operation reads or writes files owned by many
different accounts, or drives a panel tool that assumes root. Today the daemon
runs as a single root process; splitting it into a small privileged helper plus
a reduced-privilege main process is planned, and this inventory is its first
stage. Capability names describe the privileged kernel interfaces as well as
the root filesystem access. For BPF, tracing and LSM programs need
CAP_BPF plus CAP_PERFMON; cgroup socket programs need CAP_BPF plus
CAP_NET_ADMIN. CAP_SYS_ADMIN is the broader fallback on older kernels.
These requirements follow the kernel program-load checks.
Trigger is automatic when the daemon may start the operation on its own,
and operator when it runs only in response to a command or a button. Not
running the command is how an operator operation is turned off.
Writes lists what the operation writes while it runs, including writes made
by a tool it invokes. Paths starting with / are filesystem paths; everything
else is a resource named <kind>:<name>. “nothing (read-only)” means the
operation only reads. Paths describe default locations; configured account roots,
state, rule, log, spool and policy paths replace those defaults and may require
service drop-ins. Shared CSM state and audit-log writes are grouped in the state
rows. This is not a list of every possible path written by an external panel tool.
Turn it off is the config key and YAML value that stops the named operation.
Dotted keys describe nested YAML mappings; they are not literal top-level keys.
Apply changes before restarting the daemon: several controls are read only at
startup. A row saying “not configurable” has no single config switch that stops
all its callers. disabled_checks takes a list and only affects scheduled checks.
Stopping new work does not undo installed rules, hooks, quarantines or blocks;
use the subsystem removal commands for that. Disabling the forward guard removes
its existing Exim configuration, which is itself a host write.
Action record says whether the operation writes to the action log. “no” does not mean the operation is silent; it means it is not on that stream yet and the daemon log is where it appears.
Without the privilege is what an operator loses by withholding it, so the row reads as a decision rather than a demand.
Relationship to the systemd sandbox
The daemon runs under ProtectSystem=strict with an explicit writable-path
allow-list; see service confinement. The two lists are
compared by tests: every declared in-daemon filesystem write must fit a packaged
grant, and every grant must have an in-daemon writer. Empty inventories and grant
lists fail. Paths are compared on directory boundaries, and an operation outside
the sandbox cannot justify a daemon grant. The unit parser rejects invalid paths
and unsupported syntax, including continuations, specifiers and directory flags,
instead of guessing which locations are writable. Tests cannot discover omitted operations
or prove the privilege and disable claims; those require tracing the runtime code.
Operations marked “outside the systemd sandbox” run as standalone operator commands or transient services forked by PID 1. The latter use direct execution on hosts without a reachable systemd service manager. Mixed operations have separate rows for their in-daemon writes, such as the AF_ALG marker repair and forward-guard lookup refresh. The service sandbox does not bound external tools.
Turning most of it off at once
mode: observe stops automatic remediation and integration deployment, and
refuses configurations that enable those subsystems. It still maintains CSM state,
loads temporary BPF capability probes in BPF builds, and invokes the KernelCare
probe, which can update its own cache. It is not a guarantee of zero host writes. See
observe mode.
The matrix
The risk tier says what can go wrong if an operation acts on the wrong
target: 0 changes nothing outside CSM’s own directories, 1 records a
recommendation or dry-run decision, 2 is a low-risk change such as a probe,
challenge gate or mail hold, 3 quarantines, blocks or denies a target, and 4
signals processes, reloads services, or rewrites content or configuration.
Rows describe maximum live effect, so preview-only tier 1 has no separate
row today. A tier does not promise automatic rollback; see recovery gaps
below and the current contracts in the JSON inventory. The TIER column of
csm privileges and the JSON inventory use the same 0 to 4 numbers.
| Operation | Needs | Trigger | Risk tier | Writes | Turn it off | Action record | Without the privilege |
|---|---|---|---|---|---|---|---|
state.control_socketcreate the private control socket used by operator commands | root | automatic | 0 | /var/run/csm | not configurable | no | CLI commands cannot reach the daemon |
state.mail_relay_policiesload operator-supplied mailer classes and proxy ranges for the PHP-relay detector | none | automatic | 0 | nothing (read-only) | email_protection.php_relay.enabled: false | no | only policy files readable by the daemon uid can be loaded |
state.php_shield_eventsreceive PHP Shield events over a socket and append their local archive | root | automatic | 0 | /var/log/csm-php-shield | php_shield.enabled: false | no | PHP runtime events are not collected |
state.sign_configrewrite CSM’s own integrity hashes into csm.yaml after an approved change | root | operator | 0 | /etc/csm | do not run the command | no | the integrity gate cannot be re-signed, so the next restart refuses to start after any config edit |
state.update_forgedownload and verify YARA Forge rules independently of YAML updates | root | automatic | 0 | /opt/csm/rules | signatures.yara_forge.enabled: false | no | YARA Forge rules are not refreshed |
state.update_signaturesdownload and signature-verify YAML malware rule updates | root | automatic | 0 | /opt/csm/rules | signatures.update_url: "" | no | detection freezes at the ruleset shipped with the installed package |
state.write_deploy_scriptrefresh the embedded upgrade script in CSM’s own directory at startup | root | automatic | 0 | /opt/csm/deploy.sh | mode: observe | no | the packaged upgrade helper is missing and upgrades are run by hand |
state.write_logswrite the daemon log and the audit-log sinks that feed a SIEM | root | automatic | 0 | /var/log/csm | not configurable | no | no local record of findings or actions |
state.write_storewrite the bbolt state database, baselines, incidents and scan reports | root | automatic | 0 | /var/lib/csm, /opt/csm/state | not configurable | no | CSM cannot run: without state there is no baseline, no dedup and no incident history |
detect.account_databasesread account MySQL credentials and scan databases for injected content and stored objects | root | automatic | 0 | nothing (read-only) | not configurable: db_object_scanning only stops stored-object scans; content and requested scans remain | no | no database detection: injected admin users, poisoned options and malicious triggers stay invisible |
detect.af_alg_socketsdeny AF_ALG sockets through BPF LSM, or observe socket use through the audit-log fallback | CAP_BPF, CAP_PERFMON, root | automatic | 3 | kernel:AF_ALG socket denial | detection.af_alg_backend: none | no | the live monitor is gone; the periodic critical check still reports an exposed kernel |
detect.audit_rulesquery loaded audit rules with auditctl to check detection coverage | CAP_AUDIT_CONTROL, root | automatic | 0 | nothing (read-only) | not configurable | no | audit-rule coverage cannot be verified |
detect.bpf_probeload and briefly attach BPF LSM, tracepoint and cgroup programs to discover kernel support | CAP_BPF, CAP_PERFMON, CAP_NET_ADMIN, root | automatic | 2 | kernel:temporary BPF programs and maps | not configurable: capability discovery runs independently of monitor settings in BPF builds | no | BPF capabilities report unavailable; configured automatic backends use their fallbacks |
detect.filesystem_eventsfanotify stream over account roots and world-writable temp directories | CAP_SYS_ADMIN | automatic | 0 | nothing (read-only) | not configurable | no | no real-time file detection; scheduled scans still run, so a webshell lives until the next cycle |
detect.kernel_livepatch_proberun kcarectl –patch-info to see whether a KernelCare livepatch covers Copy Fail; kcarectl rewrites its own cache on every run | root | automatic | 2 | /var/cache/kcare | not configurable: the startup kernel probe and hardening audits also invoke kcarectl, including in observe mode | no | a patched kernel reads as vulnerable, so the AF_ALG finding cannot be cleared |
detect.kernel_oomread the restricted kernel message buffer with dmesg to find recent OOM kills | CAP_SYSLOG, root | automatic | 0 | nothing (read-only) | not configurable | no | swap statistics remain available, but OOM kills are not reported |
detect.mail_queue_probequery the Exim queue through a transient unit, because Exim opens its logs even to answer a read | root | automatic | 2 | /var/log/exim_mainlog, /var/log/exim_paniclog, /var/log/exim4 (outside the systemd sandbox) | disabled_checks: [mail_queue] | no | queue depth is unknown, so a queue-size spam outbreak may go undetected |
detect.outbound_connectionswatch outbound connections through BPF cgroup hooks, or by polling /proc/net/tcp | CAP_BPF, CAP_NET_ADMIN, root | automatic | 2 | kernel:BPF programs and maps | detection.connection_tracker_backend: none | no | auto mode uses polling; an explicitly selected BPF backend stays unavailable |
detect.pam_eventsreceive authentication attempts from the pam_csm.so hook over a private socket | root | automatic | 0 | /var/run/csm | not configurable | no | no immediate brute-force or credential-stuffing block; log parsing lags by up to a minute |
detect.process_execwatch process execution through a BPF tracepoint, or by walking /proc | CAP_BPF, CAP_PERFMON, root | automatic | 2 | kernel:BPF programs and maps | detection.exec_monitor_backend: none | no | auto mode uses periodic /proc sampling; an explicitly selected BPF backend stays unavailable |
detect.read_service_logsread mail, authentication, web server and ModSecurity logs | root | automatic | 0 | nothing (read-only) | not configurable | no | no mail abuse, brute-force or WAF detection: these logs are root-readable only |
detect.scan_account_filesread every account’s files for scheduled, real-time and on-demand scans | CAP_DAC_READ_SEARCH, root | automatic | 0 | nothing (read-only) | not configurable: disabled_checks only suppresses scheduled checks; realtime and requested scans remain | no | only files readable by CSM’s own uid are scanned, which on a shared host is close to nothing |
detect.sensitive_file_writeswatch writes to /etc/shadow and comparable files through a BPF LSM hook | CAP_BPF, CAP_PERFMON, root | automatic | 2 | kernel:BPF programs and maps | detection.sensitive_files_backend: none | no | sensitive-file changes are found by periodic hashing instead of at the moment of the write |
integrate.auditd_ruleswrite CSM’s auditd rules and reload them, so audit-backed detection survives package upgrades | CAP_AUDIT_CONTROL, root | automatic | 4 | /etc/audit, kernel:audit rules | mode: observe | no | audit-backed detection layers stay inactive after an upgrade |
integrate.challenge_port_gateinstall the separate nftables port gate for the public challenge listener | CAP_NET_ADMIN, root | automatic | 2 | nftables:challenge port gate | challenge.port_gate.enabled: false | no | the listener remains reachable without the challenge port filter |
integrate.challenge_snippetrefresh the web server snippet and rewrite maps that route challenged visitors to CSM’s proof-of-work listener, and reload the web server when the snippet changed | root | automatic | 4 | /etc/apache2/conf.d, /etc/apache2/conf-enabled, /etc/httpd/conf.d, /etc/nginx/conf.d, /usr/local/lsws/conf/templates, /var/cache/csm, service:web server reload | mode: observe | no | suspicious visitors are blocked outright instead of being offered a challenge |
integrate.firewall_rulesetbuild and load CSM’s nftables table, including the operator’s port policy and rate limits | CAP_NET_ADMIN, root | automatic | 4 | nftables:csm table | firewall.enabled: false | yes | no firewall management; another tool owns the host’s packet policy |
integrate.modsec_sectionrefresh the managed ModSecurity section at startup and during periodic WAF checks, preserving operator configuration | root | automatic | 4 | /etc/apache2/conf.d, /usr/local/apache/conf | mode: observe | no | CSM’s virtual-patch WAF rules are not installed |
integrate.panel_plugindeploy the WHM plugin CGI and its AppConfig entry, and register it with the panel | root | automatic | 4 | /usr/local/cpanel/whostmgr/docroot/cgi, /var/cpanel | mode: observe | no | no panel plugin; the web UI is still reachable on its own port |
integrate.php_shieldinstall the PHP runtime hook and register its shared event directory for an operator-scheduled CageFS remount | root | operator | 4 | /opt/csm, /var/log/csm-php-shield, /etc/cagefs, /etc/csm, /opt/cpanel, /opt/alt, /usr/local/lsws, php:runtime configuration (outside the systemd sandbox) | do not run the command | no | no PHP runtime blocking of uploads and temp-directory execution |
integrate.waf_vendor_rulesask the panel to update stale ModSecurity vendor rulesets when a periodic check finds them out of date | root | automatic | 4 | /etc/apache2/conf.d, /usr/local/apache/conf, /var/cpanel, service:web server reload | mode: observe | no | stale WAF rules are reported but not refreshed |
operate.export_archivesread protected configuration, state or account evidence and export backup or forensic archives, temporary snapshots and checksum sidecars | root | operator | 4 | filesystem:operator-selected archive destinations (outside the systemd sandbox) | do not run the command | no | protected configuration, state and account evidence cannot be exported completely |
operate.harden_hostapply a supported CVE mitigation: a modprobe blacklist, or seccomp drop-ins for the services that need one | CAP_SYS_MODULE, root | operator | 4 | /etc/modprobe.d, /etc/systemd/system, service:restart, kernel:modules (outside the systemd sandbox) | do not run the command | no | the mitigation is applied by hand from the command the audit prints |
operate.install_serviceinstall or remove CSM itself: the systemd unit, the PAM hook, logrotate and the panel integrations | CAP_AUDIT_CONTROL, CAP_LINUX_IMMUTABLE, root | operator | 4 | /opt/csm, /etc/csm, /var/lib/csm, /var/log/csm, /etc/systemd/system, /etc/pam.d, /etc/logrotate.d, /etc/audit, /usr/sbin/csm, /lib64/security, /usr/lib64/security, /lib/security, /lib/x86_64-linux-gnu/security, /usr/lib/x86_64-linux-gnu/security, /lib/aarch64-linux-gnu/security, /usr/lib/aarch64-linux-gnu/security, /usr/local/cpanel/whostmgr/docroot/cgi, /var/cpanel, service:csm and panel integrations, /etc/cron.d, /var/cache/csm, /opt/cpanel, /var/run/csm, /etc/apache2/conf.d, /etc/apache2/conf-enabled, /etc/httpd/conf.d, /etc/nginx/conf.d, /usr/local/apache/conf, /usr/local/lsws/conf/templates (outside the systemd sandbox) | do not run the command | no | CSM cannot be installed as a service |
operate.manual_firewallblock, allow, tempban or flush addresses on request, and roll a firewall apply back | CAP_NET_ADMIN, root | operator | 3 | nftables:csm sets | do not run the command | yes | firewall changes are made with nft or the panel’s own tooling |
operate.manual_remediationclean or quarantine files and spool messages, truncate malicious crontabs, kill verified malware processes, or change database objects on request | CAP_KILL, root | operator | 4 | /home, /tmp, /var/tmp, /dev/shm, /var/spool/cron, /var/spool/exim/input, /var/spool/exim4/input, /opt/csm/quarantine, mysql:account databases, process:signal | do not run the command | no | remediation is done by hand over SSH |
operate.rehashre-sign configuration, converge legacy config copies, set binary immutability and refresh the launcher, service and log rotation | CAP_LINUX_IMMUTABLE, root | operator | 4 | /opt/csm, /etc/csm, /etc/systemd/system, /etc/logrotate.d, /usr/sbin/csm, service:daemon reload (outside the systemd sandbox) | do not run the command | no | hash signing and upgrade integration refresh cannot complete |
operate.restore_backupstage and restore protected configuration, drop-ins and state from a backup while the daemon is stopped | root | operator | 4 | /etc/csm, /var/lib/csm, /opt/csm, /tmp (outside the systemd sandbox) | do not run the command | no | configuration and state cannot be restored |
operate.truncate_error_logempty an account error log that has grown large enough to threaten the filesystem | root | operator | 4 | /home | do not run the command | no | a bloated log is reported and truncated by hand |
respond.af_alg_enforceunload the AF_ALG kernel modules again when an opted-in mitigation marker is present | CAP_SYS_MODULE, root | automatic | 4 | kernel:modules (outside the systemd sandbox) | auto_response.disable_enforce_af_alg: true | no | a module reload silently reopens the Copy Fail exposure |
respond.af_alg_killkill a verified AF_ALG socket caller through the separate Copy Fail response setting | CAP_KILL, root | automatic | 4 | process:signal | auto_response.copy_fail_kill_process: false | no | the process is reported but remains running; this path is independent of kill_processes |
respond.af_alg_markerrestore a changed AF_ALG mitigation marker after the operator has opted in | root | automatic | 4 | /etc/modprobe.d | auto_response.disable_enforce_af_alg: true | no | a changed module blacklist is reported but cannot be repaired |
respond.block_ipadd an attacker address or subnet to the firewall’s deny sets | CAP_NET_ADMIN, root | automatic | 3 | nftables:csm sets | auto_response.block_ips: false | yes | attacks are reported but not stopped; dry-run records what would have been blocked |
respond.bpf_deny_egressdeny matched outbound connections in the kernel through a BPF cgroup hook | CAP_BPF, CAP_NET_ADMIN, root | automatic | 3 | kernel:bpf cgroup program | bpf_enforcement.enabled: false | no | direct-to-MX spam egress is detected but not stopped at the source |
respond.clean_filestrip injected code from a PHP or access file, keeping a pre-clean backup | root | automatic | 4 | /home, /tmp, /var/tmp, /dev/shm, /opt/csm/quarantine | auto_response.enabled: false | yes | injected files are reported and left in place |
respond.database_cleanuprevoke a rogue CMS admin, sanitize poisoned options, and drop confirmed malicious stored objects after recording their definition | root | automatic | 4 | mysql:account databases | auto_response.clean_database: false | no | database persistence survives file cleanup and re-infects the account |
respond.enforce_permissionschmod a world-writable or group-writable PHP file back to 644 | root | automatic | 3 | /home | auto_response.enforce_permissions: false | no | writable code files are reported only |
respond.fix_wp_crondisable WP-Cron for an account and install a per-user system cron entry instead | root | automatic | 4 | /home, /var/spool/cron, /tmp | auto_response.fix_wp_cron: false | no | runaway WP-Cron load is reported only |
respond.forward_guardinstall or remove the managed Exim block that holds forwarded spam and backscatter, and rebuild the Exim configuration | root | automatic | 4 | /etc/exim.conf.local, /etc/exim.conf, /var/lib/csm, exim:rebuild artifacts and service reload (outside the systemd sandbox) | mode: observe | no | forwarded spam keeps damaging the host’s sending reputation |
respond.forward_guard_lookuprefresh the forward-guard bad-sender lookup inside the daemon sandbox | root | automatic | 0 | /var/lib/csm/forward_guard | email_protection.forward_guard.enabled: false | no | Exim continues using the last successfully written sender lookup |
respond.freeze_mailfreeze queued Exim messages attributed to a confirmed PHP-relay finding | root | automatic | 2 | /var/spool/exim/input, /var/spool/exim4/input, /var/log/exim_mainlog, /var/log/exim_paniclog, /var/log/exim4 (outside the systemd sandbox) | auto_response.php_relay.freeze: false | no | a compromised script keeps sending until an operator freezes the queue |
respond.hold_outgoing_mailrequest a cPanel account outgoing-mail hold through whmapi1 after sustained mail abuse | root | automatic | 2 | cpanel:account outgoing mail hold | auto_response.enabled: false | no | the mail abuse finding is reported but the account can continue sending |
respond.kill_processsignal a malicious process through a kernel process handle, never a recycled PID, never root | CAP_KILL, root | automatic | 4 | process:signal | auto_response.kill_processes: false | yes | reverse shells and miners keep running until an operator kills them |
respond.mail_delivery_gatedefer Exim delivery with fanotify permission responses when tempfail policy requires it | CAP_SYS_ADMIN, root | automatic | 2 | fanotify:mail delivery decisions | email_av.enabled: false | no | mail AV falls back to notifications and cannot defer delivery on scan failures |
respond.quarantine_filemove a confirmed malicious file out of an account tree into CSM’s quarantine, preserving owner, permissions and mtime | root | automatic | 3 | /home, /tmp, /var/tmp, /dev/shm, /opt/csm/quarantine | auto_response.quarantine_files: false | yes | malware is reported and left in place |
respond.quarantine_mailmove an infected message out of the Exim spool after an antivirus match | root | automatic | 3 | /var/spool/exim/input, /var/spool/exim4/input, /opt/csm/quarantine | email_av.quarantine_infected: false | no | infected mail is reported and delivered |
respond.restart_mail_authrestart the panel’s mail authentication service after a sustained outage | root | automatic | 4 | service:mail authentication | auto_response.mail_auth_recovery.restart_enabled: false | no | an authentication backend outage is alerted but not repaired |
respond.virtual_patchwrite a reversible deny rule into an account access file to close an exposed file | root | automatic | 3 | /home, /opt/csm/quarantine | auto_response.virtual_patch_exposed_files: off | no | exposed backups, dumps and configs stay reachable until the account owner fixes them |
Regenerate the table with go run ./cmd/csm privileges --markdown. A gate in
internal/ci fails when the page and the inventory disagree.
Risk tiers and recovery coverage
csm privileges --json reports each operation’s risk tier as a number from 0
to 4. A row with no tier would report -1; the test suite refuses such an
inventory, so a release never ships one. The tier is the operation’s maximum
live effect on a wrong target. It is not the severity of the finding that
triggered the operation.
Seven operations carry a safety contract in the JSON inventory:
respond.block_ip, operate.manual_firewall, integrate.firewall_ruleset,
integrate.challenge_port_gate, integrate.challenge_snippet,
respond.quarantine_file and respond.clean_file. Each states the authority it
needs, how the target is revalidated before the change, how the change is
reversed, and the limit that bounds it. The contracts describe current
behaviour, including what it does not cover. Every other operation that changes
the host states the recovery this inventory does not yet cover:
| Operations | Recovery gap |
|---|---|
detect.bpf_probe, detect.outbound_connections, detect.process_exec, detect.sensitive_file_writes, detect.af_alg_sockets, respond.bpf_deny_egress | This inventory does not yet specify verified detach, map restoration and crash recovery for these kernel hooks. |
detect.kernel_livepatch_probe, detect.mail_queue_probe | This inventory does not specify recovery of incidental external cache or log writes by probe commands. |
respond.mail_delivery_gate, respond.hold_outgoing_mail, respond.freeze_mail, respond.quarantine_mail | This inventory does not yet specify identity checks for releasing or restoring mail, or recovery after a restart. |
respond.af_alg_enforce, respond.af_alg_marker, respond.forward_guard, respond.fix_wp_cron, integrate.auditd_rules, integrate.modsec_section, integrate.panel_plugin, integrate.php_shield, integrate.waf_vendor_rules, operate.harden_host, operate.install_service, operate.rehash, operate.restore_backup | This inventory does not specify an action-wide snapshot and verified rollback of configuration, service and external-tool side effects. |
respond.af_alg_kill, respond.kill_process, respond.restart_mail_auth | Process termination and restart cannot restore lost process state; this inventory does not yet specify a full recovery contract. |
respond.database_cleanup, respond.virtual_patch, respond.enforce_permissions, operate.manual_remediation, operate.truncate_error_log | Current remediation may retain local recovery evidence, but per-operation identity-checked undo and partial-failure recovery are not specified by this inventory. |
operate.export_archives | Export can replace an existing operator-selected archive; removing the new archive does not restore overwritten bytes. |