Skip to content

Docker sensor

An exposed, unauthenticated Docker Engine API: dockerd 27.3.1 (API 1.47) on Ubuntu 24.04, over plain HTTP with no TLS and no authentication. The real docker CLI (version, info, ps, images, run, exec), curl and nmap -sV all get believable answers.

It listens on container port 2375. Compose maps the host port DOCKER_HOST_PORT (default 2375) to it.

  • Read requests (/version, /info, /containers/json, /images/json, /networks, /volumes) get canned or generated JSON. Containers that the same address created earlier show up in later listings.
  • Creating and running containers is recorded in full. The container gets an ID and a state. Nothing is started, pulled or built. The output of trivial commands (id, whoami, echo) is produced by reading the command text, never by running it.
  • Pulls report bounded, realistic progress. No image is fetched.
  • Builds, image loads, commits and archive uploads are recorded and answered with a generic result. Remote URLs inside them are recorded as downloads.
  • Swarm answers 503 This node is not a swarm manager. swarm init and swarm join are recorded.
  • Registry login (POST /auth, or an X-Registry-Auth header) records the credentials and answers 401.

All events use the shared Event schema.

Event typeContent
connecttransport.
http_requestEvery request: method, path, query, headers, user_agent, body_preview, body_sha256, status, route, and for container creates the image, command, entrypoint, environment, binds, privileged, pid_mode, network_mode, cap_add and user.
commandContainer create or exec: input (entrypoint and command), kind, image, flags.
download_attemptA URL found in a command, an environment variable, fromSrc or a build context.
login_attemptRegistry credentials (username, password, server, method = registry_auth).
scanNon-HTTP bytes, incomplete requests, and rate-limited connections.
disconnectduration_s, reason, requests, commands, logins, downloads.

Rules live in ingest/rules/services.yaml.

TagSet when the sensor flags
container-escapeprivileged, host_mount (a bind from /, /etc, /root, /proc, /sys, /home and similar), or docker_sock_mount.
crypto-minerminer (an image, command or environment that names a known miner, such as xmrig or kinsing).
malware-dropAny download_attempt.

Other flags, such as pid_host, net_host, cap_add, image_build, swarm_join and recon, are shown on the session.

Compose sets DOCKER_MAX_CONNECTIONS to 200 and DOCKER_MAX_PER_IP to 8. The sensor’s own defaults are in its README.

ScopeLimit
New connections5 per second per source, burst 20.
RequestRequest line 8 KB, head 32 KB and 64 headers. Larger requests get 431 or 414.
BodyRead and hashed up to 8 MB. The first 256 KB are parsed. JSON is checked for size and depth (24) before parsing.
TimeoutsHead 20 s, body 60 s in total, keep-alive 15 s, 200 requests or 300 s per connection, write 10 s.
MemoryA 96 MB byte budget (DOCKER_MEMORY_BUDGET_MB). A guard refuses new connections above 70 % of the container limit.
Per-address state512 addresses, with 8 containers and 16 execs each. Least recently used entries go first.
Events20 per second per session, 200 per second for the sensor, plus a guaranteed bucket for commands, downloads and logins.

A watchdog thread exits the process if the event loop stalls for 10 seconds.

Same hardening as the other sensors: non-root (UID 10001), read-only root file system, tmpfs at /tmp, all capabilities dropped, no-new-privileges, 256 MB of memory with no swap, 0.5 CPU and 128 processes. Health check: python -m docker_sensor.healthcheck. The profile (a random identity for the fake daemon) is stored in the docker-sensor-data volume.