Skip to content

Roadmap: post-bootstrap improvements (audit follow-ups, categorized) #2

Description

@AviBackToBlack

Meta-issue preserving the full audit findings and improvement ideas from the open-source bootstrap (PR #1). Categorized by priority; each item can be split into its own issue when picked up.

Updated 2026-08-17 after the Devin Review engagement on PR #1 (6 rounds, 20 findings: 11 fixed in the PR, 9 dispositioned as intentional/unreachable — see the PR discussion).

Reorganized 2026-08-17: work items now carry RM-n identifiers used by the delivery pipeline, and closely related items have been merged into single grouped items with staged sub-tasks. Nothing was dropped in the merge — the detail from each original bullet is preserved under its group. Sub-tasks are lettered (RM-5a, RM-5b, …) and are individually shippable; a group is done when all of its sub-tasks are.

Must-fix before first public release (v1.0.0)

All code items originally in this category were fixed in PR #1 (module path, doctor where.exe parsing, duplicate registry sections, reserved shim names, lock repo-mismatch fail-closed, version injection). Status of the rest:

  • Confirm license choice — Apache-2.0 confirmed by the maintainer (2026-08-17).
  • Required checks in the ruleset — done: main-protection requires 6 contexts (CI ×3, CodeQL analyze ×2, Dependency review). Devin Review is deliberately NOT a required check: there is no free tier for public GitHub projects, it consumes the maintainer's personal quota, and it may become unavailable — so gating merges on it would be gating on a resource that can vanish. It is fired manually when quota allows, and its findings have been substantive (genuine bugs on PR Open-source bootstrap: audit fixes, OSS scaffolding, docs, CI/security workflows #1 and PR Return an error from mountSpec instead of aborting, and validate dst for commas #9). Revisit if a sustainable free/OSS arrangement appears.
  • Release dry-run: push a v1.0.0-rc tag on a throwaway branch after merge to verify the release workflow (draft release, checksums, attestation) end to end before the real v1.0.0. The ldflags version-injection mechanism itself is now asserted in CI, but the attestation step has never executed.

Should-fix soon

  • RM-1 — mountSpec aborts the process on comma-containing paths — done in PR Return an error from mountSpec instead of aborting, and validate dst for commas #9. Chose a returned error over a -v fallback: fail-closed, no second Docker argv shape to maintain. mountSpec now returns (string, error) and all seven call sites propagate it. The change also closed a hole found while scoping: the old check inspected only src, but dst can carry a comma via workspaceRootFor (/workspace/<project-basename>, e.g. a directory named My, Project) and via hand-edited project_volumes/shared_volumes, which parseVolumeBinding does not comma-validate — and at the volume call sites src is a generated volume name that never contains a comma, so a malformed --mount string was reaching Docker and being misparsed. Volume creation now also happens after mount validation, so an invalid binding no longer leaves empty cb.managed volumes behind (that last part from Devin Review). Covered by a portable mountspec_test.go. Correction (RM-6c, PR Document that comma-named project roots cannot work under --mount syntax (RM-6c) #18): the workspaceRootFor-via-volume-call-site reachability claim above does not actually occur in practice — the root bind mount (main.go:1115) uses the same root as both src and the input to workspaceRootFor, and its src check fires first, so a comma-named project already fails closed there before the stateful volume loop is ever reached. The dst check's real, still-live value is guarding hand-typed project_volumes/shared_volumes TOML, not the workspaceRootFor case. The fix itself remains correct; only the motivating example was wrong. See docs/windows-paths.md row P15.

  • RM-2 / RM-3 — Registry write durability. Two independent gaps in the same code path (atomicWriteFile and its callers). Related enough to keep adjacent and to sequence deliberately — they touch the same functions, so doing them concurrently would conflict — but they are separate mechanisms with different risk profiles and should ship as separate PRs.

    • RM-2 — Concurrent cb registry mutations are uncoordinated. Two simultaneous cb expose/cb update runs can interleave read-modify-write (atomic file replace protects integrity, not lost updates). A simple lock file next to the registry would close it. Concurrency-sensitive; the harder of the two.
    • RM-3 — atomicWriteFile .bak window: crash between the two renames leaves the registry only at .bak. Startup could auto-recover (if registry missing && .bak present → restore). Self-contained; the cheaper of the two.
  • RM-4 — Test coverage for mapToolArgs/pathMapper on Windows CI — done in PR Add unit tests for mapToolArgs and pathMapper #8 (pathmap_test.go): 7 cross-platform tests plus 14 Windows-gated ones covering in-root mapping, external mount allocation and dedup, path_next/path_equals/path_last semantics, the dangling-option error, and nearest-existing-ancestor mounting. Confirmed genuinely executing on the windows-latest runner, not just skipping. Broader successor: RM-6b.

  • RM-5 — Environment diagnostics and support tooling. (Merged from six previously separate items: the cb doctor container-mode check, the registry/lock permission check, cb doctor filesystem/path diagnostics, cb self-test --json, cb self-test --release, and cb bugreport + its environment fingerprinting. They are one coherent capability — turning obscure Docker/path/permission failures into actionable, reportable diagnostics — and were duplicating each other: the path-diagnostics item independently listed the container-mode check, and the fingerprinting item was a sub-part of cb bugreport.) Ordered cheapest-first; each sub-task is shippable alone.

    • RM-5a — cb doctor verifies Docker Desktop is in Linux-container mode (docker info --format {{.OSType}}). Smallest useful increment and a prerequisite signal for the rest.

    • RM-5b — cb doctor registry/lock/shim permission check: warn if the shim directory or registry is writable by other users (trust boundary).

    • RM-5c — cb doctor filesystem/path diagnostics. Detect or warn about path environments known to be unusual or insufficiently tested:

      • the ContainerBin directory living on a network share;
      • a project rooted on UNC/network storage;
      • reparse-point-heavy project roots;
      • Docker Desktop path-sharing/access failures.

      The goal is not to reject everything unusual, but to turn obscure Docker/path failures into actionable diagnostics. Depends on the classification decisions made in RM-6a — a warning should not contradict what the mapper actually supports.

    • RM-5d — cb self-test --json / machine-readable diagnostics for CI-style consumption on real Windows hosts.

    • RM-5e — cb self-test --release qualification mode for real Windows hosts. An optional broader release-validation mode recording Windows version; PowerShell version; Docker Desktop / engine version; Docker OSType; filesystem location characteristics; and the results of the existing persistence/path/argv tests. Produces a concise machine-readable summary suitable for attaching to release notes or CI artifacts. Keep normal cb self-test lightweight and offline. Builds on RM-5d's output format.

    • RM-5f — cb bugreport: assemble sanitized doctor + version + inspect output into one paste-ready block, with secret redaction. Include sanitized compatibility/environment fingerprinting: Windows build; shell/version; Docker version and OSType; ContainerBin version; registry schema version; lock status; and whether the current path is local, UNC, mapped, or reparse-backed. Never include full environment dumps or secrets by default.

  • RM-6 — Windows path semantics: classification, hardening, and regression corpus. (Merged from the reparse-point/unusual-path-forms hardening item and the path-normalization test corpus item — the corpus is the test vehicle for the hardening, and specifying them separately would mean deciding the semantics twice.) The mapper is intentionally conservative, but Windows has additional filesystem semantics deserving explicit correctness and security coverage. This is the highest-risk area of the project: the failure mode is not a crash but a silently wrong mount, so it warrants unusually dense coverage and careful splitting into small tasks.

    • RM-6a — Decide and document the classification for each of: junctions and other reparse points inside or below the detected project root; path case-folding / case-insensitive equivalence; UNC paths (\\server\share\...); long-path syntax (\\?\C:\..., \\?\UNC\server\share\...); subst drives and mapped network drives; paths whose target changes between classification and docker run (TOCTOU); and project paths that escape the expected tree through a junction/reparse point. Each must land as supported, rejected, or documented as unsupported — no implicit third state. Preserve the narrow-mount / fail-closed philosophy rather than broadening mounts to make edge cases "just work". Two further inputs carried over from RM-6d: (a) arguments whose final element is ... are now declined outright, which should appear as a row in the classification table; and (b) separator normalization — a declined argument such as .\cmd\... reaches the Linux container with backslashes and the tool reports no match. Normalizing it to ./cmd/... was deliberately not done in RM-6d, because rewriting an argument just classified as not-a-path reintroduces guessing; decide it here, once, for every argument shape rather than as a special case (raised by Devin Review on PR Never map an argument whose final path element is ... #15).
    • RM-6b — Table-driven path-normalization test corpus, independent of Docker execution: drive-letter absolute paths; forward- and backslash forms; . / ..; spaces; commas; non-ASCII characters; mixed case; trailing dots/spaces where Windows permits or normalizes them; UNC and long-path forms; reparse/junction scenarios where practical. Encodes the RM-6a decisions as executable assertions. Keep the corpus close to internal/pathmap if/when RM-9 lands.
    • RM-6c — Investigated: comma-named project directories cannot be made to work by sanitizing workspaceRootFor. The original framing assumed the fix was extending workspaceRootFor's existing /_ collapse to commas. It does not work: mountSpec's src check runs before its dst check, and every tool invocation's root bind mount (main.go:1115) passes the same root variable as both src and the input to workspaceRootFor — so workspaceRootFor's output can only contain a comma when root itself already does, and the src check fails closed first, unconditionally, for every provider. No sanitization of the container-side basename changes which branch fires. Docker's --mount syntax has no escape for a comma in a field value, and src must be the real host path, so there is nothing to sanitize on that side without bind-mounting the wrong directory. Done as documentation, PR Document that comma-named project roots cannot work under --mount syntax (RM-6c) #18: classified as Rejected, docs/windows-paths.md row P15, with a corrected/added mountspec_test.go test (TestRootBindMountCommaFailsClosedOnSrc) pinning the real call site — the prior test asserting the dst branch used an artificially clean src that cannot occur together with a comma-laden workspaceRootFor output at any real call site. Genuinely making this work would need a different mechanism (e.g. bind-mounting a Windows 8.3 short-path alias of the real directory where one exists) — a materially larger, riskier change than this item, listed separately below.
    • RM-6d — Never map an argument whose final path element is .... (Found by the Windows CI runner while adding the Go shims in RM-10, and not Go-specific.) isExplicitWindowsRelPath("./...") is true, so the mapper canonicalizes it — and filepath.Abs on Windows goes through GetFullPathNameW, which strips trailing dots from a path component. C:\proj\... therefore collapses to C:\proj, pathWithin returns ".", and the mapper emits the bare workspace root. The user-visible result is that go test ./... runs only the root package and exits 0 — it under-tests and reports success, the "silently wrong" failure class this project treats as worst. ./cmd/... degrades to that one directory. Any tool given a ./…/... argument is affected; Go is simply the first where the shape is idiomatic. Fix: treat such an argument as not-a-path, which is already correct because the container's working directory is the mapped project directory. There are no false negatives — the mapper only runs on Windows, where a directory named ... cannot exist. Blocks RM-10. Done in PR Never map an argument whose final path element is ... #15. Guarded by hasPackagePatternSuffix, placed as the first statement in resolveWindowsPathArgMode so it precedes even force — a declared path_next/path_equals/path_last value ending in ... is no longer rewritten, and the registry docs record that exception.
  • RM-7 — Shell/process compatibility: contract and matrix. (Merged from the Windows compatibility matrix item and the shell/process compatibility contract item — the matrix is what you validate the contract against.) PowerShell/cmd behavior is effectively part of ContainerBin's ABI, because argv normalization and executable dispatch depend on Windows shell/process semantics.

    • RM-7a — Document the contract: which semantics ContainerBin tries to preserve across Windows process launchers — argv delivery; stdin/stdout/stderr; exit codes; TTY detection; current working directory; PowerShell-specific native argument behavior; cmd.exe behavior; and the differences ContainerBin deliberately does not emulate. Makes future regressions identifiable and prevents "fixes" that optimize for one shell while breaking another.
    • RM-7b — Define and maintain the validation matrix: Windows 11; Windows PowerShell 5.1; PowerShell 7.x; cmd.exe; representative Docker Desktop versions; Linux-containers mode. GitHub-hosted CI should continue covering pure/unit logic and Windows compilation, but should not pretend to provide full Windows + Docker Desktop E2E coverage. Prefer a documented manual release matrix first; a self-hosted Windows runner only if the maintenance cost is justified. Natural consumer of RM-5e.
  • RM-8 — Structured errors/exit codes: distinguish "tool failed" from "cb infrastructure failed" (e.g. reserve exit code 125+ like Docker does) so scripts can tell them apart. Note that RM-1 moved mountSpec off fatalf onto returned errors, which is a prerequisite shape for this. Done in PR RM-8: reserve exit codes 120 (cb failure) and 130 (interrupted) #13. Reserved 120 = cb infrastructure failure (via fatalf's single choke point) and 130 = interrupted (128 + SIGINT); tool exit codes still pass through untouched, and an unknown subcommand stays 2. 125 was deliberately rejected despite this item's original text: Docker already uses 125/126/127 for daemon-error/not-executable/not-found, so reusing it would recreate the very ambiguity the task exists to remove.

  • Periodically bump the govulncheck pin in ci.yml (currently @v1.7.0). Dependabot cannot track go install versions inside workflows, so this is a manual chore; results stay fresh regardless (the vulnerability DB is fetched at scan time), but tool-side fixes are not. (From Devin Review round 3.) Recurring chore, not an RM task. Checked 2026-08-18: v1.7.0 is still the latest release (confirmed against both the golang/vuln GitHub tags and the Go module proxy version list) — nothing to bump yet.

Useful enhancements

  • RM-9 — Decompose main.go (~2,800 lines) into packages: internal/registry, internal/lockfile, internal/pathmap, internal/dockerrun, internal/state, internal/backup, internal/diag, thin main. Mechanical, behavior-preserving, best done as one dedicated PR with no functional changes mixed in. Also the right moment to remove the current = &t aliasing in the registry parser flagged by review. Several other items (RM-6b, test-file consolidation) become easier afterwards, so ordering matters. Done in PR refactor: decompose main.go into internal/ packages (RM-9) #26 (main.go: 3,876 → 249 lines). Final layout differs from the original sketch in ways that avoid real import cycles the sketch didn't have: internal/backup was folded into internal/cli (backup/restore are commands, not a separable data layer), and three packages were added that the original list missed — internal/toml (the TOML lexer shared by both registry and lockfile), internal/atomicio (crash-safe write + .bak recovery, also shared), and internal/mutationlock (split from the signal-handling wrapper, which stays in main to keep exit-code policy in one place). The current = &t aliasing fix landed as its own isolated commit, exactly as scoped. Run through a deliberately heavier-than-usual five-stage review (design-critique-before-implementation, catching import cycles and a version/-ldflags bug before any code moved; implementation with a checkpoint after every step; full-repo-access review; a blind adversarial review; two rounds of Devin Review) given the regression risk of touching every file in the repo at once — full record in .handoff/RM-9/.
  • RM-10 — Go toolchain shims (go, gofmt). Adds Go to the default registry so go build, go test, go run and gofmt work on a machine with no Go installed. No new provider is needed: unlike Python — whose virtualenv is per-project state living inside the project tree, which is why that provider is hardcoded in runTool — every piece of Go's state lives outside the project (module cache GOMODCACHE=/go/pkg/mod, build cache GOCACHE=/root/.cache/go-build, installed binaries GOBIN=/go/bin), all shared across projects. That is exactly what the declarative stateful provider already expresses, so Go is registry configuration rather than code. node needs a project volume only because node_modules sits inside the project directory; Go needs none. Two user-facing details the task must settle: go build in a Linux container produces a Linux binary (inherent, not a bug — document it, and GOOS=windows is the escape hatch, which is why GOOS is allowlisted), and path-valued Go variables (GOPATH, GOROOT, GOBIN, GOCACHE, GOMODCACHE, GOTMPDIR, GOENV) must not be passed through, since on Windows they hold Windows paths that would silently point Go away from the mounted cache volumes. Done in PR Add Go toolchain shims (go, gofmt) to the default registry #14. Registry configuration only, no runTool changes, as predicted. Two things review changed: neither profile declares any path semantics at allpath_next on go corrupted pass-through argv (go run . -o json handed the user's program a container path) and path_last on gofmt turned the -r rewrite rule into a path. Forced path semantics are positionally blind, so any future profile for a tool with pass-through arguments must weigh the same hazard.
  • RM-11 — Versioned / per-tool Node runtimes. Support selecting a Node runtime per tool/profile instead of forcing all npm CLIs through the default Node image. Real-world trigger: @whdrnr2583/token-meter exits 1 under the current node:24-slim profile because its native better-sqlite3 11.x dependency does not provide a compatible Node 24 prebuild, while the same package works under node:22-slim. Keep Node-major state groups ABI-safe, preserve image-lock semantics, and define how cb expose npm retains the runtime/state group it originated from. Prefer a small set of supported/LTS runtimes rather than adding every Node release. Done in PR feat: versioned Node runtimes — node22/npm22/npx22, generalized cb expose (RM-11) #30, closes Support versioned / per-tool Node runtimes #28. Added node22/npm22/npx22 as a second, fully-isolated Node runtime (node:22-slim, node22 state group) — no path-mapper or lockfile changes needed, since volume IDs and image locking are already generic over state_group/configured image. Generalized cb expose to accept the name of any npm-shaped stateful profile (not just literally npm) as its positional source argument, so cb expose npm22 <binary> retains the Node 22 runtime identity. Two review rounds (SWE-1.7 Max implement, GLM-5.2 blind verify, Devin Review + Copilot on the PR) found and fixed: a missing lockfile-resolution step in cb expose's discovery path, a missing stateful-provider guard, the npm-global volume matched by logical name instead of mount destination, and a missing test for the core inherited-fields behavior.
  • RM-12 — Explicit profile-level host mounts. Allow trusted registry profiles to declare narrow host bind mounts with an explicit Windows source, Linux container target, and ro/rw mode. Real-world trigger: Token Meter can run successfully inside node:22-slim, but must read %USERPROFILE%\.claude and %USERPROFILE%\.codex to ingest real Claude/Codex session history; its own persistent state lives under /root/.tokenpulse. Preserve fail-closed validation, narrow mounts, the existing Windows path/reparse-point discipline, and clear trust-boundary diagnostics. Done in PR feat: explicit profile-level host mounts, host_mounts (RM-12) #31, closes Add explicit profile-level host mounts (host_mounts) #29. New provider-agnostic host_mounts registry field (SOURCE:/CONTAINER_PATH:MODE, mode required with no default, only %USERPROFILE% expanded and nothing else, two-stage validation — static structural checks at registry load, environment-dependent resolution at run time through the existing pathmap.CanonicalPath). cwd_mode (the related but orthogonal launcher-CWD problem) was deliberately split out — see the dedicated follow-up below. This PR received unusually deep review (5 Devin Review rounds, 4 GLM adversarial passes) given its trust-boundary nature, and it earned it: found and fixed an orchestrator scoping error (missing project_volumes collision coverage), a severe bug where a path.Clean normalization fix for one finding silently reopened the reserved-namespace check via a ..-traversal target that collapsed to / (caught only by GLM's full-diff re-verify, never by either GitHub-native reviewer), and a second real bug where /venv//root/.cache/pip were reserved by exact match only instead of by prefix like /workspace//cb. Full retrospective in the PR discussion and .handoff/state.md.
  • RM-13 cwd_mode — explicit isolated/background-launcher CWD semantics. Split out of RM-12 (issue Add explicit profile-level host mounts (host_mounts) #29) because it is implementation-orthogonal: host_mounts is a new registry-declared mount, cwd_mode changes dockerrun.RunTool's project-root-finding logic. Confirmed real during RM-12 scoping: a GUI/MCP host invoking a shim from an arbitrary directory (e.g. C:\Windows\System32) causes RunTool to treat that directory as the project — for a stateful tool, pathmap.WorkspaceRootFor always returns /workspace/<basename> regardless of whether a project marker was found, so npx launched from System32 really does produce /workspace/system32 and a project volume keyed by a hash of that path. Needs an explicit cwd_mode = "isolated" opt-in for background-launcher tools (default "project" unchanged) rather than silently guessing another project root or auto-mounting user data. (From issue Add explicit profile-level host mounts (host_mounts) #29's "Related finding" section.)
  • RM-14 — AI / automation invocation guidance. Research and document the correct way for AI coding agents and automation launchers to invoke ContainerBin-backed tools when shell CWD is arbitrary or not preserved between tool calls. Real-world failure: an agent verifies D:\Work\project\script.js with Test-Path, then runs node script.js from a different CWD and incorrectly concludes the file/container mapping is broken. Determine the supported instruction mechanisms for major agents (AGENTS.md, CLAUDE.md, Copilot/Codex/Devin equivalents), define one canonical execution contract, and decide whether cb trace/diagnostics should expose CWD/project/path-resolution context more explicitly. Preserve the fail-closed rule: prefer absolute Windows paths or set CWD explicitly; never guess/search for the intended file.
  • - RM-15 — Runtime defaults / version aliases. Separate stable versioned runtime profiles (node22, node24, future node26; likewise Python/Go/etc.) from unversioned convenience commands (node, npm, python, ...). Add a generic alias/default mechanism so users can inspect and atomically switch the default version for a runtime family (e.g. cb default, cb default set node 22) without duplicating profile configuration. Related tools in one family (node/npm/npx, python/pip) must switch together; versioned shims remain stable. Alias resolution should be visible in cb inspect/cb trace and reuse the target profile's state/image lock rather than creating copied configuration.
  • Reserved-namespace protection doesn't cover project_volumes/shared_volumes destinationshost_mounts' reserved-namespace check (/workspace, /cb, /venv, /root/.cache/pip, prefix-based) only guards host_mounts targets. A profile could still declare shared_volumes = ["x:/venv"] and shadow the python provider's venv at runtime — the same class of latent breakage the host_mounts fix addresses, just via a different field. Pre-existing (volumes predate host_mounts), not a regression; worth its own pass extending the same pathContainsOrEquals reservation check to ParseVolumeBinding's destination validation. (Raised by GLM-5.2's fourth verify pass on PR feat: explicit profile-level host mounts, host_mounts (RM-12) #31.)
  • Lock support for locally built imagescb lock fails closed on images without matching RepoDigests. Correct for registry images, but a locally built docker build -t image can never be locked, and once a lockfile exists such a tool becomes unusable. Evaluate locking by image ID, or a per-tool unlocked = true opt-out. (From Devin Review round 3; not a PR Open-source bootstrap: audit fixes, OSS scaffolding, docs, CI/security workflows #1 regression — baseline already errored on digest-less images.)
  • Reserved-name migration path — reserved tool names are rejected at registry parse time (fail-closed, fine pre-1.0 with no installed base). If a future release ever renders previously legal names reserved, ship the softer design: enforce at mutation/shim-install time, doctor-warn on load, keep removal commands working. (Documented per Devin Review rounds 2–3 discussion.)
  • Run the Windows CI test step with -v. Test and build (Windows) runs go test ./..., so a skipped test is indistinguishable from a passing one in the log. That matters more here than in most repos: mapToolArgs returns early on non-Windows, so 15+ path-mapper tests exist solely to run on that job, and confirming they actually execute has meant manually digging through job logs since PR Add unit tests for mapToolArgs and pathMapper #8. Two rows of the RM-6a classification table (P1/P2) additionally skip when the runner cannot create a directory symlink, which is invisible today. (Raised by Devin Review on PR Document and test Windows path-form classification (RM-6a) #16; deliberately not folded into a docs PR.)
  • Version-shaped cb-v dispatch — dispatch (and therefore the name reservation) claims the entire cb-v* prefix for versioned binary names; tightening both in tandem to cb-v<digit> would free names like cb-vault. Low priority: collision space is negligible. (From Devin Review round 6.)
  • cb add TOOL --image IMAGE: guided profile creation instead of hand-editing TOML.
  • Speculative: bind-mount a Windows 8.3 short-path alias for a comma-named (or otherwise --mount-unrepresentable) project root, instead of the real long path, so such projects could actually work rather than failing closed (see RM-6c / docs/windows-paths.md row P15). Unscoped and not committed to: 8.3 short-name generation is disabled by default on many modern NTFS volumes (a performance optimization), so this would need a capability check and a documented fallback to today's fail-closed behavior, plus verifying EvalSymlinks/case-folding still behave sanely against a short-name path. Materially bigger and riskier than a documentation task.
  • More providers/integrations: pipx-style Python global tools, Rust/cargo, Ruby/gem, .NET SDK, uv as an alternative Python provider. (Go toolchain carved out as RM-10 above.)
  • cb expose for go install-ed binariesgo install drops executables into the shared gobin volume (/go/bin), exactly as npm globals land in npm-global. Once RM-10 lands, extending cb expose beyond npm would turn those into shims too. Related to the cb expose beyond npm item below.
  • cb expose beyond npm: pip/pipx entry points, cargo bin, generic "expose any file from a shared volume".
  • cb self-test node22 coverageinternal/diag's selfTestSteps only exercises the node/npm/npx (node24) family; RM-11's node22/npm22/npx22 profiles get no offline end-to-end verification. Not a mechanical addition: selfTestSteps currently treats a tool missing from the registry as a hard fail, not a skip — a registry that hasn't yet been upgraded via cb setup/cb install to pick up node22 would start failing self-test the moment steps for it are added. Needs a deliberate policy decision (e.g. distinguish "tool never configured" from "tool added by a newer default set the user hasn't installed yet") before adding node22-image-local/node22-modules-* steps. (Raised by Devin Review on PR feat: versioned Node runtimes — node22/npm22/npx22, generalized cb expose (RM-11) #30; deliberately deferred rather than folded in.)
  • Startup latency: measure docker run overhead; evaluate --pull=never (should already hold with digests), pre-created networks, and whether a long-lived per-state-group container with docker exec is worth the state-model complexity (probably opt-in only).
  • Windows ARM64 release target — cross-compiles trivially, but do not claim support until actually validated on ARM64 hardware with Docker Desktop. Natural consumer of RM-5e.
  • Authenticode signing of released cb.exe (SmartScreen reputation for a tool whose whole job is being on PATH). Needs a cert; sigstore/attestation already covers provenance.
  • Self-update: cb self-update verifying attestation before replacing the binary; mind the running-exe-replacement dance on Windows.
  • Proxy/air-gap story: document private registry mirrors in profiles; note that cb lock currently fails closed for mirror-hosted references whose RepoDigests name the canonical repository (deliberate — see PR Open-source bootstrap: audit fixes, OSS scaffolding, docs, CI/security workflows #1 discussion); offline volume export/import (cb backup --state including volume tarballs for disaster recovery).
  • Observability: CB_DEBUG=1 structured tracing of the exact docker run argv (today cb trace covers pre-flight only); optional log file for support bundles. Feeds RM-5f.

Speculative / future ideas

  • Linux/macOS hosts: the dispatch/registry/lock core is portable; shims become symlinks, path mapping becomes near-pass-through. Big validation surface; only with real demand.

  • Policy controls for enterprise: allowed-registry allowlist, mandatory lockfile mode (refuse UNLOCKED), signed registry files.

  • Image trust: verify Docker Content Trust / cosign signatures at cb lock time and record the verification in the lockfile.

  • Plugin/provider architecture: external provider executables discovered via naming convention, so new ecosystems don't require forking cb.

  • WSL2: decide the interoperability model before implementing anything. (Merged from the "WSL2 interoperability model" and "WSL2 integration mode" items — the latter was one candidate direction within the former, and listing both invited implementing before deciding.) The supported direction must be explicit first; the candidates are:

    1. Windows process → Windows cb.exe → Docker Desktop (today's model);
    2. WSL process → Linux-side shim → Docker Desktop integration;
    3. Windows cb.exe targeting a Docker engine exposed from a WSL distro.

    These have very different path-mapping rules (C:\x vs /mnt/c/x), filesystem semantics, TTY behavior, and trust boundaries. Do not implement a generic "WSL mode" until the direction and path contract are explicit.

  • Per-project registry overlays (.container-bin.toml in project root, merged fail-closed) — powerful but expands the trust boundary to repos you clone; needs careful design (explicit trust prompt à la direnv).

  • Benchmark suite tracking shim overhead across Docker Desktop versions.

  • SBOM for releases — low value while stdlib-only; revisit if dependencies appear.

  • Snyk — currently redundant with CodeQL + govulncheck + Dependabot for a zero-dependency Go binary; revisit if the dependency graph grows. Requires external account/token either way.

Design principles to preserve (from PR #1 review)

Conservative path mapping (never guess), allowlist-only env passing, narrow mounts, fail-closed lock/registry validation, dry-run-by-default destructive operations, label-proven ownership before deletion, zero third-party Go dependencies unless clearly justified. Reservation lists must stay in lockstep with the dispatch rules they protect.

Metadata

Metadata

Assignees

No one assigned

    Labels

    roadmapLong-term planning

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions