Skip to content

CL-0009: Disabled seccomp and AppArmor profiles

Severity: HIGH

Derivation (see severity model):

  • Baseline: A — the attacker already has code execution in this container, as the workload uid
  • Precondition: Second flaw — removing the syscall filter and the LSM profile grants nothing on its own. It widens the kernel attack surface, and landing an escape still needs a kernel defect
  • Impact: Cross-container — the exposed surface is the kernel, shared by every container on the host
  • Qualifier/modifier: none
  • Derived: Second flaw × Cross-container = HIGH
  • Shipped: HIGH
  • Evidence: _cl0009 (a seccomp filter is active by default). The gate is capability-shaped rather than absolute: at default capabilities bpf(2) and init_module(2) return EPERM, and with the matching capability added they reach the kernel and fail on their arguments instead (EINVAL, ENOEXEC) — verified live on Docker 29.1.3

References: - OWASP Docker Security Rule #6 - CIS Docker Benchmark 5.22 — Ensure that the default seccomp profile is not Disabled - CIS Docker Benchmark 5.2 — Ensure that, if applicable, an AppArmor Profile is enabled - CIS Docker Benchmark 5.3 — Ensure that, if applicable, SELinux security options are set

What it detects

Any service with seccomp:unconfined, apparmor:unconfined, or label:disable in security_opt.

This rule only flags explicitly disabled profiles. Services that omit security_opt entirely are not flagged — the host applies default profiles automatically. Custom profiles (e.g., seccomp:custom.json) and SELinux label overrides that don't disable confinement (e.g., label:user:system_u) are also not flagged. SELinux label:type:spc_t (super-privileged container) is functionally similar to label:disable but is currently out of scope for this rule — it requires SELinux-specific knowledge of type transitions to evaluate.

Why it matters

Linux distributions apply security profiles by default:

  • seccomp: Docker's default profile (profiles/seccomp/default.json) blocks dozens of syscalls outright and gates many more behind a specific capability. Disabling it gives the container the kernel's full unrestricted syscall surface — a massively expanded attack surface for kernel exploits.

    The gate is mostly capability-shaped rather than absolute, which matters when reasoning about what the profile is actually protecting. At default capabilities bpf(2) and init_module(2) both return EPERM; add BPF or SYS_MODULE and the same calls reach the kernel and fail on their arguments instead — EINVAL and ENOEXEC respectively (verified on Docker 29.1.3). So the default profile is not what stands between a container and those syscalls; the capability set is, and dropping capabilities (CL-0006) is the control that closes them. kexec_load(2) is the counter-example that stays shut: it returns EPERM even with SYS_BOOT held. - AppArmor: Docker's default profile (docker-default) restricts file paths in /proc and /sys, blocks ptrace of host processes, and deny-lists /proc/kcore and similar. Disabling it removes those mandatory access controls beyond what capabilities alone enforce. - SELinux: On RHEL-family hosts, applies type enforcement via labels (container_t) that confine what files and processes a container can access. label:disable turns off type-enforcement confinement for the container; the container then runs in the calling user's domain, which on a typical container host is unconfined_t.

Explicitly disabling any of these is an intentional weakening of container isolation that should only be done with strong justification.

Fix

Remove the unconfined profile — Docker's defaults apply automatically:

# Before
services:
  web:
    image: nginx:1.27-alpine
    security_opt:
      - seccomp:unconfined

# After
services:
  web:
    image: nginx:1.27-alpine
    security_opt:
      - no-new-privileges:true

If your application requires specific syscalls blocked by the default profile, create a custom seccomp profile rather than disabling it entirely:

security_opt:
  - seccomp:custom-seccomp.json

When to suppress

  • A debugger or tracer that genuinely needs ptrace of arbitrary syscalls — narrow to a custom profile if at all possible, and never run it on a production network.
  • Sandboxes that need to run their own seccomp profile (browsers, language runtimes) — seccomp:unconfined is sometimes the documented way to let the inner sandbox install its filter. Suppress only when verified.

ATT&CK coverage

Remediating this finding contributes to mitigating the following MITRE ATT&CK techniques (pinned to ATT&CK v18). compose-lint is a static analyser, so this is mitigation coverage — it detects nothing at runtime.

Technique Tactic
T1685 Disable or Modify Tools Defense Impairment

See also

  • CL-0002privileged: true (drops the default seccomp and AppArmor profiles)
  • CL-0003no-new-privileges:true (keeps the LSM/seccomp gate closed across execve)