catalog / docker.io/library/redis

docker.io/library/redis

validated  pin current  tags: 8.2.7

redis — minimum capabilities, derived by drop-test against the default
(root-then-drop) invocation.

capabilities: cap_drop:[ALL] + cap_add:[SETGID, SETUID]. Both are STARTUP caps:
the entrypoint starts as root and re-execs the server as the redis user
(uid 999). The failure mode when either is dropped is the sharp edge: redis
does NOT crash — it keeps serving correctly AS ROOT. Only the correctness
check's non-root uid assertion catches it; a health/round-trip-only workload
would falsely derive both as removable (same shape as valkey, same lesson).

run_config is the image default: no user: override, no data bind (the image's
own /data anonymous volume, initialised redis-owned, so nothing is chowned),
plus no-new-privileges:true (the catalog convention, matching valkey — gosu's
drop is a capability-gated setuid syscall, not a setuid-bit escalation, so
no-new-privileges does not block it; re-derivation confirmed [SETGID, SETUID]
under it). A deployment binding a foreign-owned data volume would need CHOWN
back — the minimum is scoped to the invocation in run_config. See
criteria/docker.io/library/redis.md.
filesystem: read_only:true, tmpfs:[]. redis's only writes are to its /data
store (a declared VOLUME, auto-mounted writable; a named volume in production,
never tmpfs). Under a read-only rootfs it serves the SET/GET + SAVE round-trip
with no tmpfs. /tmp was drop-tested and is NOT required. Derived by drop-test.

Use it

services:
  redis:
    image: docker.io/library/redis:8.2.7
    cap_drop: [ALL]
    cap_add: [SETGID, SETUID]
    security_opt: ["no-new-privileges:true"]
    read_only: true

This profile has multiple dimensions and is applied as a unit. Dimensions can interact — a capability can be required only because of a sibling read-only/tmpfs recommendation — so where they do, the minimum was derived under the sibling dimension's context. See the criteria doc and each dimension's recorded invocation below before applying them separately.

Dimension: capabilities

cap_drop: ALL + cap_add: SETGID, SETUID

toolcontainer-sec-derive 0.7.0
observerdrop-test
validated imagedocker.io/library/redis@sha256:d30960f73a59…
validated date2026-07-16
confidencehigh
validated viadrop-test, ci-smoke
workloadprofiles/workloads/redis.sh
workload sha256bbe4e8fcc050…
ig versionv0.51.0

Drop-test evidence

Each candidate removed in turn, the container restarted, and the workload re-verified.

RemovedVerdictObserved
CHOWNremovablecorrect: SET/GET + SAVE ok, redis-server uid=999 (non-root)
DAC_OVERRIDEremovablecorrect: SET/GET + SAVE ok, redis-server uid=999 (non-root)
FSETIDremovablecorrect: SET/GET + SAVE ok, redis-server uid=999 (non-root)
FOWNERremovablecorrect: SET/GET + SAVE ok, redis-server uid=999 (non-root)
MKNODremovablecorrect: SET/GET + SAVE ok, redis-server uid=999 (non-root)
NET_RAWremovablecorrect: SET/GET + SAVE ok, redis-server uid=999 (non-root)
SETGIDrequiredredis-server running as ROOT or not found: uid=0
SETUIDrequiredredis-server running as ROOT or not found: uid=0
SETFCAPremovablecorrect: SET/GET + SAVE ok, redis-server uid=999 (non-root)
SETPCAPremovablecorrect: SET/GET + SAVE ok, redis-server uid=999 (non-root)
NET_BIND_SERVICEremovablecorrect: SET/GET + SAVE ok, redis-server uid=999 (non-root)
SYS_CHROOTremovablecorrect: SET/GET + SAVE ok, redis-server uid=999 (non-root)
KILLremovablecorrect: SET/GET + SAVE ok, redis-server uid=999 (non-root)
AUDIT_WRITEremovablecorrect: SET/GET + SAVE ok, redis-server uid=999 (non-root)

Recorded invocation (run_config)

The minimum is valid for this invocation; a different user:, volume state, or entrypoint can change it.

security_optno-new-privileges:true
Workload script (profiles/workloads/redis.sh)
#!/usr/bin/env bash
# Workload exerciser for the redis reference image.
#
# Connects to a running container and drives a representative round-trip —
# PING, SET and GET a key, then a synchronous SAVE (a real RDB write to the
# /data volume). Returns 0 if every step succeeded. The non-root
# privilege-drop is asserted by the drop-test correctness check (see the
# criteria doc), not here — that assertion is load-bearing: with SETUID or
# SETGID dropped, redis keeps serving correctly AS ROOT, so a workload-only
# check would falsely call both removable.
#
# Required env: REDISCONTAINER (target container name or id).
set -euo pipefail

: "${REDISCONTAINER:?REDISCONTAINER must be set}"

# Probes run as the redis user (never root — a root probe's own needs would
# pollute the derived minimum) and capture-then-match (never `producer | grep -q`
# under pipefail: grep's early exit SIGPIPEs the producer and a matching
# response reads as failure).
deadline=$((SECONDS + 30))
until grep -qi PONG <<<"$(docker exec --user redis "${REDISCONTAINER}" redis-cli ping 2>/dev/null)"; do
    if (( SECONDS >= deadline )); then
        echo "redis did not respond to PING in 30s" >&2
        exit 1
    fi
    sleep 1
done

docker exec --user redis "${REDISCONTAINER}" redis-cli set csd:probe ok >/dev/null
value="$(docker exec --user redis "${REDISCONTAINER}" redis-cli get csd:probe | tr -d '[:space:]')"
if [ "$value" != ok ]; then
    echo "GET returned '${value}' (expected 'ok')" >&2
    exit 1
fi

grep -q OK <<<"$(docker exec --user redis "${REDISCONTAINER}" redis-cli save)" || {
    echo "SAVE failed (cannot write the RDB to /data)" >&2
    exit 1
}

Dimension: filesystem

read_only: true, no tmpfs

toolcontainer-sec-derive 0.7.0
observerdrop-test
validated imagedocker.io/library/redis@sha256:d30960f73a59…
validated date2026-07-16
confidencehigh
validated viadrop-test, ci-smoke
workloadprofiles/workloads/redis.sh
workload sha256bbe4e8fcc050…
ig versionv0.51.0

Drop-test evidence

Each candidate removed in turn, the container restarted, and the workload re-verified.

RemovedVerdictObserved
/tmpremovablecorrect: SET/GET + SAVE ok, redis-server uid=999 (non-root)
Workload script (profiles/workloads/redis.sh)
#!/usr/bin/env bash
# Workload exerciser for the redis reference image.
#
# Connects to a running container and drives a representative round-trip —
# PING, SET and GET a key, then a synchronous SAVE (a real RDB write to the
# /data volume). Returns 0 if every step succeeded. The non-root
# privilege-drop is asserted by the drop-test correctness check (see the
# criteria doc), not here — that assertion is load-bearing: with SETUID or
# SETGID dropped, redis keeps serving correctly AS ROOT, so a workload-only
# check would falsely call both removable.
#
# Required env: REDISCONTAINER (target container name or id).
set -euo pipefail

: "${REDISCONTAINER:?REDISCONTAINER must be set}"

# Probes run as the redis user (never root — a root probe's own needs would
# pollute the derived minimum) and capture-then-match (never `producer | grep -q`
# under pipefail: grep's early exit SIGPIPEs the producer and a matching
# response reads as failure).
deadline=$((SECONDS + 30))
until grep -qi PONG <<<"$(docker exec --user redis "${REDISCONTAINER}" redis-cli ping 2>/dev/null)"; do
    if (( SECONDS >= deadline )); then
        echo "redis did not respond to PING in 30s" >&2
        exit 1
    fi
    sleep 1
done

docker exec --user redis "${REDISCONTAINER}" redis-cli set csd:probe ok >/dev/null
value="$(docker exec --user redis "${REDISCONTAINER}" redis-cli get csd:probe | tr -d '[:space:]')"
if [ "$value" != ok ]; then
    echo "GET returned '${value}' (expected 'ok')" >&2
    exit 1
fi

grep -q OK <<<"$(docker exec --user redis "${REDISCONTAINER}" redis-cli save)" || {
    echo "SAVE failed (cannot write the RDB to /data)" >&2
    exit 1
}

Evidence & provenance

Raw profile YAML
# redis — minimum capabilities, derived by drop-test against the default
# (root-then-drop) invocation.
#
# capabilities: cap_drop:[ALL] + cap_add:[SETGID, SETUID]. Both are STARTUP caps:
# the entrypoint starts as root and re-execs the server as the redis user
# (uid 999). The failure mode when either is dropped is the sharp edge: redis
# does NOT crash — it keeps serving correctly AS ROOT. Only the correctness
# check's non-root uid assertion catches it; a health/round-trip-only workload
# would falsely derive both as removable (same shape as valkey, same lesson).
#
# run_config is the image default: no user: override, no data bind (the image's
# own /data anonymous volume, initialised redis-owned, so nothing is chowned),
# plus no-new-privileges:true (the catalog convention, matching valkey — gosu's
# drop is a capability-gated setuid syscall, not a setuid-bit escalation, so
# no-new-privileges does not block it; re-derivation confirmed [SETGID, SETUID]
# under it). A deployment binding a foreign-owned data volume would need CHOWN
# back — the minimum is scoped to the invocation in run_config. See
# criteria/docker.io/library/redis.md.
# filesystem: read_only:true, tmpfs:[]. redis's only writes are to its /data
# store (a declared VOLUME, auto-mounted writable; a named volume in production,
# never tmpfs). Under a read-only rootfs it serves the SET/GET + SAVE round-trip
# with no tmpfs. /tmp was drop-tested and is NOT required. Derived by drop-test.
#
schema_version: "1.5"
image: docker.io/library/redis
reference_url: https://tmatens.github.io/container-security-profiles/profiles/docker.io/library/redis.html
applies_to:
  tags: ["8.2.7"]
status: validated
dimensions:
  capabilities:
    cap_drop: [ALL]
    cap_add: [SETGID, SETUID]
    derivation:
      tool: container-sec-derive
      tool_version: "0.7.0"
      sidecar_schema_version: "1.5"
      observer: drop-test
      validated_image: docker.io/library/redis@sha256:d30960f73a599496d8b2802c97758bab6b1cd421fd06337f837779c47a57e1f3
      validated_date: "2026-07-16"
      duration_seconds: 47
      confidence: high
      workload: profiles/workloads/redis.sh
      workload_sha256: "bbe4e8fcc050581d48d2dd45a086ee1f4d471845bf8e27a6ca5147c6bc688cce"
      validated_via: [drop-test, ci-smoke]
      observation_backend:
        ig_version: v0.51.0
        gadgets: []
      run_config:
        user: ""
        command: []
        entrypoint: ""
        network: ""
        pid: ""
        devices: []
        security_opt: ["no-new-privileges:true"]
        mounts: []
        env: []
      drop_test:
        checks:
          - {removed: CHOWN, required: false, observed: "correct: SET/GET + SAVE ok, redis-server uid=999 (non-root)"}
          - {removed: DAC_OVERRIDE, required: false, observed: "correct: SET/GET + SAVE ok, redis-server uid=999 (non-root)"}
          - {removed: FSETID, required: false, observed: "correct: SET/GET + SAVE ok, redis-server uid=999 (non-root)"}
          - {removed: FOWNER, required: false, observed: "correct: SET/GET + SAVE ok, redis-server uid=999 (non-root)"}
          - {removed: MKNOD, required: false, observed: "correct: SET/GET + SAVE ok, redis-server uid=999 (non-root)"}
          - {removed: NET_RAW, required: false, observed: "correct: SET/GET + SAVE ok, redis-server uid=999 (non-root)"}
          - {removed: SETGID, required: true, observed: "redis-server running as ROOT or not found: uid=0"}
          - {removed: SETUID, required: true, observed: "redis-server running as ROOT or not found: uid=0"}
          - {removed: SETFCAP, required: false, observed: "correct: SET/GET + SAVE ok, redis-server uid=999 (non-root)"}
          - {removed: SETPCAP, required: false, observed: "correct: SET/GET + SAVE ok, redis-server uid=999 (non-root)"}
          - {removed: NET_BIND_SERVICE, required: false, observed: "correct: SET/GET + SAVE ok, redis-server uid=999 (non-root)"}
          - {removed: SYS_CHROOT, required: false, observed: "correct: SET/GET + SAVE ok, redis-server uid=999 (non-root)"}
          - {removed: KILL, required: false, observed: "correct: SET/GET + SAVE ok, redis-server uid=999 (non-root)"}
          - {removed: AUDIT_WRITE, required: false, observed: "correct: SET/GET + SAVE ok, redis-server uid=999 (non-root)"}
  filesystem:
    read_only: true
    tmpfs: []
    derivation:
      tool: container-sec-derive
      tool_version: "0.7.0"
      sidecar_schema_version: "1.5"
      observer: drop-test
      validated_image: docker.io/library/redis@sha256:d30960f73a599496d8b2802c97758bab6b1cd421fd06337f837779c47a57e1f3
      validated_date: "2026-07-16"
      duration_seconds: 2
      confidence: high
      workload: profiles/workloads/redis.sh
      workload_sha256: "bbe4e8fcc050581d48d2dd45a086ee1f4d471845bf8e27a6ca5147c6bc688cce"
      validated_via: [drop-test, ci-smoke]
      observation_backend:
        ig_version: v0.51.0
        gadgets: []
      run_config:
        user: ""
        command: []
        entrypoint: ""
        network: ""
        pid: ""
        devices: []
        security_opt: []
        mounts: []
        env: []
      drop_test:
        checks:
          - {removed: /tmp, required: false, observed: "correct: SET/GET + SAVE ok, redis-server uid=999 (non-root)"}