catalog / exploratory/docker.io/netdata/netdata

docker.io/netdata/netdata

exploratory  pin unchecked  tags:

netdata cap_add_validation — EXPLORATORY (below the validated bar; see
acceptance_contract_violations). Validates netdata's officially-recommended
grant (cap_add: [SYS_PTRACE, SYS_ADMIN]) against observed cap_capable
syscalls, under the same representative run as the validated capabilities
dimension (pid:host, host network, host /proc,/sys,docker.sock ro-mounts).

Why exploratory, probably permanently: the target itself generates the
event flood. apps.plugin trips a SYS_PTRACE cap_capable check for every
/proc/<pid> read of every host process, every second (~44k held events per
30s), so the perf-buffer drop rate stays >=1% on any host — measured 1.9%
over 310s on a 16-core desktop with in-gadget container filtering active,
~4% on the quiet csd-derive VM. In-kernel filtering cannot help when every
dropped event is attributable to the target (csd#407 discussion).

READ WITH the validated capabilities dimension in
catalog/docker.io/netdata/netdata.yaml, which is AUTHORITATIVE for the
minimum: its drop-test proves SYS_ADMIN is droppable (the image ships no
ebpf.plugin, so nothing gates on it) while SYS_PTRACE is required. This
observation adds the complementary fact: netdata EXERCISES SYS_ADMIN
opportunistically when granted (collectors probe SYS_ADMIN-gated
operations and degrade gracefully without) — "exercised" here does NOT
mean "required". The two observers answer different questions; where they
diverge, drop-test wins for the minimum.

Use it

services:
  netdata:
    image: docker.io/netdata/netdata:<tag>

Dimension: cap_add_validation

cap_add_validation

toolcontainer-sec-derive 0.7.0
observercapadd
validated imagedocker.io/netdata/netdata@sha256:bcc822ec685d…
validated date2026-07-16
confidencelow
validated viabpf-observation
workloadprofiles/workloads/netdata.sh
workload sha256ba6ae07c95ee…
ig versionv0.51.0
Workload script (profiles/workloads/netdata.sh)
#!/usr/bin/env bash
# Correctness check for netdata cap_add bisection (docker.io/netdata/netdata).
#
# netdata is run as a host monitor — pid:host, host /proc,/sys,/var/run/docker.sock
# read-only mounts, host network, and the granted cap set present — then each
# candidate capability is dropped, the container restarted, and this correctness check
# decides "still works CORRECTLY". Correctness is not just liveness: netdata must
# still drop root -> the unprivileged `netdata` user AND still collect per-process
# metrics for non-root processes, or the derivation is wrong.
#
# Required env: NDCONTAINER (target container name or id).
# Exit 0 = correct; non-zero = the dropped capability was required.
set -euo pipefail
: "${NDCONTAINER:?NDCONTAINER must be set}"

API=http://localhost:19999
# chart_nonzero <chart-id> -> 0 if the latest sample has a NON-ZERO value.
# Non-zero, not merely non-null: apps.plugin denied access to a process's /proc
# reports the metric as 0 (not null), so a non-null check passes on broken
# collection. A live process always has open fds > 0.
chart_nonzero() {
    curl -fsS --max-time 4 "${API}/api/v1/data?chart=$1&after=-20&points=1&format=json" 2>/dev/null \
        | python3 -c 'import sys,json
try:
 rows=json.load(sys.stdin).get("data",[])
 sys.exit(0 if [v for r in rows for v in r[1:] if v is not None and v != 0] else 1)
except Exception:
 sys.exit(1)'
}

# 1. Healthy within the container's own healthcheck window.
deadline=$((SECONDS + 75))
until [ "$(docker inspect -f '{{.State.Health.Status}}' "$NDCONTAINER" 2>/dev/null)" = healthy ]; do
    [ "$(docker inspect -f '{{.State.Status}}' "$NDCONTAINER" 2>/dev/null)" != running ] && { echo "not running"; exit 1; }
    (( SECONDS >= deadline )) && { echo "did not become healthy"; exit 1; }
    sleep 3
done

# 2. HTTP API up and apps.plugin collecting.
curl -fsS --max-time 5 "${API}/api/v1/info" >/dev/null || { echo "API down"; exit 1; }
# capture then grep — piping the (large) charts dump into `grep -q` can SIGPIPE
# curl and, under pipefail, mark the pipeline failed despite a match.
charts="$(curl -fsS --max-time 5 "${API}/api/v1/charts")" || { echo "charts API down"; exit 1; }
grep -q '"apps\.' <<<"$charts" || { echo "apps.plugin not collecting"; exit 1; }

# 3. CORRECTNESS: the netdata daemon dropped privileges (must NOT be uid 0/root).
pid="$(docker inspect -f '{{.State.Pid}}' "$NDCONTAINER")"
uid="$(awk '/^Uid:/{print $2}' /proc/"$pid"/status)"
[ "$uid" != 0 ] || { echo "daemon is running as ROOT (privilege drop failed)"; exit 1; }

# 4. CORRECTNESS: per-process io/fd metrics are actually COLLECTED (non-zero).
# apps.plugin runs real-uid = the netdata user (setuid gives euid 0, not real uid
# 0), so it needs CAP_SYS_PTRACE to read ANY process's /proc/<pid>/io|fd; without
# it, it reports every per-process metric as 0 (not null). So require a per-process
# fds_open to be NON-ZERO — dropping SYS_PTRACE zeros it. (A prior non-null check
# passed on all-zeros and wrongly derived SYS_PTRACE removable, regressing prod.)
ppok=0
for _ in 1 2 3 4 5 6 7 8; do
    for ch in app.dockerd_fds_open app.netdata_fds_open app.systemd-journald_fds_open app.containerd_fds_open; do
        if chart_nonzero "$ch"; then ppok=1; PPCH="$ch"; break 2; fi
    done
    sleep 3
done
[ "$ppok" = 1 ] || { echo "per-process metrics all zero — apps.plugin can't read /proc/<pid>/fd (SYS_PTRACE gap)"; exit 1; }

echo "netdata correct: healthy, API up, daemon uid=$uid (non-root), per-process metrics collecting non-zero ($PPCH)"

Dimension: capabilities

Not yet derived — this dimension has not been drop-tested for this image. Absence means not tested, not that a minimum here is infeasible.

Dimension: filesystem

Not yet derived — this dimension has not been drop-tested for this image. Absence means not tested, not that a minimum here is infeasible.

Evidence & provenance

Raw profile YAML
# netdata cap_add_validation — EXPLORATORY (below the validated bar; see
# acceptance_contract_violations). Validates netdata's officially-recommended
# grant (cap_add: [SYS_PTRACE, SYS_ADMIN]) against observed cap_capable
# syscalls, under the same representative run as the validated capabilities
# dimension (pid:host, host network, host /proc,/sys,docker.sock ro-mounts).
#
# Why exploratory, probably permanently: the target itself generates the
# event flood. apps.plugin trips a SYS_PTRACE cap_capable check for every
# /proc/<pid> read of every host process, every second (~44k held events per
# 30s), so the perf-buffer drop rate stays >=1% on any host — measured 1.9%
# over 310s on a 16-core desktop with in-gadget container filtering active,
# ~4% on the quiet csd-derive VM. In-kernel filtering cannot help when every
# dropped event is attributable to the target (csd#407 discussion).
#
# READ WITH the validated capabilities dimension in
# catalog/docker.io/netdata/netdata.yaml, which is AUTHORITATIVE for the
# minimum: its drop-test proves SYS_ADMIN is droppable (the image ships no
# ebpf.plugin, so nothing gates on it) while SYS_PTRACE is required. This
# observation adds the complementary fact: netdata EXERCISES SYS_ADMIN
# opportunistically when granted (collectors probe SYS_ADMIN-gated
# operations and degrade gracefully without) — "exercised" here does NOT
# mean "required". The two observers answer different questions; where they
# diverge, drop-test wins for the minimum.
schema_version: "1.5"
image: docker.io/netdata/netdata
reference_url: https://tmatens.github.io/container-security-profiles/profiles/exploratory/docker.io/netdata/netdata.html
status: exploratory
acceptance_contract_violations:
  - confidence.level "low" below moderate (see confidence.factors)
  - trace_health.drop_rate 1.8663 >= 0.01
dimensions:
  cap_add_validation:
    recommended_cap_add: [SYS_ADMIN, SYS_PTRACE]
    validated_caps: [SYS_ADMIN, SYS_PTRACE]
    derivation:
      tool: container-sec-derive
      tool_version: "0.7.0"
      sidecar_schema_version: "1.6"
      observer: capadd
      validated_image: docker.io/netdata/netdata@sha256:bcc822ec685dd7ee488e9f99b957f479b66301be04f13f346df692ad7de94151
      validated_date: "2026-07-16"
      duration_seconds: 310
      confidence: low
      workload: profiles/workloads/netdata.sh
      workload_sha256: "ba6ae07c95eed30155baff45933b0ba9c171694a294539b08119360fc31180f6"
      validated_via: [bpf-observation]
      observation_backend:
        ig_version: v0.51.0
        gadgets: []