# Capabilities

> How each agent decides which features it exposes, configured entirely on the agent host.

URL: https://docs.serverbee.app/en/docs/capabilities

ServerBee enforces the principle of least privilege through per-agent capabilities. **Capabilities are owned by the agent host**: each agent computes its own capability set from its config file (and optional CLI flags) and reports it to the server. The server can read and display these capabilities, but it **cannot** change them — there is no server-side toggle.

<Callout type="info">
  This is a deliberate trust-boundary decision. The machine that runs the agent is the only place that decides what that agent will do. A compromised or misconfigured server cannot silently turn on a remote shell, file access, or command execution on your hosts.
</Callout>

## Capability List [#capability-list]

ServerBee defines 11 capability bits across two risk levels. The valid mask is `2047` (bits 0..=10).

### High Risk (Disabled by Default) [#high-risk-disabled-by-default]

| Capability            | Key        | Bit Value          | Description                                                             |
| --------------------- | ---------- | ------------------ | ----------------------------------------------------------------------- |
| **Web Terminal**      | `terminal` | `CAP_TERMINAL` (1) | Allow opening a remote terminal via browser                             |
| **Remote Exec**       | `exec`     | `CAP_EXEC` (2)     | Allow remote command execution                                          |
| **File Manager**      | `file`     | `CAP_FILE` (64)    | Allow remote file browsing, editing, upload/download                    |
| **Docker Management** | `docker`   | `CAP_DOCKER` (128) | Allow Docker container monitoring, log streaming, and container actions |

<Callout type="warn">
  These capabilities allow executing arbitrary code or accessing the filesystem on the target server, so they are disabled by default. Enable them only on trusted hosts by editing that host's agent config.
</Callout>

<Callout type="info">
  File Manager requires additional agent-side configuration (`root_paths`, `deny_patterns`) for path sandbox security. See the [Agent Setup](/en/docs/agent) and [Configuration](/en/docs/configuration) pages for details.
</Callout>

### Low Risk (Enabled by Default) [#low-risk-enabled-by-default]

| Capability             | Key               | Bit Value                   | Description                                                                                                                                                             |
| ---------------------- | ----------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Auto Upgrade**       | `upgrade`         | `CAP_UPGRADE` (4)           | Allow remote binary upgrades                                                                                                                                            |
| **ICMP Ping**          | `ping_icmp`       | `CAP_PING_ICMP` (8)         | Allow ICMP probe tasks                                                                                                                                                  |
| **TCP Probe**          | `ping_tcp`        | `CAP_PING_TCP` (16)         | Allow TCP port probe tasks                                                                                                                                              |
| **HTTP Probe**         | `ping_http`       | `CAP_PING_HTTP` (32)        | Allow HTTP probe tasks                                                                                                                                                  |
| **Security Events**    | `security_events` | `CAP_SECURITY_EVENTS` (256) | Allow agent to report SSH login / brute-force / port-scan events (see [Security Events](/en/docs/security-events))                                                      |
| **Firewall Blocklist** | `firewall_block`  | `CAP_FIREWALL_BLOCK` (512)  | Allow agent to apply the server-pushed nftables blocklist. Requires root or `CAP_NET_ADMIN` plus the `nft` CLI on the host. See [Firewall Blocklist](/en/docs/firewall) |
| **IP Quality**         | `ip_quality`      | `CAP_IP_QUALITY` (1024)     | Allow the Agent to run service-unlock probes and report results; optional metadata and risk enrichment runs on the Server                                               |

An agent with no `[capabilities]` overrides defaults to `1852` (auto upgrade + the three ping probes + security events + firewall blocklist + IP quality), which keeps the high-risk terminal, exec, file, and Docker capabilities off.

## Configuring Capabilities (on the agent host) [#configuring-capabilities-on-the-agent-host]

An agent computes its capability bitmask as:

1. Start from the built-in default set (`CAP_DEFAULT = 1852`).
2. Apply the config file `[capabilities]` block — `allow` adds bits, `deny` removes bits. Within this layer, `deny` wins over `allow`.
3. Apply CLI `--allow-cap` / `--deny-cap` flags on top, for ad-hoc overrides. The CLI layer wins over the config layer.
4. Reconcile against subsystem availability: `file` is dropped unless `[file].enabled = true` with at least one `root_path`, and `firewall_block` is dropped when the startup `nft` probe fails (binary, kernel, or privileges missing). A reported capability always means the agent can actually serve it.

Unknown capability keys are a hard error at startup, so a typo fails fast rather than silently dropping a capability.

### Config file [#config-file]

For installer-managed deployments, edit `/opt/serverbee/etc/agent.toml` on the agent host. Manual deployments may use `/etc/serverbee/agent.toml` or `agent.toml` in the working directory:

```toml
[capabilities]
# Turn on high-risk capabilities this host should expose.
allow = ["terminal", "file"]
# Strip default capabilities this host should not expose.
deny = ["ip_quality"]
```

Restart the agent for changes to take effect:

```bash
sudo serverbee restart agent
```

### Environment variables [#environment-variables]

The same block can be supplied via environment variables (Figment, `SERVERBEE_` prefix, `__` nesting):

```bash
SERVERBEE_CAPABILITIES__ALLOW=["terminal","file"]
SERVERBEE_CAPABILITIES__DENY=["ip_quality"]
```

### CLI flags [#cli-flags]

For one-off overrides, pass repeated flags when launching the agent:

```bash
serverbee-agent --allow-cap terminal --allow-cap file --deny-cap ip_quality
```

### At install time [#at-install-time]

The install script accepts `--caps` to seed the agent's capability config during enrollment:

```bash
curl -fsSL https://raw.githubusercontent.com/ZingerLittleBee/ServerBee/main/deploy/install.sh | sudo sh -s -- agent \
  --server-url https://monitor.example.com \
  --enrollment-code YOUR_ONE_TIME_CODE \
  --caps terminal,file
```

Running the installer interactively also prompts for capabilities, with the defaults pre-checked.

## Temporary grants [#temporary-grants]

Editing the config and restarting the agent is the right way to enable a capability permanently. For short-lived, ad-hoc needs — "give me a terminal on this box for the next 30 minutes" — the agent ships a host-local CLI that temporarily turns on an off-by-default capability, then automatically turns it back off when the window ends.

<Callout type="info">
  Temporary grants are a **host-local** mechanism, fully consistent with the agent-owned trust model: only someone with a shell on the agent host can issue one. The server and the web/iOS UI remain read-only and **cannot** grant a capability. The server merely mirrors what the agent reports, gates control-plane requests on it, audits the change, and can fire an alert.
</Callout>

### CLI [#cli]

Run these subcommands on the **agent host** (typically with `sudo`, since they write under `state_dir` and the daemon usually runs as the agent user/root). They share the daemon's config (`/opt/serverbee/etc/agent.toml` for installer-managed deployments), so the grants file location and the maximum duration come from the same `[capabilities]` block.

```bash
# Temporarily enable an off-by-default capability for a bounded duration.
serverbee-agent grant terminal --for 30m --reason "debugging a stuck deploy"
serverbee-agent grant file --for 2h
serverbee-agent grant docker --for 1d

# Revoke an active grant early.
serverbee-agent revoke terminal

# List the currently active grants (cap, remaining seconds, who granted it, reason).
serverbee-agent grants
```

* The duration passed to `--for` is `<number><unit>` where the unit is one of `s` (seconds), `m` (minutes), `h` (hours), or `d` (days) — e.g. `90s`, `30m`, `2h`, `1d`. `--reason` is optional free-form text recorded with the grant.
* `grant` only **turns on** a capability that is currently **off**. Granting a capability that is already enabled in `agent.toml` is rejected (`'terminal' is already enabled in agent.toml; nothing to grant`).
* A duration longer than `temporary_max_duration` (default `24h`, see [Configuration](/en/docs/configuration)) is refused.
* The CLI is one-shot: it writes the grants file and exits. The running daemon picks the change up within a few seconds — there is no need to restart the agent.

### Restart and expiry semantics [#restart-and-expiry-semantics]

Grants are persisted to `<state_dir>/capability_grants.json` (default `/var/lib/serverbee/capability_grants.json`, written `0600`) with an **absolute** `expires_at` (a Unix epoch timestamp), not a relative countdown. This makes the behavior robust:

* **Survives restarts** — if the agent restarts mid-window, the grant is reloaded and remains active for the rest of its original window.
* **Expires at the original deadline** — restarting does not extend a grant. A grant for `30m` expires 30 minutes after it was issued regardless of how many times the agent restarts in between.
* **Fails safe to OFF** — if the grants file is missing, empty, corrupt, or written by an unknown schema version, the agent treats it as "no grants" and the capability stays off. A temporary grant can never *strengthen* the permanent set; it can only flip an otherwise-off bit on for its window.
* **Expiry bounds running work, not just new requests** — when a `terminal` grant expires or is revoked, live terminal sessions opened under it are closed immediately (the browser sees the session end), and a `security_events` grant starts the agent's security pipeline the moment it becomes effective and stops it when the window closes — including grants issued while the agent was already running.

### What the server sees [#what-the-server-sees]

When a grant becomes active (or expires/is revoked), the agent re-reports its effective capability set, so the server opens or closes the gate **live**:

* The change is mirrored into `servers.capabilities` and broadcast to browsers, so the UI updates in real time.
* The transition is written to the [audit log](/en/docs/admin) as `capability_temporarily_granted`, `capability_grant_expired`, or `capability_grant_revoked`.
* A temporary grant of a **high-risk** capability (`terminal`, `exec`, `file`, or `docker`) also evaluates the event-driven `capability_grant_detected` [alert rule](/en/docs/alerts). Expiry, revoke, and low-risk grants are audited but do not fire an alert.
* Everything the agent executes for the server is re-synced against the new capability set: ping tasks are re-filtered, network probe targets and IP-quality services are re-pushed, and the firewall blocklist is reset (and re-sent only while `firewall_block` is still effective). Revoking a ping capability stops those probes immediately, and revoking `firewall_block` removes ServerBee's nftables rules from the host without waiting for a reconnect.

<Callout type="warn">
  A grant issued while the agent is **disconnected** from the server is applied locally and shows up on reconnect, but it does **not** produce a `granted` audit entry or alert. The server only sees the post-reconnect capability state, not the moment of transition, so it cannot distinguish a freshly-granted capability from one that was already active. Plan around this if you rely on the alert as a tripwire.
</Callout>

### In the web UI [#in-the-web-ui]

While a temporary grant is active, the affected capability is shown with an amber **Temporary** badge and a live countdown to expiry, in both the **Server Detail → Capabilities** dialog and the **Settings → Capabilities** fleet matrix. The badge disappears automatically when the grant expires or is revoked.

## Enforcement model [#enforcement-model]

The agent host is the single source of truth, but enforcement happens at two boundaries for defense in depth.

### Server-side gating (based on reported capabilities) [#server-side-gating-based-on-reported-capabilities]

The server records what each agent reports and rejects control-plane requests for capabilities the agent has not enabled:

* **Terminal**: the WebSocket upgrade is rejected with 403
* **Exec**: `POST /api/tasks` and scheduled task runs filter out servers whose agent lacks `exec` and write synthetic results (`exit_code = -2`)
* **Auto Upgrade**: `POST /api/servers/{id}/upgrade` returns 403 when `upgrade` is not reported
* **Ping and Traceroute**: probe tasks are filtered by capability; traceroute requires `ping_icmp`
* **File Manager**: file endpoints reject requests before dispatch when `file` is not reported
* **Docker**: Docker read/action endpoints and the Docker log WebSocket routes require `docker` and agent runtime Docker support

Because capabilities are agent-owned, the denial reason is always `agent_capability_disabled`.

### Agent-side enforcement [#agent-side-enforcement]

Even if a server-side request slips through, the agent re-checks capabilities locally:

* It returns a `CapabilityDenied` message for unauthorized commands
* On receiving `CapabilityDenied`, the server writes a synthetic result (`exit_code = -1`)
* Denial events are recorded in the audit log

### Reporting and display [#reporting-and-display]

When an agent connects (or reconnects), it sends `SystemInfo` carrying `agent_local_capabilities`. The server:

1. Persists that value into the `servers.capabilities` column as a display mirror (so the dashboard can show capabilities even while the agent is offline).
2. Broadcasts `CapabilitiesChanged` to all connected browsers so the UI reflects the current set in real time.

If ping-related bits change between reconnects, the server automatically re-syncs ping tasks for the agent.

## Frontend behavior [#frontend-behavior]

The web and iOS clients display capabilities **read-only**:

* **Server Detail → Capabilities**: a read-only summary of which capabilities the agent has enabled, with a note that they are configured in the agent config file
* **Settings → Capabilities**: a read-only fleet matrix showing the enabled/disabled state per server
* **Tasks page**: servers whose agent lacks `exec` are greyed out
* **Terminal button**: hidden for servers whose agent lacks `terminal`
* **Files button**: hidden for servers whose agent lacks `file`
* **Docker link**: hidden for servers whose agent lacks `docker`

## Runtime capability fields [#runtime-capability-fields]

The server response exposes three capability fields, which are all equal under the agent-owned model:

* `capabilities`: the persisted mirror of the last value the agent reported
* `agent_local_capabilities`: the live value reported by the connected agent
* `effective_capabilities`: the value actually enforced (identical to the agent-local value)

The fields are kept distinct in the API for forward compatibility and so an offline server can still display its last-known capabilities from the mirror.

<Cards>
  <Card title="Web Terminal" href="/en/docs/terminal" />

  <Card title="File Manager" href="/en/docs/file-manager" />

  <Card title="Ping Monitoring" href="/en/docs/ping" />

  <Card title="Admin Guide" href="/en/docs/admin" />
</Cards>
