This document lists known issues and limitations. Click the issue title for full details, workarounds, and discussion.
| Issue | OS | Shell/Terminal | Fix Planned? |
|---|---|---|---|
| Brokered Docker socket: chunked-body create requests skip inspection | All | - | Yes |
| Brokered Docker socket: DinD + airlock mode — Testcontainers cannot reach sibling containers | macOS, Windows | - | Investigating |
| Brokered Docker socket: broker binds 0.0.0.0 on non-Linux | macOS, Windows | - | By design (host-gateway routing) |
The --dind flag enables Testcontainers and sibling-container workflows by routing every Docker API call from the workload container through a host-side broker. The broker is part of the copilot_here binary itself, owns the rules, and forwards only requests that match the allowlist. This is meaningfully safer than mounting /var/run/docker.sock directly:
- The host process stays in control of every call.
- Dangerous endpoint families are denied by default:
swarm,services,tasks,nodes,secrets,configs,plugins,session,distribution,auth,events. POST /containers/createis body-inspected:HostConfig.Privileged=true, host network/PID/IPC namespaces, forbidden bind mounts (/,/etc,/var,/var/run/docker.sock, …), and dangerous Linux capabilities (SYS_ADMIN,SYS_MODULE, …) are all rejected at request time.- A strict default-deny image allowlist gates which images may be spawned at all. Empty
body_inspection.allowed_imagesmeans nothing can spawn until the user enumerates trusted patterns (e.g.mcr.microsoft.com/mssql/server:*). - In airlock mode,
HostConfig.NetworkModeis rewritten to the airlock compose network. Spawned siblings join the same internal-only network as the workload, which reaches them by Docker DNS instead of crossing the airlock boundary. - Rules live in
.copilot_here/docker-broker.json(local) or~/.config/copilot_here/docker-broker.json(global), withenforceandmonitormodes mirroring airlock.
- Chunked-body create requests skip inspection. Body inspection requires a
Content-Lengthheader onPOST /containers/create. If the client usesTransfer-Encoding: chunked(rare for known-size JSON, common for streaming uploads), the broker forwards the body without inspection because the dechunker isn't implemented yet. The endpoint allowlist still gates the call. Tracked for a follow-up. - Bodies larger than 2 MiB skip inspection. Same posture: forwarded without rewriting, endpoint allowlist still applies. Container create payloads are typically a few KB so this is a generous safety margin.
- Path canonicalization for bind mounts is string-level.
IsForbiddenHostPathmatches an entry against the deny list as exact-or-subpath, but doesn't follow symlinks or normalize..segments. A bind like/tmp/../etc/passwd:/mntwould slip through. The Docker daemon does its own canonicalization but the broker should mirror it for defense-in-depth. Tracked for a follow-up. - Broker binds
0.0.0.0on non-Linux hosts. On macOS and Windows the broker listens on TCP (the VM file-sharing layer can't proxy Unix sockets), and it binds0.0.0.0:<ephemeral>instead of127.0.0.1. The trigger was #111 on Windows + Podman: the workload reaches the broker viahost.docker.internal, whichcopilot_herewires up with--add-host host.docker.internal:host-gateway.host-gatewayresolves to a non-loopback bridge/proxy IP on every runtime that doesn't natively providehost.docker.internal(Linux Docker, Podman + gvproxy on Windows/macOS), so a127.0.0.1-only listener can't accept that traffic. The integration tests already boundAnyfor the Linux CI case (seetests/CopilotHere.IntegrationTests/BrokerSmokeTests.cs); this PR brings production in line. Docker Desktop's nativehost.docker.internalstill works against anAny-bound listener, so one default covers both runtimes. Every call still passes through the rule allowlist and body inspector, and the default image allowlist is empty (nothing spawns until you opt in). To force127.0.0.1, setCOPILOT_HERE_BROKER_BIND_LOOPBACK=1— only affects the non-Linux TCP path; fine on Docker Desktop, breaks Podman Machine. (Linux uses a UDS regardless, so the env var is a no-op there.) Runcopilot_here --show-docker-broker-statusfor a per-host snapshot. - Windows upstream is best-effort. The broker connects upstream via Docker Desktop's named pipe (
\\.\pipe\docker_engine) or Podman's machine pipe (\\.\pipe\podman-machine-default). If your environment routes the daemon differently, setDOCKER_HOSTexplicitly or run from a Linux/macOS host. - Podman: Works via runtime detection. The broker queries
podman info --format '{{.Host.RemoteSocket.Path}}'and falls back to the conventional rootless and rootful socket paths. If your Podman setup doesn't exposeHost.RemoteSocket.Path, setDOCKER_HOST=unix:///path/to/podman.sock. - OrbStack: Works without configuration. OrbStack exposes the standard
/var/run/docker.sockon macOS, so the broker connects to it the same way as Docker Desktop. - DinD + airlock (macOS / Windows): The Docker API path works (workload →
proxy:2375→ socat → host broker), and sibling containers are created on the airlock compose network. However, Testcontainers data-plane connections fail: the workload setsTESTCONTAINERS_HOST_OVERRIDE=host.docker.internal, buthost.docker.internalis unreachable via raw TCP from theinternal: trueairlock network. Testcontainers tries to connect to<host>:<random_mapped_port>and times out. Workaround: use--dindwithout--airlockuntil a fix lands. See #101 for potential solutions and discussion.
- Rootless Podman can't write bind-mounted host files — fixed automatically. Rootless Podman runs inside a user namespace that maps your host user to
rootinside the container. A bind-mounted file you own on the host therefore shows up asroot-owned inside, while the workload runs asappuser(your host UID, which maps to a subordinate UID). The result:appusercan't write the files you mounted in, so the CLI tool fails to make changes. See #119.copilot_heredetects rootless Podman viapodman info --format '{{.Host.Security.Rootless}}'and adds--userns=keep-id:uid=<PUID>,gid=<PGID>(plus--user 0:0so the entrypoint still starts as root for its user setup). keep-id maps your host UID/GID to the same values inside the container, so the mounts show up owned byappuserand stay writable. Docker, OrbStack, and Podman Machine already expose the real host UID, so they're left untouched; rootful Podman is skipped because keep-id is rootless-only.
Found a new issue? Please report it on our GitHub Issues page with:
- Operating system and version
- Shell version (e.g., PowerShell 5.1, Bash 5.2, Zsh 5.9)
- Steps to reproduce
- Expected vs actual behavior
- Any error messages or screenshots