Skip to content

refactor(sandbox): container hardening is copy-pasted across three Docker clients, with a docstring as the only thing preventing drift #17267

Open
0 of 5 issues completed

Description

@mrveiss

Problem

Three modules each open their own Docker client and each re-declare the container hardening flags:

Module Client
autobot-backend/secure_sandbox_executor.py:132 docker.from_env()
autobot-backend/services/execution/docker_backend.py:96 docker.from_env(timeout=10)
autobot-backend/services/docker_task_workspace.py:196 docker.from_env()

The constraint set — network_mode: "none", read_only, no-new-privileges, cap_drop: ALL, memory/CPU limits — is duplicated rather than shared.

The codebase already knows this is a risk

services/docker_task_workspace.py's own docstring states it "copies the exact constraint set" from the sandbox executor "so the two paths can never diverge independently". That is drift being prevented by hand, in a comment. A shared constant would make divergence impossible instead of merely discouraged — and a security constraint that relies on a human noticing a copy is the kind that silently weakens on the third copy.

Scope note

This is consolidation of an existing, working constraint set — not a behaviour change. Per the project's canonical-source discipline, one definition, three consumers.

Acceptance criteria

  • The container security constraints are defined once and imported by all three call sites
  • A test asserts every container-creating path applies the shared constraints
  • Adding a new container-creating module without the shared constraints fails a guard
  • No behaviour change: the resulting flags are identical to today's on each path

Refs #17265.

Activity

  1. mrveiss commented on Oct 10, 2026

    @mrveiss
    OwnerAuthor

    Two of three delivered on branch issue-17267-container-hardening (d61381f411, queued). secure_sandbox_executor and docker_task_workspace now take their hardening from autobot_shared/container_hardening.hardening_kwargs. The kwargs reaching docker are identical before and after, pinned by a test against the old literals.

    Decision needed (owner), and it's a security finding rather than a copy: the third client, services/execution/docker_backend.py (containers.run at :272 and the snapshot-restore at :488), never had any hardening: no cap_drop, no no-new-privileges, no read_only, no network isolation, no pids limit, no swap cap. Code run through that backend runs in a default-privileged container. Adopting the shared builder makes it stricter and changes what that code can do (e.g. no network for pip, read-only rootfs):

    • (c) recommended first: adopt only the non-behavioural subset (cap_drop=['ALL'], no-new-privileges, a pids limit). No network or filesystem change.
    • (b): the full builder with an explicit network_mode and a writable tmpfs plan.
    • (a): leave it as it is.

    services/execution/container_pool.py:207 also calls containers.run with caller kwargs and still needs a look.

  2. added
    needs-decisionBlocked on an owner decision; options and a recommendation are on the issue
    on Oct 10, 2026
  3. mrveiss commented on Oct 10, 2026

    @mrveiss
    OwnerAuthor

    Owner decision: (c). services/execution/docker_backend.py (containers.run at :272 and the snapshot-restore at :488) adopts the non-behavioural subset through the shared hardening_kwargs: cap_drop=['ALL'], no-new-privileges and a pids limit. Network and filesystem stay unchanged. Full hardening (option b) remains open as a follow-up on this issue. container_pool.py:207 still needs the same review. Recorded by the merge coordinator on the owner's instruction.

  4. removed
    needs-decisionBlocked on an owner decision; options and a recommendation are on the issue
    on Oct 10, 2026
  5. mrveiss commented on Oct 10, 2026

    @mrveiss
    OwnerAuthor

    Option (c) applied, and the container_pool.py:207 review

    Branch issue-17267-container-hardening, local commit 9ef024a5a7 (on top of d61381f411). This is option (c) only. Option (b), full hardening, stays open.

    Per-site change. Each site gains exactly three keys from the shared builder: security_opt=["no-new-privileges"], cap_drop=["ALL"], pids_limit=512. Network, read_only, volumes, memory, cpus, user and environment are unchanged at every site. Per-site tests pin the full kwargs before and after.

    Site Before
    services/execution/docker_backend.py:272 (cold start) detach, mem_limit, cpus, environment, stdout, stderr, remove=False
    services/execution/docker_backend.py:488 (snapshot restore) detach, remove=False
    services/execution/container_pool.py:207 (warm pool) detach, remove=False, and no caller-supplied hardening

    Why container_pool needed it, with evidence. WarmContainerPool is constructed in exactly one place, by ContainerPoolRegistry (container_pool.py:283), whose default_container_kwargs defaults to {} (:268, :272). Its only caller, docker_backend.py:122, calls ContainerPoolRegistry(docker_client=..., pool_size=...) and passes no kwargs. So pooled containers ran with neither cap_drop nor no-new-privileges. The floor is merged last ({**caller_kwargs, **baseline_hardening_kwargs()}), so a caller cannot weaken it, while other caller keys survive.

    Shared builder. autobot_shared/container_hardening.py gains baseline_hardening_kwargs(); the existing hardening_kwargs() composes it and its output is unchanged. The pids limit moved into that module as CONTAINER_PIDS_LIMIT. It still reads the existing AUTOBOT_WORKSPACE_PIDS_LIMIT env var (default 512), which now governs the execution-backend containers too.

    Open points

    • No one has measured whether any execution-backend workload needs more than 512 processes. The tests check the kwargs, not runtime process counts.
    • The env var's name and its registry description still say "task workspace". The registry file is owned by queued branches, so updating the description is deferred.
    • Pooled containers still lack a memory limit, network isolation and read_only. Those belong to option (b).
  6. mrveiss commented on Oct 10, 2026

    @mrveiss
    OwnerAuthor

    Owner decision (2026-10-10): option (b), full hardening for the pooled and execution containers: a memory limit, network disabled by default with explicit per-workload opt-in, a read-only root filesystem with tmpfs for scratch. Opt-in flags are named and audited; defaults are hardened.

  7. mrveiss commented on Oct 10, 2026

    @mrveiss
    OwnerAuthor

    Status note: AC4 is overtaken by the owner's decision (b), which applies full hardening rather than the narrower (c) subset. AC3, the guard covering every container-creating site, is met only once the guard runs over the whole tree and matches create as well as run (in progress on the branch). Real-container verification is tracked in #18211.

  8. added a commit that references this issue on Oct 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions