What does dropping a container's default privilege set add when the workload already runs under an unprivileged account?
answer
- account and powers are different axes
- granted to the process, not the account
- the default set exists for compatibility
- drop all, add back at most one
- change the port rather than keep the power
basics
~20 sWho the process is and what powers it holds are separate axes. Runtimes hand every container a default slice of superuser powers, held by the process rather than its account. Dropping them all and adding back only what is needed closes that second axis.
solid answer
~50 sA container runtime does not start a workload with either nothing or everything. It grants a **default set** of individually-named superuser powers - chosen so that ordinary images keep working - and that set belongs to the process, not to the account it runs under. So an unprivileged id and a non-empty privilege set are two different questions, and answering only the first leaves the second at the vendor's convenience default. The hardened form is deny-by-default: drop the whole set in the spec, then add back the single power the workload genuinely needs, if any. Most services need none, because the things that set exists for - binding a low-numbered port, changing another account's file ownership, adjusting the clock - are either avoidable or belong in the build. What you get is a declaration a reviewer can read: this workload holds nothing beyond its account.
go deeper
Know that a container starts with a default slice of superuser powers, separate from which account it runs as, and that hardened specs drop them.
Explain why the two are independent: the powers belong to the process, the default set exists for image compatibility, and deny-by-default means dropping everything and adding back at most one.
Work the breakages back to design changes - a high port behind a publishing hop, ownership set at build time - rather than restoring the set, and justify any power that stays.
Set the estate default and the exception process: empty by default, one documented power per exception with a review date, and a gate that refuses the rest.
## Two axes, not one Hardening a workload at start has two independent questions, and they get collapsed constantly: - **Which account does the process run under?** That decides what the filesystem and ordinary permission checks allow. - **Which powers does the process hold beyond that account?** That decides which privileged operations the kernel will let it perform regardless of the account, and it is a property of the process itself. A workload can run under an unprivileged id and still hold a slice of superuser powers, because those powers were granted by the runtime when it started the container and nothing about the account emptied them. That is the gap this declaration closes. ## What the default set is for Container runtimes ship a middle setting: a subset of the superuser's powers, granted to every container by default. It exists so that the enormous back catalogue of images that were written assuming a privileged environment still starts. The set typically allows things like binding to a low-numbered network port, changing the ownership of files, altering process identity, and a handful of other classically privileged operations - deliberately excluding the ones that would let a process reconfigure the machine outright. The design goal was compatibility, not your threat model. The set is the same for a workload that needs one of those powers and for the ninety-nine that need none. Note also that platforms differ in what their default set contains and in whether the spec can add powers back at all, so 'the default' is not one fixed list. ## Deny by default, then add back The hardened form is the same shape as every other allowlist: 1. **Drop the entire default set** in the workload spec. 2. **Start the workload and find what breaks.** In most cases nothing does. 3. **Add back exactly one power** if something genuinely needs it, and write down why next to the declaration. 4. **Prefer removing the need** over granting the power: bind a high-numbered port and let the publishing hop present the low one; set file ownership in the build instead of at start-up. Step four is where most of the value is. Almost every reason to keep a power turns out to be a habit inherited from running the same software directly on a host, and the containerised form has a different answer available. ## What breaks first, and what it means | symptom after dropping the set | what it points at | the usual answer | |---|---|---| | the listener fails to bind at start-up | a port below the privileged threshold | bind a high port; publish the low one at the hop in front | | a start-up step cannot change file ownership | ownership being fixed at runtime | fix ownership in the build instead | | a helper cannot change its process identity | a supervisor dropping privilege itself | start as the target id directly; drop the supervisor | | a diagnostic tool reports refusals | tooling that expects a privileged host | keep it out of the workload image | Each row is a design signal as much as a breakage: the workload was relying on a privileged environment, and that reliance is now visible in a spec rather than invisible in an image. ## What the declaration is worth Be precise about the benefit, because overselling it is a common interview stumble: - It **removes a step from an escalation chain**. A process with no powers has fewer paths from code execution to anything that touches the host. - It **makes the intent checkable**. An empty set in the spec is something a reviewer reads in a second and a gate can enforce before start. - It **does not stop the workload doing what it was authorized to do** - reading its data, calling its dependencies, using its credentials - because none of that requires elevated powers. - It **is not the boundary**, and it is not the filter over which kernel calls the process may make. Those are separate controls with separate failure modes. ## The trap The trap is treating the unprivileged account as the whole answer, shipping it, and calling the workload hardened. An unprivileged id with the default powers intact is better than the superuser and materially weaker than the same id with an empty set, and the difference is one line in the spec. The second trap is the mirror image: dropping everything, hitting a bind failure at three in the morning, and restoring the entire set instead of the one power - or better, changing the port so no power is needed at all.
- The service must serve on a low-numbered port. What are the options?Three, in order of preference: bind a high-numbered port inside and let the hop that publishes the workload present the low one, which is how most platforms expose traffic anyway; grant the single power that permits the low bind and nothing else, documented next to the declaration; or attach that power to the program file in the build so the process holds it only for the bind. Restoring the whole default set is not on the list.
- If the account is unprivileged, how can the process use those powers at all?Because they are held by the process independently of its account. They matter both when something inside switches to the superuser id and when a program that raises privilege is executed, and some operations are permitted on the strength of the power alone. Emptying the set removes that whole question rather than reasoning about which case applies.
- How do you decide whether a requested power is reasonable?Ask what it is for, then whether the need can be moved elsewhere - into the build, into the hop in front, or into the platform. If it cannot, grant that one power, record the justification beside the declaration, and set a date to revisit it. A request to restore the default set wholesale is a request for a list nobody has read.
saying these in an interview costs you the question
- An unprivileged account means the privilege set no longer matters.
- The default set is empty until the spec grants something.
- Dropping the set stops the workload opening network connections.
- Restore the whole set when one operation fails.
- The default set is the same on every platform.