Using Linux Capabilities (--cap-add and --cap-drop) for Container Security
Apple Container implements Linux capabilities as a fine-grained security mechanism that lets you add or drop specific privileges using --cap-add and --cap-drop flags to enforce least-privilege container workloads.
The apple/container repository provides a Swift-based container runtime that uses Linux capabilities to restrict which privileged operations a container process can perform. Instead of running containers with full root privileges or completely unprivileged, you can tailor the exact kernel capabilities available to your workload. This approach minimizes the attack surface by ensuring containers possess only the specific privileges they require to function.
How Linux Capabilities Work in Apple Container
Apple Container processes capability configurations through a three-stage pipeline that transforms user input into kernel-enforced restrictions. When you specify --cap-add or --cap-drop on the command line, the system validates, normalizes, and applies these settings before the container process starts.
Parsing CLI Input in Flags.swift
The command-line parser extracts capability arguments from the user input and stores them in the Flags.Management structure. In Sources/Services/ContainerAPIService/Client/Flags.swift, lines 31-44 define the capAdd and capDrop properties that capture the string arrays passed via CLI flags.
let flags = Flags.Management(
arch: "x86_64",
capAdd: ["NET_ADMIN", "SYS_TIME"],
capDrop: ["MKNOD"]
)
Validation and Normalization in Parser.swift
Before reaching the Linux kernel, capability strings undergo strict validation in Sources/Services/ContainerAPIService/Client/Parser.swift. The capabilities() function (lines 21-55) performs three critical operations:
- Converts all input to uppercase
- Validates against the known
CapabilityNameset from the containerization library - Normalizes strings to the canonical
CAP_prefix format (accepting bothNET_ADMINandCAP_NET_ADMIN)
The parser also handles the special ALL keyword, which represents the complete capability set.
Runtime Enforcement
The normalized capability lists are passed to container-runtime-linux, which invokes the Linux kernel APIs (prctl and capset) to configure the process capability sets. Any attempt by the container to exercise a dropped capability results in an EPERM (Operation not permitted) error from the kernel.
Default Capability Sets and Security Best Practices
Apple Container ships with a default whitelist of capabilities that include the minimal privileges needed for most workloads (such as CAP_NET_RAW and CAP_SETUID). You can view these defaults in the user documentation or by inspecting Flags.Management in the source code.
Key security considerations include:
- Least-privilege principle: Start with the default set and only add capabilities that a specific workload truly needs, such as
CAP_NET_ADMINfor low-level networking operations. - Complete capability dropping: Use
--cap-drop ALLto remove every capability, then selectively re-add only required privileges using--cap-add. - Processing order: Drops are processed before adds, meaning
--cap-drop ALL --cap-add ALLresults in a container with all capabilities granted. - Input flexibility: The parser accepts both
CAP_prefixed and bare capability names, handling case insensitivity automatically.
Practical Examples for Container Security
Granting Specific Capabilities
To grant a single additional capability for network administration tasks:
container run --cap-add NET_ADMIN alpine ip link set lo down
To grant all capabilities (use with extreme caution):
container run --cap-add ALL alpine sh -c "ip link set lo down && echo ok"
Dropping All Capabilities
For maximum security, drop all capabilities and restore only those required for setuid programs:
container run \
--cap-drop ALL \
--cap-add SETUID \
--cap-add SETGID \
alpine id
Combining Add and Drop Operations
When you need to grant all capabilities except specific dangerous ones:
container run --cap-add ALL --cap-drop NET_ADMIN alpine sh
To drop a default capability and add a different one:
container run --cap-drop MKNOD --cap-add SYS_ADMIN alpine sh
Note that adds are applied after drops, so the final capability set depends on the order of operations.
Implementation Details for Developers
When integrating capability management into Swift applications using the Apple Container client library, explicitly validate flags before execution:
let flags = Flags.Management(
arch: "x86_64",
capAdd: ["NET_ADMIN", "SYS_TIME"],
capDrop: ["MKNOD"]
)
try flags.validate()
let (add, drop) = try Parser.capabilities(capAdd: flags.capAdd, capDrop: flags.capDrop)
// add and drop now contain normalised strings like ["CAP_NET_ADMIN", "CAP_SYS_TIME"]
// These are sent to the container-runtime via the API.
Key Source Files
| File | Role |
|---|---|
Sources/Services/ContainerAPIService/Client/Flags.swift |
Defines --cap-add and --cap-drop CLI options in Flags.Management |
Sources/Services/ContainerAPIService/Client/Parser.swift |
Validates and normalizes capability strings via the capabilities() function |
Tests/IntegrationTests/Run/TestCLIRunCapabilities.swift |
Integration tests verifying add/drop combinations and parser behavior |
docs/how-to.md |
User-facing documentation of default capability sets (lines 70-101) |
docs/command-reference.md |
Reference documentation for capability flags |
Summary
- Apple Container uses
--cap-addand--cap-dropto implement fine-grained privilege control through Linux capabilities. - The system normalizes capability names in
Parser.swiftand validates them against known capability sets before passing them to the Linux kernel. - Security best practices include starting with
--cap-drop ALLand selectively adding only required capabilities, remembering that add operations are processed after drops. - Implementation spans
Flags.swiftfor CLI parsing,Parser.swiftfor validation, and the container runtime for kernel enforcement viaprctlandcapset.
Frequently Asked Questions
What is the difference between --cap-add and --cap-drop?
--cap-add grants additional Linux capabilities to a container beyond the default set, while --cap-drop removes capabilities from the default set. When both flags are used together, drops are processed first, followed by adds. For example, --cap-drop ALL --cap-add NET_ADMIN results in a container with only the CAP_NET_ADMIN capability.
Can I use capability names without the CAP_ prefix?
Yes, Apple Container accepts capability names with or without the CAP_ prefix, and the parser is case-insensitive. The Parser.capabilities() function in Parser.swift automatically normalizes inputs like net_admin or NET_RAW to the canonical CAP_NET_ADMIN and CAP_NET_RAW formats before kernel submission.
How do I run a completely unprivileged container?
Use --cap-drop ALL to remove every capability from the container process. If your application requires specific privileges (such as changing user IDs), you can then selectively re-add only those capabilities: --cap-drop ALL --cap-add SETUID --cap-add SETGID. This follows the principle of least privilege by explicitly whitelisting only necessary operations.
Where does Apple Container validate capability strings?
Validation occurs in Sources/Services/ContainerAPIService/Client/Parser.swift within the capabilities() function. This method checks each capability string against the known CapabilityName enumeration, converts inputs to uppercase, and ensures only valid kernel capabilities or the special ALL keyword are passed to the runtime.
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 →