If you use proximo to reach your local-dev apps at https://<name>.test on the host, the toolbox makes those same URLs work from inside the container — for any client (curl, git, Node, Python, Playwright/Chromium, …), with proximo's certificate trusted so no -k / ignoreHTTPSErrors is needed. This file covers why that needs toolbox plumbing, how DNS + CA trust are established, and the bridge-backed lifecycle commands.
.test is unreachable from a sibling containerproximo makes any labelled Docker container reachable at https://<name>.<tld> (default .test): it runs Traefik publishing :80/:443 on the host, installs a host resolver mapping *.<tld> → 127.0.0.1, and trusts a local CA in the host OS + NSS stores. That works for the host browser. It does not work from inside a toolbox container: 127.0.0.1 there is the container's own loopback, not the host where Traefik listens, so DNS resolves but the connection refuses. proximo also never injects its CA into arbitrary containers, so even a reachable endpoint fails TLS verification.
proximo)config.Config.Proximo is a *bool, resolved into the Proximo Availability Gate by proximo.Resolve(host, cfg): an explicit true/false wins; omitted (nil) auto-detects — on iff proximo's root CA exists on the host (proximo install wrote it under ~/.proximo). So a host with proximo installed gets .test reachability in every shell with zero per-repo opt-in, and a host without it pays nothing. proximo: false opts a project out; proximo: true forces it on even when the CA is absent (the mount then soft-skips). *bool (vs a plain bool) is what makes nil mean "auto" rather than "off" — the same tri-state shape as bridge, but with an auto rather than always-on default.
internal/proximo supplies reachability and trust. The toolbox CLI runs on the host alongside proximo; no route enumeration belongs in .toolbox.yaml, and the toolbox never joins proximo's private network.
| Concern | Mechanism | Seam |
|---|---|---|
| DNS | Proximo's effective-route inventory supplies only the local bare and qualified names it actually serves. Each is pinned to the Docker host gateway, which reaches the host where Traefik publishes :443. The inventory directory is queried with proximo config inventory-dir, mounted read-only, and projected into /etc/hosts by proximo-hosts before shell startup and throughout the session. An older proximo falls back to its declared proximo.hosts labels, which cannot include generated qualified names. |
path and mount: internal/proximo; projection: bin/proximo-hosts |
| Cert | proximo's root CA (path queried via proximo config ca-path — the stable contract from filippolmt/proximo#20; fallback ~/.proximo/tls/ca.pem, proximo's current state home, filippolmt/proximo#17) is bind-mounted RO at /etc/ssl/proximo-ca.pem. entrypoint.sh then establishes seamless trust for every client (see below). NODE_EXTRA_CA_CERTS (Node uses its own bundle) and TOOLBOX_PROXIMO_CA (path pointer for the certifi gap) are also exported. |
mount: the gate's CAMount injected in mountplan.Merge; env: the gate's Env appended in sessionplan.Plan; trust: entrypoint.sh proximo block |
entrypoint.sh gates CA trust purely on [ -f /etc/ssl/proximo-ca.pem ]. It is idempotent and every step is best-effort (|| true) so a trust failure never aborts boot. Route projection has a separate enablement marker so a forced-on integration can still project routes when that CA is absent: Gate.Env emits TOOLBOX_PROXIMO_ENABLED=1, while withholding every trust path.
| Client | Trusted via | Notes |
|---|---|---|
curl / git / wget / python (ssl, urllib) |
system bundle | sudo cp into /usr/local/share/ca-certificates/proximo.crt + sudo update-ca-certificates (passwordless sudo; refreshed only when the cert changes via a cmp guard) |
| Chromium / Firefox (incl. Playwright's bundled browsers) | NSS | certutil -A -t C,, -n proximo into $HOME/.pki/nssdb (libnss3-tools, base apt layer). ~/.pki is a HOME subdir, not a bind-mount → ephemeral, rebuilt from the mounted CA every shell |
| Node / Playwright (node API) | NODE_EXTRA_CA_CERTS |
additive, set by the gate's Env |
| python-requests | — | uses certifi, not the system store; set REQUESTS_CA_BUNDLE="$TOOLBOX_PROXIMO_CA" (this is the one non-seamless client) |
The generic CA-certificate folder (~/.toolbox/certs/) shares this same system bundle: when any cert is dropped there, its entrypoint block re-points NODE_EXTRA_CA_CERTS / REQUESTS_CA_BUNDLE at the full /etc/ssl/certs/ca-certificates.crt — a superset that still contains proximo's CA, so proximo trust is unaffected. Reach for that folder for any non-proximo CA (corporate proxy, internal runtime); proximo's CA is wired automatically and needs nothing dropped there.
proximo up|down|status|errors|skill|doctor|update|version works inside toolbox shell via the bridge: the image ships /usr/local/bin/proximo (internal/build/assets/bin/proximo, sibling of the editor shims) which POSTs {"command": "<sub>", "args": [...]} to the daemon's /proximo endpoint; the daemon execs the host proximo binary and returns {"exit": N, "output": "…"}, which the shim prints and propagates. Running the real binary in-container cannot work — and the failure is silent, not loud: proximo materializes its compose stack under ~/.proximo and bind-mounts those paths, which the host Docker daemon resolves host-side where they don't exist, while the colliding compose project name makes up recreate the working host stack instead of refusing. Hence no proximo binary in the image and no catalog row: absence is the only robust boundary (ADR-0004).
proximoAllowlist (internal/bridge/proximo.go) = up/down/status/errors/skill/doctor/update/version; everything after the subcommand is forwarded verbatim, so a flag added upstream needs no toolbox change. The shim knows nothing about proximo's flags — classification lives only in the daemon. Two rules close the host-write path. --out/-o (upstream's spelling, on the errors dom and errors transcript leaves) is rejected with a 400 — via the bridge it writes to the host filesystem, and redirecting the shim's stdout in the container is the right way to capture output. And errors dom is refused outright, flag or no flag: it always writes an HTML file, defaulting to os.TempDir()/proximo-dom-<id>.html, so no flag rule can see it. Through the bridge that file lands where the container cannot read it, which makes the subcommand useless here anyway; errors transcript writes to stdout without --out and stays bridged. Everything else passes, --image on up included: pulling an arbitrary reference is the same trust the container already holds over the mounted Docker socket. install/uninstall/trust need host root → refused by the shim with a run-on-host hint, never bridged; an unattended container→host-root path is one a prompt-injected agent inherits. config is refused too: its one mutating form (config tld) rewrites the host resolver, and its queries describe a host the container cannot change.skill runs with the agent home rewritten. proximo skill install writes files an in-container agent must read, so the daemon runs that one verb with HOME=$HOME/.toolbox and CODEX_HOME=$HOME/.toolbox/.codex at --scope global — exactly the sources mountplan.defaults binds to /home/toolbox/.claude and /home/toolbox/.codex, so one run installs for Claude and Codex together. --scope is appended only to the install/uninstall leaves, which are the only ones upstream registers it on — a bare proximo skill would otherwise fail with unknown flag. An explicit --scope the caller passed is left alone rather than silently overridden, and a caller-supplied home that is not an existing host directory fails the request instead of falling back to a default. Restart the agent session afterwards to pick the skill up. → Proximo Execution Modes[ -f /etc/ssl/proximo-ca.pem ] — the same predicate entrypoint.sh self-gates its trust block on, and the in-container shadow of the host-side gate. A host without proximo, a workspace with proximo: false, or a container created before proximo was installed (the CA bind is fixed at create — toolbox stop and reopen) each get one refusal naming all three causes, and get it even when the bridge is not installed. The check sits after the subcommand dispatch, so a host-only verb still gets its own run-on-host hint. TestBridgeContract_ShimMatchesGo pins that path against proximo.CATarget. → Proximo Availability Gateerrors transcript is unredacted. Upstream documents it as "may contain credentials", and this route carries it from the host's in-memory buffer to the stdout of an agent inside the container. Treat the output as secret-bearing. Note the gate is the shim's alone: the bridge token is readable inside the container by design, so a direct curl to /proximo bypasses it, and a workspace that opted out with proximo: false is not protected from a deliberately hostile container process. That is the bridge's standing trust model — /credential already forwards host git credentials — not a property this endpoint weakens.requestTimeout. First up pulls the stack images, so /proximo gets proximoTimeout (120s) and pushes the connection write deadline past the server's WriteTimeout: 10s via http.NewResponseController (per-response, other routes keep tight limits). cmd.WaitDelay = 2s stops CombinedOutput from hanging on pipes inherited by compose children after a deadline kill. Shim curl --max-time 135 sits above the server budget. A non-zero exit is data (propagated), not an error; only infra failures (binary missing, deadline) are 502./opt/homebrew/bin and ~/go/bin; resolveProximoBinary does exec.LookPath then probes /opt/homebrew/bin/proximo, /usr/local/bin/proximo, $HOME/go/bin/proximo. No binary → ErrProximoNotInstalled, surfaced verbatim by the shim; it names the host command to run, because install is host-only by construction — the daemon would have to exec a binary that isn't there. The child proximo process gets the same treatment: its PATH is augmented (appendPathDirs + proximoChildPathDirs — dir of the resolved binary, the well-known bin dirs, ~/.docker/bin, ~/.orbstack/bin; append-only, existing entries win) so proximo's own docker lookup also survives the minimal service PATH — otherwise proximo status bridged from inside reports "docker is not installed" on a host that runs Docker fine. Lives in daemon code → active on daemon restart, no plist/unit regeneration.toolbox bridge install"; new daemon + old image → no shim, nothing to break. Daemon restart (to pick up the endpoint) = rerun toolbox bridge install.proximo-hosts picks the routes up automatically without recreating the container.proximo-hosts, automatic)The in-container proximo-hosts command (internal/build/assets/bin/proximo-hosts) owns the Proximo Route Projection in a marker-delimited /etc/hosts block. It reads the mounted routes.json, selects only bare and qualified, resolves the host-gateway IPv4, and pins those names there. claimed, collision and peer names are deliberately ignored: they are not local names the route currently answers on. The inventory is replaced atomically by proximo, so the command polls the document rather than racing proximo's watcher on the same Docker event. Rewriting is idempotent, and an empty inventory clears the managed names.
It runs automatically. When proximo is enabled (signalled by the CA mount for compatibility or TOOLBOX_PROXIMO_ENABLED=1), entrypoint.sh makes one bounded, best-effort foreground sync before the interactive shell can start, then launches proximo-hosts --watch in the background. A failed initial discovery never blocks shell access; the watcher retries immediately. If the inventory directory is not mounted because proximo predates that contract, both modes retain the old label discovery and the watcher follows Docker events; bare hosts keep working, while upgrading proximo and recreating the toolbox container adds qualified-host support. A mounted inventory is authoritative even while routes.json is temporarily absent or unreadable: the command preserves the last valid managed block rather than falling back to declarations. Running proximo-hosts by hand stays available to force a sync. Logs go to ~/.toolbox-state/proximo-hosts.log.
routes.json by rename, so binding the file would freeze Toolbox on its old inode. Gate.InventoryMount binds the directory only when it exists; absence is silent and selects the legacy label path. A container created before the directory existed needs one recreate to gain the mount. Inventory-backed projection needs no Docker socket. Legacy Proximo without the default docker-sock mount is explicitly unsupported: Toolbox warns but still opens the shell. Upgrade Proximo and run toolbox stop to recreate the container with the inventory mount, or re-enable docker-sock.CAMount is added in Merge (not defaults()), so the canonical default-mount set and the smoke-test init.d/completion bijections are untouched (the proximo trust step lives in entrypoint.sh, not an init.d script, so it ties to no catalog tool). A missing CA file (proximo not installed) soft-skips the mount with a resolveAll warning; the gate's Env withholds the trust variables on that same absence — one resolved CAExists, read by both — so Node never points at an absent NODE_EXTRA_CA_CERTS. Only the projection enablement marker remains.certutil (libnss3-tools, base apt layer) and the lifecycle shim ships at /usr/local/bin/proximo; smoke-test.sh asserts both. Everything else is host-side and image-hash-neutral.proximo config flag steers mounts and env only; the shim is unconditional in the image (it degrades with an install hint when the bridge state dir is absent).