Skip to content

[Security] Sandbox container leaks: unbounded stdout logs, never-removed containers, missing pids_limit #338

Description

Audit findings: BUG-R2-S2-A6-H2 + BUG-R2-S2-A6-H3 | CWE-400 / CWE-401 | CVSS 7.7 / 6.3 | PoC: reproduced, EXP confirmed

Summary

SecureCodeExecutor._run_in_container (secure_code_executor.py:279-291) configures mem_limit/cpu_quota/network_mode but leaves three containment gaps:

  1. No log_config — the daemon's default json-file driver is unbounded (no max-size/max-file; repo ships no daemon.json). Sandbox stdout is written by dockerd to /var/lib/docker/containers/<id>/<id>-json.log on the daemon host, outside every container resource limit. POC-measured: a gate-compliant while True: print('A' * 10000) wrote 2.30 GB in one 10 s window (~230 MB/s) while container memory stayed at ~15.7 MB. EXP-measured full cascade: one request hit ENOSPC on a modeled host disk, dockerd container-start failed, stats engine down for all tenants, co-hosted services failing until manual docker rm.
  2. remove=False with zero removal sites — repo-wide grep finds no .remove()/.prune()/docker rm anywhere. Every execution (benign or malicious) leaks one stopped container plus its full log file, indefinitely.
  3. No pids_limit — process count is the one kernel resource left unbounded. Spawn primitives that pass all gates (see companion executor-gate-bypass issue): import posix + chr-built shell fork bomb, or multiprocessing.Pool(99999) — every fork allocates a host PID up to kernel.pid_max; host-wide fork-failure DoS during each 10 s window, sustained at ~5-9 windows/min within the per-key rate limit.

Envelope: live where the API shares a filesystem with the Docker daemon (documented bare-metal mode); shipped compose/k8s fail closed; socket-compose mode still leaks containers.

Suggested fix

containers.run(
    ...,
    log_config=LogConfig(type=LogConfig.types.JSON,
                         config={"max-size": "10m", "max-file": "1"}),
    pids_limit=128,
)
# and in finally:
container.remove(force=True)

(Result read-back uses the mounted result.json, never container logs, so removal after wait()/kill() is safe.) Defense in depth: daemon-wide json-file rotation.

Related: #322 (sandbox hardening — commented with these findings).


Source: OpenVuln external audit of QWED-AI/qwed-verification (snapshot v7.0.0, re-verified against current main v7.1.0). All code anchors below were spot-checked against current main before filing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High prioritybugSomething isn't workingexternal-auditFindings from external security auditsqwed securityqwed securitysecuritySecurity finding from adversarial auditverificationverification

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions