# CL-0007: Filesystem Not Read-Only

**Severity:** MEDIUM

**References:**
- [OWASP Docker Security Rule #8](https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-8-set-filesystem-and-volumes-to-read-only)
- CIS Docker Benchmark 5.13 — Ensure that the container's root filesystem is mounted as read only

## What it detects

Any service that does not set `read_only: true`, including services that omit the key entirely or set `read_only: false`.

## Why it matters

A writable root filesystem allows an attacker who gains code execution inside a container to:

- Modify application binaries to inject backdoors
- Install additional attack tools (curl, wget, netcat)
- Persist malware that survives container restarts (if using bind mounts or named volumes)
- Modify configuration files to weaken security settings

A read-only filesystem forces the application to use only explicitly declared writable paths, limiting what an attacker can modify.

## Fix

Set the root filesystem to read-only and declare writable paths with `tmpfs`:

```yaml
services:
  web:
    image: nginx:1.27-alpine
    read_only: true
    tmpfs:
      - /tmp
      - /run
      - /var/cache/nginx
```

Common writable paths applications may need:
- `/tmp` — temporary files
- `/run` — PID files and sockets
- `/var/log` — application logs (prefer stdout/stderr instead)
- Application-specific cache directories

## Compatibility

`read_only: true` is an advanced hardening step. Most images write to paths beyond `/tmp` and `/run` at startup, and the minimal `tmpfs` list above is rarely sufficient on its own.

Observed failure modes:

- Images that write state or registry files under image-specific paths (e.g. `netdata` writes to `/etc/netdata/.container-hostname` and `/var/lib/netdata/registry`).
- Entrypoints that `chown` their data directory on first boot (e.g. `valkey`, `redis`). On a read-only rootfs `chown` fails unless the `CHOWN` capability is retained and the target path is on a writable mount.
- Applications that rewrite their own config at startup (some exporters, some TLS-fetching sidecars).

### Writable-path discovery

1. Run the service **without** `read_only` and let it reach a steady state.
2. Inspect what was written:

   ```
   docker diff <container>
   ```

3. Add each writable path as a `tmpfs` entry (for ephemeral data) or a named volume (for persistent data). Named volumes remain writable regardless of `read_only`.
4. Enable `read_only: true` and restart.

If the entrypoint runs `chown`, either pre-own the mount as the target UID:GID in the image build, or drop that specific service from the rule scope.

## Further reading

- [Docker best practices: read-only containers (ploetzli)](https://blog.ploetzli.ch/2025/docker-best-practices-read-only-containers/)
- [netdata read-only filesystem discussion](https://github.com/netdata/netdata/issues/14103)
