Scanner Sécurité Docker Compose

Analyse profonde de sécurité docker-compose détectant conteneurs privilégiés et secrets

Docker Compose Security Scanning

Docker Compose security scanning is the practice of analyzing docker-compose.yml files for misconfigurations that introduce privilege escalation paths, secret exposure, network isolation failures, and resource exhaustion vulnerabilities into containerized environments. While Docker Compose simplifies multi-container orchestration, its declarative syntax makes it dangerously easy to grant containers root-level host access, expose credentials in plaintext environment variables, bypass network segmentation, or deploy services without memory and CPU constraints — any of which can transform a single compromised container into a full host takeover.

Unlike general-purpose Docker Compose inspectors that focus on service topology, dependency graphs, and port mappings, a security scanner examines the attack surface of each service definition. It identifies configurations where privileged: true grants unrestricted kernel access, where cap_add: [SYS_ADMIN] enables container escapes, where host network mode eliminates network namespace isolation, and where volume mounts like /var/run/docker.sock or / give containers control over the Docker daemon or the entire host filesystem. These findings are classified by severity and accompanied by specific remediation guidance — turning a passive configuration review into an actionable security audit.

Privileged Mode and Linux Capability Abuse

The most dangerous Docker Compose misconfiguration is running containers in privileged mode. When privileged: true is set on a service, Docker disables all security mechanisms — removing seccomp profiles, AppArmor confinement, and capability restrictions. The container gains full access to all host devices (/dev), can load kernel modules, modify iptables rules, and mount host filesystems without restriction. A single compromised process inside a privileged container effectively becomes root on the host system.

  • privileged: true — Grants all Linux capabilities and removes all security profiles. The container has unrestricted access to the host kernel. This should never appear in production Compose files. In the rare cases where device access is genuinely needed (GPU workloads, USB passthrough), use devices directives with specific device paths instead.
  • cap_add: [SYS_ADMIN] — The most abused individual capability. SYS_ADMIN permits mounting filesystems, using ptrace, modifying kernel parameters via /proc/sys, and performing namespace operations. Attackers use SYS_ADMIN to escape containers by mounting the host filesystem from within. Legitimate alternatives: use specific capabilities like DAC_READ_SEARCH for file access or NET_ADMIN for networking only when absolutely required.
  • cap_add: [NET_ADMIN, NET_RAW] — NET_ADMIN allows modification of network interfaces, routing tables, and firewall rules inside the container's network namespace. NET_RAW enables raw socket creation for packet crafting. Together they allow ARP spoofing, traffic interception, and network-level attacks against other containers on the same Docker network.
  • Missing cap_drop: [ALL] — Docker containers inherit a default set of capabilities including CHOWN, DAC_OVERRIDE, SETUID, SETGID, and NET_BIND_SERVICE. The security best practice is to drop all capabilities and add back only the specific ones required: cap_drop: [ALL] followed by cap_add: [NET_BIND_SERVICE] for services that only need to bind privileged ports.

Secret Exposure and Environment Variable Risks

Docker Compose files frequently become repositories of plaintext secrets. Environment variables defined directly in docker-compose.yml are stored unencrypted in version control, visible in docker inspect output, logged by orchestration systems, and accessible to any process inside the container via /proc/1/environ. A security scanner identifies hardcoded credentials and recommends secure alternatives.

  • Inline secrets in environment: blocks: Patterns like DB_PASSWORD=production_s3cr3t, AWS_SECRET_ACCESS_KEY=..., or API_KEY=sk_live_... directly in the Compose file. These persist in Git history even after removal and are visible to anyone with repository access.
  • Secrets in .env files without .gitignore protection: While env_file: .env separates secrets from the Compose file, the .env file itself is often committed accidentally. A scanner flags env_file directives and recommends verifying exclusion from version control.
  • Docker Secrets vs environment variables: For Docker Swarm mode and Compose v3+, the secrets top-level key provides encrypted secret storage. Secrets are mounted as files at /run/secrets/<name> with restricted permissions, never appearing in docker inspect or container environment. The scanner recommends migrating from environment: to secrets: for any variable containing credentials.
  • Build arguments containing secrets: Using args: in the build: section to pass secrets like SSH keys or tokens embeds them in image layers. Even multi-stage builds retain build arguments in the build cache. The scanner flags args: values that match secret patterns.

Network Isolation and Exposure Vulnerabilities

Docker's network namespacing is a primary security boundary between containers and the host. Compose configurations that weaken or eliminate this isolation expose services to attacks from adjacent containers, the host network, or external traffic that should never reach internal services.

  • network_mode: host — Removes network namespace isolation entirely. The container shares the host's network stack, seeing all host interfaces, all listening ports, and all network traffic. Services binding to 0.0.0.0 inside the container are directly accessible on the host without port mapping. This eliminates Docker's network-level containment and should only be used when specific host networking features are required (typically performance-sensitive applications needing kernel bypass).
  • Missing network segmentation: Compose files that define all services on the default bridge network allow unrestricted inter-container communication. A compromised web frontend can directly probe the database, cache, and queue services. The scanner recommends defining explicit networks — a frontend network for public-facing services and a backend network for data stores — with services attached only to the networks they genuinely need.
  • Unnecessary port publishing: Publishing internal service ports with ports: ["5432:5432"] for databases or ports: ["6379:6379"] for Redis exposes these services to the host network (and potentially to external traffic if the host has a public IP). Internal services should communicate through Docker networks without port publishing. Only edge services (reverse proxies, load balancers) need published ports.
  • Binding to all interfaces: Port mappings without explicit bind addresses (ports: ["8080:80"]) default to binding on 0.0.0.0, accepting connections from any network interface. For development environments, ports: ["127.0.0.1:8080:80"] restricts access to localhost. The scanner flags published ports without bind address restrictions.

Resource Limits and Denial of Service Prevention

Containers without resource constraints can consume unlimited CPU, memory, and disk I/O from the host system. A single runaway container — whether due to a memory leak, infinite loop, or malicious workload — can starve other containers and host processes, causing cascading failures across the entire Docker environment.

  • Missing memory limits: Without mem_limit (Compose v2) or deploy.resources.limits.memory (Compose v3), a container can allocate memory until the host OOM killer intervenes — killing arbitrary processes including other containers or critical system services. Every production service should have explicit memory limits based on profiled resource usage plus a safety margin.
  • Missing CPU limits: Without cpus or deploy.resources.limits.cpus, a container can monopolize all CPU cores. This is particularly dangerous in multi-tenant environments where a crypto-mining attack in one container degrades all co-located services. Set CPU limits to prevent any single container from consuming more than its fair share.
  • No restart policy or uncontrolled restarts: A restart: always policy without resource limits means a crashing container continuously restarts, potentially creating fork-bomb-like resource consumption. Combine restart policies with resource limits and consider restart: on-failure with a maximum retry count to prevent infinite crash loops.
  • PID limits: Without pids_limit, a container can create unlimited processes via fork bombs, exhausting the kernel's PID space and affecting the entire host. Setting pids_limit: 100 (or an appropriate value for the workload) prevents process-based DoS attacks.

Dangerous Volume Mounts and Filesystem Access

Volume mounts bridge the isolation boundary between container and host filesystems. While necessary for persistent storage and configuration injection, certain mount paths grant containers the ability to escape their confinement, compromise other containers, or modify critical host system files.

  • /var/run/docker.sock mounts: Mounting the Docker socket gives the container full control over the Docker daemon — it can create privileged containers, access any volume, read secrets from other containers, and effectively gain root access to the host. This is the single most dangerous volume mount in Docker security. If Docker API access is required (CI/CD runners, monitoring tools), use TCP-based access with TLS mutual authentication and restrict API endpoints via authorization plugins.
  • Root filesystem mounts (/:/host): Mounting the entire host filesystem into a container allows reading and writing any file — /etc/shadow for credential theft, /etc/crontab for persistence, or /root/.ssh for lateral movement. Even read-only root mounts (/:/host:ro) expose sensitive configuration and credentials.
  • /etc and /proc mounts: Mounting /etc enables modification of host configuration including passwd, sudoers, and resolv.conf. Mounting /proc from the host exposes process information and kernel tunables. Both paths should be avoided unless a specific file within them is needed (and can be mounted individually).
  • Missing :ro flag: Volumes mounted without the read-only flag grant write access by default. Configuration files, TLS certificates, and reference data that containers only need to read should always use :ro to enforce the principle of least privilege: ./config:/app/config:ro.

Code Examples

Insecure vs Secure Docker Compose Configuration

# ⚠️ INSECURE: Common security misconfigurations
# This configuration has multiple critical vulnerabilities

version: "3.8"
services:
  web:
    image: myapp:latest
    privileged: true                    # CRITICAL: Full host access
    cap_add:
      - SYS_ADMIN                       # CRITICAL: Container escape risk
      - NET_ADMIN                       # HIGH: Network manipulation
    network_mode: host                  # HIGH: No network isolation
    ports:
      - "3000:3000"                     # Binds to all interfaces
    environment:
      - DB_PASSWORD=super_secret_123    # CRITICAL: Plaintext secret
      - AWS_SECRET_ACCESS_KEY=wJalrXU   # CRITICAL: Cloud credentials
      - JWT_SECRET=my-signing-key       # HIGH: Auth token forgery
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock  # CRITICAL: Docker escape
      - /:/host                         # CRITICAL: Full host filesystem
      - ./config:/app/config            # Missing :ro flag

  database:
    image: postgres:15
    ports:
      - "5432:5432"                     # HIGH: DB exposed to network
    environment:
      - POSTGRES_PASSWORD=admin123      # CRITICAL: Weak plaintext password
    # No resource limits                # MEDIUM: DoS vulnerability

---
# ✅ SECURE: Hardened configuration with proper security controls

version: "3.8"
services:
  web:
    image: myapp:latest
    cap_drop:
      - ALL                             # Drop all capabilities first
    cap_add:
      - NET_BIND_SERVICE                # Only add what's needed
    read_only: true                     # Read-only root filesystem
    security_opt:
      - no-new-privileges:true          # Prevent privilege escalation
    networks:
      - frontend                        # Explicit network segmentation
    ports:
      - "127.0.0.1:3000:3000"          # Bind to localhost only
    env_file:
      - .env                            # Secrets in .gitignored file
    secrets:
      - db_password                     # Docker secrets for sensitive data
      - jwt_signing_key
    volumes:
      - ./config:/app/config:ro         # Read-only mount
    tmpfs:
      - /tmp                            # Writable temp in memory only
    deploy:
      resources:
        limits:
          memory: 512M                  # Memory ceiling
          cpus: "0.5"                   # CPU limit
        reservations:
          memory: 256M                  # Guaranteed minimum
    pids_limit: 100                     # Prevent fork bombs

  database:
    image: postgres:15
    networks:
      - backend                         # Internal-only network
    # No published ports — accessed via Docker network only
    environment:
      - POSTGRES_PASSWORD_FILE=/run/secrets/db_password
    secrets:
      - db_password
    volumes:
      - pgdata:/var/lib/postgresql/data # Named volume for persistence
    deploy:
      resources:
        limits:
          memory: 1G
          cpus: "1.0"
    pids_limit: 200

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true                      # No external access

volumes:
  pgdata:

secrets:
  db_password:
    file: ./secrets/db_password.txt     # External secret file
  jwt_signing_key:
    file: ./secrets/jwt_key.txt

Output:

Security Scan Results:
─────────────────────
Score: 15/100 (INSECURE configuration)

CRITICAL (4 findings):
  • Line 6:  privileged: true — Full host kernel access, container escape trivial
  • Line 15: DB_PASSWORD in plaintext — Secret exposed in version control
  • Line 20: /var/run/docker.sock mounted — Container can control Docker daemon
  • Line 21: Root filesystem (/) mounted — Full host read/write access

HIGH (4 findings):
  • Line 8:  cap_add: SYS_ADMIN — Enables namespace manipulation and escape
  • Line 10: network_mode: host — All network isolation removed
  • Line 28: Database port 5432 published — Direct network exposure
  • Line 17: AWS_SECRET_ACCESS_KEY in plaintext — Cloud credential leak

MEDIUM (2 findings):
  • Line 13: Port 3000 bound to 0.0.0.0 — Accessible from all interfaces
  • Line 30: No resource limits on database — DoS vulnerability

Recommendations:
  1. Remove privileged: true, use specific capabilities instead
  2. Move all secrets to Docker secrets or external vault
  3. Remove Docker socket mount, use TCP+TLS if API access needed
  4. Add deploy.resources.limits to all services
  5. Define explicit networks with internal flag for backend

Standards & Specifications

  • CIS Docker Benchmark — Industry-standard security configuration guidelines for Docker deployments covering host, daemon, container runtime, and image security
  • Docker Security Documentation — Official Docker documentation on kernel namespaces, control groups, capabilities, seccomp profiles, and container security best practices
  • OWASP Docker Security Cheat Sheet — OWASP guidelines for secure Docker configuration including network segmentation, secret management, and least-privilege containers
  • Docker Compose Specification — Deploy — Official specification for resource limits, restart policies, and deployment constraints in Docker Compose files

Questions Fréquentes

How is the Docker Compose Security Scanner different from the Docker Compose Inspector?

The Docker Compose Inspector focuses on general best practices and misconfigurations (exposed ports, latest tags, unused networks). The Security Scanner goes deeper with security-specific analysis: it detects host namespace sharing (PID, IPC, network), writable root filesystems, containers running as root, hardcoded secrets in environment variables, dangerous Linux capabilities, missing health checks, insecure registries, and missing CPU limits. Use the Inspector for quick checks, and the Security Scanner for thorough security audits.

What makes a container 'privileged' and why is it critical?

A container with privileged: true or cap_add: ALL has unrestricted access to the host kernel, all devices, and all Linux capabilities. An attacker exploiting a vulnerability in a privileged container can escape to the host, access other containers, modify kernel parameters, and potentially compromise the entire infrastructure. The scanner flags this as critical because it completely eliminates container isolation.

Why does the scanner flag containers without a user specification?

By default, Docker containers run as root (UID 0). If an attacker gains code execution inside the container, they have root privileges — which combined with any misconfiguration (like a writable host volume) can lead to host compromise. Setting user: '1000:1000' or a named non-root user limits the blast radius of a container breach.

What are dangerous Linux capabilities?

Linux capabilities are fine-grained privileges that replace the traditional root/non-root binary. Dangerous capabilities include SYS_ADMIN (near-root access, mount operations), NET_ADMIN (network configuration, firewall changes), SYS_PTRACE (debug other processes, read memory), SYS_RAWIO (raw I/O access to hardware), and NET_RAW (create raw sockets for network sniffing). The scanner flags these individually because each introduces specific attack vectors.

Why is sharing host PID/IPC/network namespace dangerous?

Host namespace sharing breaks container isolation at the kernel level. pid: host lets the container see and signal all host processes. ipc: host allows access to host shared memory. network_mode: host bypasses Docker network isolation entirely — the container shares the host's network stack, ports, and can sniff all traffic. Each is flagged as critical or high severity because they enable direct host-level attacks.

How does the scanner detect secrets in environment variables?

The scanner checks environment variable names and values against known secret patterns: passwords, API keys, tokens, private keys, database URLs, and session secrets. It specifically flags hardcoded values (not variable references like ${SECRET}) because these end up in version control, docker inspect output, and process listings. The recommendation is to use Docker secrets, env_file, or external secret managers.

What is a writable root filesystem and why does it matter?

By default, a container's root filesystem is writable. If an attacker gains code execution, they can modify application binaries, install tools, or alter configuration files. Setting read_only: true makes the filesystem immutable — the attacker cannot persist changes. Use tmpfs mounts for paths that need writes (like /tmp or /var/run).

Is my docker-compose.yml sent to any server?

No. All security scanning happens entirely in your browser using JavaScript. Your Docker Compose configuration — which may contain service names, internal hostnames, registry URLs, and infrastructure topology — never leaves your device. No data is stored, logged, or transmitted.