# CL-0003: Privilege Escalation Not Blocked

**Severity:** MEDIUM

**References:**
- [OWASP Docker Security Rule #4](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-4-prevent-in-container-privilege-escalation)
- CIS Docker Benchmark 5.26 — Ensure that the container is restricted from acquiring additional privileges

## What it detects

Any service missing `no-new-privileges:true` in its `security_opt:` list.

## Why it matters

`no_new_privs` (kernel: `prctl(PR_SET_NO_NEW_PRIVS)`) blocks any privilege gain at `execve()` — including file capabilities (`setcap`-set xattrs on a binary) and the seccomp/LSM-bypass paths a setuid root binary can otherwise unlock. Without it, an attacker with shell access in a container started as a non-root user can pivot through any setuid binary or capability-marked binary the image ships, regaining privileges the runtime had otherwise constrained. CVE-2019-5736-style escape chains assume this gate is open.

Docker bounds the capability set regardless, so a setuid root binary alone won't gain capabilities the container doesn't already have — but seccomp filters and LSM transitions are bypassable through `execve` of a setuid binary unless `no_new_privs` is set.

## Fix

```yaml
security_opt:
  - no-new-privileges:true
```

## Compatibility

`no-new-privileges` blocks any privilege gain through `execve`, including the user-switching that many official images perform at startup. Applying it to those services will crash-loop the container.

Known-incompatible patterns:

- Entrypoints that drop from `root` to an unprivileged user via `gosu` or `su-exec` — e.g. the official `postgres`, `redis`, and `mysql` images, `itzg/minecraft-server`, `itzg/mc-backup`.
- Plugins or agents that rely on capability inheritance across `execve` — e.g. `netdata/netdata`.

Services that already start as a non-root user (`USER` in the Dockerfile, or `user:` in compose) and do not fork privileged helpers are safe.

### How to test

Add the setting, then run:

```
docker compose up
```

If the container exits immediately with a message like `operation not permitted` around `setuid`, `setgid`, or `execve`, the image is incompatible. Either remove the setting for that service or rebuild the image to start directly as the target user.

### Per-service handling

Until per-service config overrides land ([#5](https://github.com/tmatens/compose-lint/issues/5)), the workaround is to disable CL-0003 globally via `.compose-lint.yml` and leave a comment in the compose file on the services where it was intentionally omitted.

## Further reading

- [Linux kernel docs: `no_new_privs`](https://www.kernel.org/doc/html/latest/userspace-api/no_new_privs.html)
- [Docker capabilities and no-new-privs (raesene)](https://raesene.github.io/blog/2019/06/01/docker-capabilities-and-no-new-privs/)
- [Docker engine security — Linux kernel capabilities](https://docs.docker.com/engine/security/#linux-kernel-capabilities)

## See also

- [CL-0006](CL-0006.md) — capability dropping (defense-in-depth sibling)
- [CL-0009](CL-0009.md) — seccomp/AppArmor profiles (the LSM gate `no_new_privs` keeps closed across `execve`)
