How to Adjust Linux Capabilities for Containers in Agent Substrate
Agent Substrate allows you to adjust Linux capabilities for containers by specifying add and drop lists in the Capabilities field of an ActorTemplate, which the runtime processes through the resolveCapabilities function in cmd/atelet/oci.go before injecting them into the OCI runtime spec.
Agent Substrate implements a fine-grained security model that lets you grant specific kernel privileges to actor containers without resorting to full root access. When you adjust Linux capabilities for containers in Agent Substrate, the system translates your declarative template configuration into concrete OCI runtime security profiles through a resolution pipeline that handles defaults, explicit drops, and capability additions.
How Capability Resolution Works
The capability resolution pipeline centers on the resolveCapabilities function defined in cmd/atelet/oci.go (lines 52-78). This function bridges the gap between user-friendly template specifications and the low-level OCI runtime requirements.
Default Capability Set
Agent Substrate starts with a conservative default set of capabilities assigned to every container:
- AUDIT_WRITE – Allows writing to the audit log
- KILL – Permits sending signals to processes
- NET_BIND_SERVICE – Enables binding to privileged ports (below 1024)
These defaults provide basic functionality while minimizing the attack surface. The system stores these as unprefixed strings internally, then prepends the OCI-standard CAP_ prefix during resolution.
The Resolution Algorithm
The resolveCapabilities function implements a three-stage resolution process:
- Initialize with the default set (
AUDIT_WRITE,KILL,NET_BIND_SERVICE) - Process drops – Removes any capability listed in the template's
Dropfield; the special value"ALL"clears the entire set - Process adds – Appends any capability from the template's
Addfield - Normalize – Prefixes each capability with
CAP_and sorts the slice for deterministic OCI specs
This logic ensures that drops take precedence over defaults, and explicit adds override any previous state. The final sorted slice guarantees stable OCI bundle generation across reconciliations.
Configuring Capabilities in Actor Templates
You adjust capabilities at the template level using the v1alpha1 ActorTemplate resource. The Capabilities message supports two string arrays: add and drop. You specify capabilities without the CAP_ prefix; the resolver handles normalization automatically.
YAML Configuration Example
apiVersion: substrate.agent/v1alpha1
kind: ActorTemplate
metadata:
name: privileged-network-actor
spec:
capabilities:
add:
- "SYS_ADMIN"
- "NET_ADMIN"
drop:
- "NET_BIND_SERVICE"
image: "my-registry/network-tool:latest"
command: ["/bin/sh", "-c", "ip link set eth0 up"]
In this example, the resulting container receives CAP_AUDIT_WRITE, CAP_KILL, CAP_SYS_ADMIN, and CAP_NET_ADMIN, but lacks CAP_NET_BIND_SERVICE despite it being part of the default set. To drop all capabilities and start from a blank slate, specify drop: ["ALL"] before adding specific privileges.
OCI Spec Injection
After resolution, the buildActorOCISpec function (lines 300-321 of cmd/atelet/oci.go) injects the capability slice into the OCI runtime specification. The function populates three key fields of the OCI specs.Process.Capabilities struct:
- Bounding – Limits which capabilities the process can gain
- Effective – Controls which capabilities are currently active
- Permitted – Defines the maximum set of capabilities the process can assume
Agent Substrate sets all three fields to the identical resolved slice for consistency, while explicitly leaving Inheritable empty. This mirrors the security posture of containerd, CRI-O, and Docker, preventing capability leakage to child processes during exec operations.
gVisor Worker Defaults
For unprivileged workloads running under gVisor, the controller applies a predefined capability set through ateomGvisorCapabilities in cmd/atecontroller/internal/controllers/workerpool_apply.go (lines 238-275). This builder uses WithCapabilities to attach capabilities such as CAP_NET_RAW and CAP_SYS_CHROOT to worker pods, ensuring sandboxed actors receive the specific privileges required for network operations and filesystem isolation without granting full container capabilities.
When creating worker pods, the controller translates these Go constants into Kubernetes corev1.Capability objects and attaches them to the container security context:
workerPod.Spec.Containers[0].SecurityContext.Capabilities = &corev1.Capabilities{
Add: ateomGvisorCapabilities,
}
Validation and Testing
The capability resolution behavior is validated by unit tests in internal/e2e/suites/capabilities/capabilities_test.go (lines 34-48). These tests verify that:
- Default capabilities are present when no modifications are specified
- The
"ALL"sentinel correctly clears the capability set - Explicit adds append to the final slice
- Drops remove entries from both defaults and adds
Running these tests ensures that changes to the resolution logic maintain backward compatibility with existing templates and security policies.
Summary
- Template-driven configuration – Adjust capabilities by modifying the
Capabilitiesfield inActorTemplateresources (v1alpha1), using unprefixed capability names - Resolution logic – The
resolveCapabilitiesfunction incmd/atelet/oci.goprocesses drops before adds, starting from the default set ofAUDIT_WRITE,KILL, andNET_BIND_SERVICE - OCI compliance – Capabilities are normalized with
CAP_prefixes and injected into the OCI spec'sBounding,Effective, andPermittedfields bybuildActorOCISpec - Security defaults – gVisor workers receive a specialized capability set defined in
cmd/atecontroller/internal/controllers/workerpool_apply.goto support sandboxed execution - Testing coverage – End-to-end tests in
internal/e2e/suites/capabilities/capabilities_test.govalidate the resolution pipeline against regression
Frequently Asked Questions
How do I drop all default capabilities and start from zero?
Specify drop: ["ALL"] in your ActorTemplate's capabilities section. This sentinel value tells the resolveCapabilities function to clear the entire default set before processing any add entries. You can then selectively add only the specific capabilities your container requires, implementing a "deny-by-default" security posture.
What is the difference between the default capabilities and gVisor worker capabilities?
Default capabilities apply to standard container workloads managed by the atelet runtime, providing basic sandbox functionality. gVisor worker capabilities, defined in cmd/atecontroller/internal/controllers/workerpool_apply.go, are specific to the gVisor sandbox controller and include additional privileges like CAP_NET_RAW required for the sandbox's networking implementation. Worker capabilities are applied at the Kubernetes pod level, while template capabilities are processed into the OCI spec.
Why does Agent Substrate leave the Inheritable capability set empty?
Agent Substrate mirrors the security model of containerd, CRI-O, and Docker by leaving Inheritable capabilities empty. This prevents privilege escalation during exec operations, ensuring that child processes cannot inherit capabilities from the parent container unless explicitly granted through the OCI runtime configuration. This containment strategy reduces the risk of capability leakage in multi-process containers.
Can I use capability names with the CAP_ prefix in my templates?
No. The resolveCapabilities function expects unprefixed capability names in the template (e.g., SYS_ADMIN rather than CAP_SYS_ADMIN). The resolver automatically prepends CAP_ during OCI spec generation. Including the prefix in your template will result in double-prefixed values like CAP_CAP_SYS_ADMIN, which the kernel will reject as invalid capabilities.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →