Skip to content

Releases: or1can/ratect

ratect 0.9.0

ratect 0.9.0 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 18 Sep 10:14
Immutable release. Only release title and notes can be modified.
ratect/v0.9.0
944b75d

Added

  • A crash now prints where to report it, the binary's version and platform, and a reminder to re-run with RUST_BACKTRACE=1 if it isn't already set.
  • A dependency that exits on its own after already becoming ready — while the task's own command, or a later dependency's own health/setup wait, is still going — now prints a warning naming the container and its exit code, in every output mode.
  • The NO_COLOR environment variable is now honored, with exactly the same effect as --no-color.
  • The CLICOLOR_FORCE environment variable is now honored: forces colored output even when stdout isn't a terminal (e.g. a CI log viewer), without affecting which output style is auto-selected. --no-color/NO_COLOR always win over it if either is also set.

Changed

  • all output mode now also flushes a line on a lone carriage return (\r), not just \n — a container redrawing progress in place (pip/curl/apt-style) now prints one line per redraw instead of staying silent until the stream ends and then dumping everything as one line.

Fixed

  • Released archives now include NOTICE, alongside the existing LICENSE/README.md/RELEASES.md.
  • fancy output no longer corrupts its live status block when the terminal window narrows mid-run on a reflowing emulator (iTerm2, GNOME Terminal, kitty) — it now restarts the block on a fresh line below the old one instead.

ratect-compat 0.30.0

ratect-compat 0.30.0 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 18 Sep 10:12
Immutable release. Only release title and notes can be modified.
ratect-compat/v0.30.0
944b75d

Added

  • A crash now prints where to report it, the binary's version and platform, and a reminder to re-run with RUST_BACKTRACE=1 if it isn't already set.
  • A dependency that exits on its own after already becoming ready — while the task's own command, or a later dependency's own health/setup wait, is still going — now prints a warning naming the container and its exit code, in every output mode.
  • The NO_COLOR environment variable is now honored, with exactly the same effect as --no-color.
  • The CLICOLOR_FORCE environment variable is now honored: forces colored output even when stdout isn't a terminal (e.g. a CI log viewer), without affecting which output style is auto-selected. --no-color/NO_COLOR always win over it if either is also set.

Changed

  • all output mode now also flushes a line on a lone carriage return (\r), not just \n — a container redrawing progress in place (pip/curl/apt-style) now prints one line per redraw instead of staying silent until the stream ends and then dumping everything as one line.

Fixed

  • Released archives now include NOTICE, alongside the existing LICENSE/README.md/RELEASES.md.
  • fancy output no longer corrupts its live status block when the terminal window narrows mid-run on a reflowing emulator (iTerm2, GNOME Terminal, kitty) — it now restarts the block on a fresh line below the old one instead.

ratect 0.8.0

ratect 0.8.0 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 16 Sep 13:42
Immutable release. Only release title and notes can be modified.
ratect/v0.8.0
bdbe6f3

Added

  • Each release now also publishes a shell installer (curl | sh) alongside
    the archives, for both ratect-compat and ratect — see
    docs/installation.md's "Install script" section.
  • Each release also now publishes a Homebrew formula for both binaries to a
    shared tap (or1can/homebrew-tap) — brew install or1can/tap/ratect /
    ratect-compat.

ratect 0.7.0

ratect 0.7.0 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 16 Sep 10:44
Immutable release. Only release title and notes can be modified.
ratect/v0.7.0
47864ba

Added

  • Added a cargo-dist-based pipeline that builds Linux (x86_64 gnu/musl,
    aarch64 musl) and native macOS (x86_64, arm64) binaries and publishes them
    to a GitHub Release on every ratect/ratect-compat version tag push,
    smoke-testing each freshly built binary (--version/--help) before it's
    uploaded, and attaching a CycloneDX SBOM of the released binary's
    dependency tree. Every binary archive, the SBOM, and the checksums
    manifest carry a GitHub Artifact Attestation, verifiable with gh attestation verify <file> --repo or1can/ratect once downloaded. This is
    the first release built and published this way — see
    decisions/0010 and
    docs/installation.md for the details.
  • Added a CI check (Release Pipeline Config) that validates
    dist-workspace.toml on every PR. Reported, not yet a required check —
    contributor-tooling only for now.

Changed

  • Replaced the tools/echoed-claims.py/spliced-docs.py/stale-claims.py/verify-docs.py documentation-integrity scripts with the claims Claude Code plugin — see decisions/0009. No effect on the released binaries; contributor-tooling only.

Security

  • Upgraded rustls to 0.23.45, fixing RUSTSEC-2026-0285 (TLS 1.3 handshake messages incorrectly accepted across encryption level boundaries).

ratect-compat 0.29.0

ratect-compat 0.29.0 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 16 Sep 13:46
Immutable release. Only release title and notes can be modified.
ratect-compat/v0.29.0
bdbe6f3

Added

  • Each release now also publishes a shell installer (curl | sh) alongside
    the archives, for both ratect-compat and ratect — see
    docs/installation.md's "Install script" section.
  • Each release also now publishes a Homebrew formula for both binaries to a
    shared tap (or1can/homebrew-tap) — brew install or1can/tap/ratect /
    ratect-compat.

ratect-compat 0.28.0

ratect-compat 0.28.0 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 16 Sep 10:44
Immutable release. Only release title and notes can be modified.
ratect-compat/v0.28.0
47864ba

Added

  • Added a cargo-dist-based pipeline that builds Linux (x86_64 gnu/musl,
    aarch64 musl) and native macOS (x86_64, arm64) binaries and publishes them
    to a GitHub Release on every ratect/ratect-compat version tag push,
    smoke-testing each freshly built binary (--version/--help) before it's
    uploaded, and attaching a CycloneDX SBOM of the released binary's
    dependency tree. Every binary archive, the SBOM, and the checksums
    manifest carry a GitHub Artifact Attestation, verifiable with gh attestation verify <file> --repo or1can/ratect once downloaded. This is
    the first release built and published this way — see
    decisions/0010 and
    docs/installation.md for the details.
  • Added a CI check (Release Pipeline Config) that validates
    dist-workspace.toml on every PR. Reported, not yet a required check —
    contributor-tooling only for now.

Changed

  • Replaced the tools/echoed-claims.py/spliced-docs.py/stale-claims.py/verify-docs.py documentation-integrity scripts with the claims Claude Code plugin — see decisions/0009. No effect on the released binaries; contributor-tooling only.

Security

  • Upgraded rustls to 0.23.45, fixing RUSTSEC-2026-0285 (TLS 1.3 handshake messages incorrectly accepted across encryption level boundaries).

ratect 0.6.0

ratect 0.6.0 Pre-release
Pre-release

Choose a tag to compare

@or1can or1can released this 13 Sep 21:36
Immutable release. Only release title and notes can be modified.
ratect/v0.6.0
7a2f0d2

Changed

  • (ratect only) ratect resources list/clean's leftover selection and removal, and ratect doctor/config validate's findings, moved from ratect/src/main.rs into new ratect-core modules (resources.rs, diagnostics.rs), generic over ContainerRuntime rather than bound to the concrete Docker client. No behaviour change — same output, same flags, same exit codes. First two commits of a refactoring release scoped in RELEASES.md; the point was testability, not surface: leftover selection, the containers-before-networks ordering rule, and partial-failure behaviour were previously provable only against a live daemon, and doctor's leftover count now goes through the exact same selection resources list uses instead of its own separate query.
  • cache.rs's cache-storage concept — what caches exist, their project/shared scope rule, and how ratect caches/ratect-compat --clean/--clean-cache list or remove them — is now one CacheStore, behind ten public items instead of seventeen (the reduction is in what each binary has to re-derive, not a specific target count: three items — CacheSelection, RemovedCache, CacheRefusal — are newly public because CacheStore's own return values need to be). Both binaries switched onto it; same output, same flags, same exit codes, verified against the real Docker suite for both — ratect-compat now makes one extra list_volumes/directory-listing call before removing (CacheStore::remove always confirms scope against a prior list), a latency cost, not a behaviour change. Volume-versus-directory is an enum rather than a Docker client that had to be there only sometimes, so "a volume cache needs a daemon" is unrepresentable rather than a runtime assertion; the shared-cache-without---scope-shared refusal is a typed value the binary renders wording for, rather than a check duplicated wherever it was needed. Part of the refactoring release scoped in RELEASES.md.
  • Docker daemon connection selection (--docker-host/-context/-tls*) moved out of docker.rs into its own docker/connection.rs — a self-contained concept (no reference to ContainerRuntime) that shared a 3,000+ line module with container lifecycle purely by history. DockerConnectionOptions's own path is unaffected. Part of the refactoring release scoped in RELEASES.md.
  • ContainerRuntime::run_container/start_background_container now take a single owned ContainerSpec (new ratect-core::container_spec module) instead of a 14-parameter and a 10-parameter call respectively — one pure function, derive_spec, assembles it from a container's config plus a task's run or a dependency's customise overlay, replacing what used to be two independently hand-built call sites in engine.rs with no shared code between them to keep in step. No behaviour change — same output, same flags, same exit codes, verified against the real Docker suite. Part of the refactoring release scoped in RELEASES.md.
  • The config loader and shell completion's own include-walker each decided whether a type: git include's target may be read by hand-combining the nested-Git-permission check with the containment check, in their own words. Both now call one include_trust::gate, consumed via refuse_read/refuse_load, so the two can no longer answer differently. No behaviour change — same output, same flags, same exit codes. Part of the refactoring release scoped in RELEASES.md.
  • TaskEngine::new now takes event_sink/interrupt directly, alongside docker — both are collaborators the engine talks to, not opt-in settings, and with_event_sink/with_interrupt are gone along with ten other builders (with_existing_network/with_image_overrides/with_image_tags/with_max_parallelism/with_cache_options/without_port_publishing/without_proxy_environment_variables/without_prerequisites/without_cleanup_after_success/without_cleanup_after_failure) that only ever reached production through TaskEngine::with_settings, which now applies TaskEngineSettings directly instead of delegating to them. No behaviour change — same output, same flags, same exit codes, verified against the real Docker suite for both binaries. Part of the refactoring release scoped in RELEASES.md.
  • Boundary's containment half (GitBoundary, previously in config.rs) and grants half (Bundle/Trust, in include_trust.rs) are now both in include_trust.rs, matching CONTEXT.md's definition of Boundary as one concept rather than a split one. No behaviour change. (#7)
  • ConfigFormat's dialect-conditional rules — which fields ratect-compat's YAML format rejects, whether extends is allowed — moved off two hand-assembled if format == ConfigFormat::X blocks in the loader and onto ConfigFormat itself (reject_incompatible_fields, allows_extends), so a future dialect-specific rule has one place to land. No behaviour change. (#8)
  • ContainerRuntime's 16 methods served three different callers, but only engine.rs's fake genuinely implemented all of them — resources.rs's and cache.rs's fakes stubbed twelve and fourteen unimplemented!() methods respectively just to satisfy a trait shape they didn't need. Split into ResourceInventory (containers/networks) and VolumeStore (volumes), each owned by the module that actually uses it, with ContainerRuntime requiring both as supertraits alongside its remaining ten methods. No behaviour change — same output, same flags, same exit codes. (#9)
  • ContainerRuntime::build_image was the one entry point still taking a flat 8-parameter list, the same shape run_container/start_background_container had before ContainerSpec replaced them earlier in this release. BuildSpec (container_spec.rs) is the build counterpart, assembled by derive_build_spec. No behaviour change. (#10)
  • ratect's private TerminalFacts struct (whether stdout is a terminal, TERM, console dimensions) moves into ratect-core::ui as pub, so ratect-compat shares it instead of reading the same three facts inline with a near-duplicate doc comment. No behaviour change. (#11)

Fixed

  • Pulling an image, or building one from a Dockerfile FROM a private base image, now uses your configured registry credentials. Ratect previously sent no credentials on either, so a private image failed unless the daemon already had it cached locally — indistinguishable from an unrelated Docker problem. Both now resolve credentials the same way the docker CLI does (~/.docker/config.json's auths/credsStore/credHelpers, including keychain-backed stores and cloud registries like ECR/GCR via their credential helper), so logging in once with docker login beforehand is enough. Build resolves every registry your Docker config declares, since neither Batect nor Ratect parses a Dockerfile's FROM lines to scope this more precisely. A broken or unreachable credential helper never fails the pull/build itself — only a warning naming the registry, so an unrelated problem elsewhere doesn't block work that never needed it.
  • A Docker daemon older than the very latest release is no longer rejected outright. Every request was previously stamped at a fixed, recent Docker Engine API version with no negotiation, so any daemon reporting a lower maximum version refused every request with a confusing "client version is too new" error. Ratect now negotiates the API version against the real daemon at connection time, downgrading to match it — any daemon at or above Ratect's own floor (Docker 20.10) now works, and one below it fails immediately with a clear error naming both its version and the one required.
  • A TLS connection to the Docker daemon no longer fails outright when a single entry in the OS trust store can't be read. bollard's connect_with_ssl treated any error from loading the OS's native certificate store as fatal, discarding every certificate that did load successfully — and the explicit --docker-tls-ca-cert/--docker-cert-path certificate this crate adds on the very next line — even though nothing about that failure has anything to do with Docker or with what was actually configured. One unreadable entry anywhere in the trust store made --docker-tls/--docker-tls-verify unusable on an otherwise-correctly-configured machine, on which the docker CLI itself connects fine. Found investigating a recurring, previously-unexplained Could not load native certs test failure — patched in the bollard fork this crate's [patch.crates-io] pins; offered upstream as fussybeaver/bollard#796.

ratect-compat 0.27.0

ratect-compat 0.27.0 Pre-release
Pre-release

Choose a tag to compare

@or1can or1can released this 13 Sep 21:36
Immutable release. Only release title and notes can be modified.
ratect-compat/v0.27.0
7a2f0d2

Changed

  • (ratect only) ratect resources list/clean's leftover selection and removal, and ratect doctor/config validate's findings, moved from ratect/src/main.rs into new ratect-core modules (resources.rs, diagnostics.rs), generic over ContainerRuntime rather than bound to the concrete Docker client. No behaviour change — same output, same flags, same exit codes. First two commits of a refactoring release scoped in RELEASES.md; the point was testability, not surface: leftover selection, the containers-before-networks ordering rule, and partial-failure behaviour were previously provable only against a live daemon, and doctor's leftover count now goes through the exact same selection resources list uses instead of its own separate query.
  • cache.rs's cache-storage concept — what caches exist, their project/shared scope rule, and how ratect caches/ratect-compat --clean/--clean-cache list or remove them — is now one CacheStore, behind ten public items instead of seventeen (the reduction is in what each binary has to re-derive, not a specific target count: three items — CacheSelection, RemovedCache, CacheRefusal — are newly public because CacheStore's own return values need to be). Both binaries switched onto it; same output, same flags, same exit codes, verified against the real Docker suite for both — ratect-compat now makes one extra list_volumes/directory-listing call before removing (CacheStore::remove always confirms scope against a prior list), a latency cost, not a behaviour change. Volume-versus-directory is an enum rather than a Docker client that had to be there only sometimes, so "a volume cache needs a daemon" is unrepresentable rather than a runtime assertion; the shared-cache-without---scope-shared refusal is a typed value the binary renders wording for, rather than a check duplicated wherever it was needed. Part of the refactoring release scoped in RELEASES.md.
  • Docker daemon connection selection (--docker-host/-context/-tls*) moved out of docker.rs into its own docker/connection.rs — a self-contained concept (no reference to ContainerRuntime) that shared a 3,000+ line module with container lifecycle purely by history. DockerConnectionOptions's own path is unaffected. Part of the refactoring release scoped in RELEASES.md.
  • ContainerRuntime::run_container/start_background_container now take a single owned ContainerSpec (new ratect-core::container_spec module) instead of a 14-parameter and a 10-parameter call respectively — one pure function, derive_spec, assembles it from a container's config plus a task's run or a dependency's customise overlay, replacing what used to be two independently hand-built call sites in engine.rs with no shared code between them to keep in step. No behaviour change — same output, same flags, same exit codes, verified against the real Docker suite. Part of the refactoring release scoped in RELEASES.md.
  • The config loader and shell completion's own include-walker each decided whether a type: git include's target may be read by hand-combining the nested-Git-permission check with the containment check, in their own words. Both now call one include_trust::gate, consumed via refuse_read/refuse_load, so the two can no longer answer differently. No behaviour change — same output, same flags, same exit codes. Part of the refactoring release scoped in RELEASES.md.
  • TaskEngine::new now takes event_sink/interrupt directly, alongside docker — both are collaborators the engine talks to, not opt-in settings, and with_event_sink/with_interrupt are gone along with ten other builders (with_existing_network/with_image_overrides/with_image_tags/with_max_parallelism/with_cache_options/without_port_publishing/without_proxy_environment_variables/without_prerequisites/without_cleanup_after_success/without_cleanup_after_failure) that only ever reached production through TaskEngine::with_settings, which now applies TaskEngineSettings directly instead of delegating to them. No behaviour change — same output, same flags, same exit codes, verified against the real Docker suite for both binaries. Part of the refactoring release scoped in RELEASES.md.
  • Boundary's containment half (GitBoundary, previously in config.rs) and grants half (Bundle/Trust, in include_trust.rs) are now both in include_trust.rs, matching CONTEXT.md's definition of Boundary as one concept rather than a split one. No behaviour change. (#7)
  • ConfigFormat's dialect-conditional rules — which fields ratect-compat's YAML format rejects, whether extends is allowed — moved off two hand-assembled if format == ConfigFormat::X blocks in the loader and onto ConfigFormat itself (reject_incompatible_fields, allows_extends), so a future dialect-specific rule has one place to land. No behaviour change. (#8)
  • ContainerRuntime's 16 methods served three different callers, but only engine.rs's fake genuinely implemented all of them — resources.rs's and cache.rs's fakes stubbed twelve and fourteen unimplemented!() methods respectively just to satisfy a trait shape they didn't need. Split into ResourceInventory (containers/networks) and VolumeStore (volumes), each owned by the module that actually uses it, with ContainerRuntime requiring both as supertraits alongside its remaining ten methods. No behaviour change — same output, same flags, same exit codes. (#9)
  • ContainerRuntime::build_image was the one entry point still taking a flat 8-parameter list, the same shape run_container/start_background_container had before ContainerSpec replaced them earlier in this release. BuildSpec (container_spec.rs) is the build counterpart, assembled by derive_build_spec. No behaviour change. (#10)
  • ratect's private TerminalFacts struct (whether stdout is a terminal, TERM, console dimensions) moves into ratect-core::ui as pub, so ratect-compat shares it instead of reading the same three facts inline with a near-duplicate doc comment. No behaviour change. (#11)

Fixed

  • Pulling an image, or building one from a Dockerfile FROM a private base image, now uses your configured registry credentials. Ratect previously sent no credentials on either, so a private image failed unless the daemon already had it cached locally — indistinguishable from an unrelated Docker problem. Both now resolve credentials the same way the docker CLI does (~/.docker/config.json's auths/credsStore/credHelpers, including keychain-backed stores and cloud registries like ECR/GCR via their credential helper), so logging in once with docker login beforehand is enough. Build resolves every registry your Docker config declares, since neither Batect nor Ratect parses a Dockerfile's FROM lines to scope this more precisely. A broken or unreachable credential helper never fails the pull/build itself — only a warning naming the registry, so an unrelated problem elsewhere doesn't block work that never needed it.
  • A Docker daemon older than the very latest release is no longer rejected outright. Every request was previously stamped at a fixed, recent Docker Engine API version with no negotiation, so any daemon reporting a lower maximum version refused every request with a confusing "client version is too new" error. Ratect now negotiates the API version against the real daemon at connection time, downgrading to match it — any daemon at or above Ratect's own floor (Docker 20.10) now works, and one below it fails immediately with a clear error naming both its version and the one required.
  • A TLS connection to the Docker daemon no longer fails outright when a single entry in the OS trust store can't be read. bollard's connect_with_ssl treated any error from loading the OS's native certificate store as fatal, discarding every certificate that did load successfully — and the explicit --docker-tls-ca-cert/--docker-cert-path certificate this crate adds on the very next line — even though nothing about that failure has anything to do with Docker or with what was actually configured. One unreadable entry anywhere in the trust store made --docker-tls/--docker-tls-verify unusable on an otherwise-correctly-configured machine, on which the docker CLI itself connects fine. Found investigating a recurring, previously-unexplained Could not load native certs test failure — patched in the bollard fork this crate's [patch.crates-io] pins; offered upstream as fussybeaver/bollard#796.

ratect 0.5.0

ratect 0.5.0 Pre-release
Pre-release

Choose a tag to compare

@or1can or1can released this 03 Sep 08:40
Immutable release. Only release title and notes can be modified.
ratect/v0.5.0
bd84f3f

Two leaks closed and a containment escape, all reachable without doing anything unusual. A run stopped by anything other than Ctrl+C — an editor closing, docker stop, systemd, a CI cancel button — used to be killed outright, leaving the task's containers and its network behind; that was the exact defect 0.25.0 set out to fix, reached by a route it never trapped. All three terminating signals now take the same cleanup path, and the exit code says which one it was.

A proxy running on your own machine finally works on Linux, closing Batect's oldest open issue — eight years old, and one Batect never fixed because its recipe predated the mechanism that makes it a one-liner. Where the URL can be made correct but the proxy still isn't reachable, Ratect now says so and names the security cost of the remedy rather than leaving you to find out.

And the surviving half of 0.25.0's containment escape is closed: a Git-included bundle could commit a symlink and mount anywhere on the host, because the check compared spellings rather than real locations.

Fixed

  • A proxy running on the host now works on Linux, closing Batect's oldest open issue, untouched for eight years. http_proxy=http://localhost:3333 was rewritten to http://host.docker.internal:3333 on macOS and Windows and left alone on Linux — where localhost, read from inside a container, means the container itself, so the proxy silently wasn't used or something unrelated was reached. Ratect now rewrites on every platform and adds the host.docker.internal:host-gateway entry that makes the name resolve where nothing else supplies it, using Docker's own --add-host mechanism. Batect's own recipe for this predated that mechanism (Docker Engine 20.10, December 2020) and was never implemented; taking it means this feature needs Docker 20.10 or newer, which is well below the API version Ratect already requires of the daemon for everything else — see Prerequisites.

    The entry is added on every platform, not only Linux, whenever a URL was rewritten: on macOS and Windows that name is one Docker Desktop already answers through its own DNS, and the entry resolves to the same gateway, so this is a new /etc/hosts line on platforms that already worked rather than a change in what they can reach. Adding it only when a URL was rewritten means a run whose proxy already names a routable host gets nothing it didn't ask for, and a container's own additional_hosts entry for that name still wins. Image builds get it too — a RUN step reaches the proxy through the same rewritten URL, and a build without the entry would fail exactly where the image it produced would have worked.

    Rewriting the URL can't make an unreachable proxy reachable, so Ratect also warns where it still won't work. A proxy bound only to 127.0.0.1cntlm and similar default to that — refuses a connection from a container however correct the URL is; on Linux, Ratect reads /proc/net/tcp/tcp6, sees the port is loopback-bound, and says so, naming both the remedy and its cost: binding to 0.0.0.0 exposes the proxy to everything else that can reach the machine. It warns rather than fails, since a run may never use the proxy and --no-proxy-vars already turns propagation off. See Proxy environment variables and Differences from Batect.

  • A run stopped with SIGTERM or SIGHUP now cleans up after itself, as Ctrl+C has since 0.25.0. Only SIGINT was trapped, so every other terminating signal killed Ratect where it stood and left the task's containers and its network behind — the exact leak 0.25.0 set out to close, reached by a different route. This is what actually bites in practice: ratect-compat run as a subprocess by an editor is sent SIGTERM when that editor closes or restarts it, and so are runs stopped by docker stop, systemd, or a CI cancel button. Networks leak faster than containers, because a run that fails during startup leaks one too — one developer machine reached 29 abandoned networks against Docker's default pool of roughly 31, at which point every Ratect run on it failed with all predefined address pools have been fully subnetted, including ones that had leaked nothing.

    All three signals take the identical path — the run is abandoned, then cleaned up, and --no-cleanup-after-failure suppresses that exactly as for any other failure. What the signal decides is the exit code, which is now 128 + the signal's own number: 130 for Ctrl+C as before, 143 for SIGTERM, 129 for SIGHUP. A run's failure message and its cleanup warning name the signal too, since "interrupted" and "press Ctrl+C again" are both untrue of a process an init system stopped. Batect traps SIGINT alone, so this is a deliberate divergence rather than a parity gap. SIGKILL cannot be trapped by anything, so it still leaks — the ownership labels remain the backstop for that, via ratect resources clean (the ratect binary; from ratect-compat, which has no such verb, a docker ps -a --filter label=eu.orican.ratect.project=<name> sweep). See Differences from Batect.

Security

  • A Git-included bundle can no longer escape its containment with a symlink it commits (both binaries; affects 0.10.0 onward, when per-bundle container boundaries were introduced). A bundle's containers may resolve volumes host paths and build_directory only within its own clone or your project directory, unless you grant allow_host_paths. That check was purely lexical, so a bundle committing a symlink escape -> / beside local: escape produced a path starting with its own clone directory while pointing anywhere on the host, and Docker dereferenced it when bind-mounting. This is the surviving half of the ..-traversal escape fixed in 0.25.0 — the same class, of which only the lexical half was closed then.

    The path is now compared by real location as well: its longest existing ancestor is resolved and the not-yet-created tail re-appended, so a volumes path that doesn't exist yet is still allowed, as Ratect or Docker creates it. Both allowed roots are resolved the same way, since a project or cache directory may itself sit under a symlink — /tmp does on macOS. A path whose real location cannot be determined at all — an ancestor this process may not search, a symlink loop — is refused rather than judged on its spelling, since the Docker daemon that dereferences it runs as root and is under no such restriction. Include targets were already checked this way; it is container paths that were not, because unlike an include they need not exist when the configuration is read.

ratect-compat 0.26.0

ratect-compat 0.26.0 Pre-release
Pre-release

Choose a tag to compare

@or1can or1can released this 03 Sep 08:40
Immutable release. Only release title and notes can be modified.
ratect-compat/v0.26.0
bd84f3f

Two leaks closed and a containment escape, all reachable without doing anything unusual. A run stopped by anything other than Ctrl+C — an editor closing, docker stop, systemd, a CI cancel button — used to be killed outright, leaving the task's containers and its network behind; that was the exact defect 0.25.0 set out to fix, reached by a route it never trapped. All three terminating signals now take the same cleanup path, and the exit code says which one it was.

A proxy running on your own machine finally works on Linux, closing Batect's oldest open issue — eight years old, and one Batect never fixed because its recipe predated the mechanism that makes it a one-liner. Where the URL can be made correct but the proxy still isn't reachable, Ratect now says so and names the security cost of the remedy rather than leaving you to find out.

And the surviving half of 0.25.0's containment escape is closed: a Git-included bundle could commit a symlink and mount anywhere on the host, because the check compared spellings rather than real locations.

Fixed

  • A proxy running on the host now works on Linux, closing Batect's oldest open issue, untouched for eight years. http_proxy=http://localhost:3333 was rewritten to http://host.docker.internal:3333 on macOS and Windows and left alone on Linux — where localhost, read from inside a container, means the container itself, so the proxy silently wasn't used or something unrelated was reached. Ratect now rewrites on every platform and adds the host.docker.internal:host-gateway entry that makes the name resolve where nothing else supplies it, using Docker's own --add-host mechanism. Batect's own recipe for this predated that mechanism (Docker Engine 20.10, December 2020) and was never implemented; taking it means this feature needs Docker 20.10 or newer, which is well below the API version Ratect already requires of the daemon for everything else — see Prerequisites.

    The entry is added on every platform, not only Linux, whenever a URL was rewritten: on macOS and Windows that name is one Docker Desktop already answers through its own DNS, and the entry resolves to the same gateway, so this is a new /etc/hosts line on platforms that already worked rather than a change in what they can reach. Adding it only when a URL was rewritten means a run whose proxy already names a routable host gets nothing it didn't ask for, and a container's own additional_hosts entry for that name still wins. Image builds get it too — a RUN step reaches the proxy through the same rewritten URL, and a build without the entry would fail exactly where the image it produced would have worked.

    Rewriting the URL can't make an unreachable proxy reachable, so Ratect also warns where it still won't work. A proxy bound only to 127.0.0.1cntlm and similar default to that — refuses a connection from a container however correct the URL is; on Linux, Ratect reads /proc/net/tcp/tcp6, sees the port is loopback-bound, and says so, naming both the remedy and its cost: binding to 0.0.0.0 exposes the proxy to everything else that can reach the machine. It warns rather than fails, since a run may never use the proxy and --no-proxy-vars already turns propagation off. See Proxy environment variables and Differences from Batect.

  • A run stopped with SIGTERM or SIGHUP now cleans up after itself, as Ctrl+C has since 0.25.0. Only SIGINT was trapped, so every other terminating signal killed Ratect where it stood and left the task's containers and its network behind — the exact leak 0.25.0 set out to close, reached by a different route. This is what actually bites in practice: ratect-compat run as a subprocess by an editor is sent SIGTERM when that editor closes or restarts it, and so are runs stopped by docker stop, systemd, or a CI cancel button. Networks leak faster than containers, because a run that fails during startup leaks one too — one developer machine reached 29 abandoned networks against Docker's default pool of roughly 31, at which point every Ratect run on it failed with all predefined address pools have been fully subnetted, including ones that had leaked nothing.

    All three signals take the identical path — the run is abandoned, then cleaned up, and --no-cleanup-after-failure suppresses that exactly as for any other failure. What the signal decides is the exit code, which is now 128 + the signal's own number: 130 for Ctrl+C as before, 143 for SIGTERM, 129 for SIGHUP. A run's failure message and its cleanup warning name the signal too, since "interrupted" and "press Ctrl+C again" are both untrue of a process an init system stopped. Batect traps SIGINT alone, so this is a deliberate divergence rather than a parity gap. SIGKILL cannot be trapped by anything, so it still leaks — the ownership labels remain the backstop for that, via ratect resources clean (the ratect binary; from ratect-compat, which has no such verb, a docker ps -a --filter label=eu.orican.ratect.project=<name> sweep). See Differences from Batect.

Security

  • A Git-included bundle can no longer escape its containment with a symlink it commits (both binaries; affects 0.10.0 onward, when per-bundle container boundaries were introduced). A bundle's containers may resolve volumes host paths and build_directory only within its own clone or your project directory, unless you grant allow_host_paths. That check was purely lexical, so a bundle committing a symlink escape -> / beside local: escape produced a path starting with its own clone directory while pointing anywhere on the host, and Docker dereferenced it when bind-mounting. This is the surviving half of the ..-traversal escape fixed in 0.25.0 — the same class, of which only the lexical half was closed then.

    The path is now compared by real location as well: its longest existing ancestor is resolved and the not-yet-created tail re-appended, so a volumes path that doesn't exist yet is still allowed, as Ratect or Docker creates it. Both allowed roots are resolved the same way, since a project or cache directory may itself sit under a symlink — /tmp does on macOS. A path whose real location cannot be determined at all — an ancestor this process may not search, a symlink loop — is refused rather than judged on its spelling, since the Docker daemon that dereferences it runs as root and is under no such restriction. Include targets were already checked this way; it is container paths that were not, because unlike an include they need not exist when the configuration is read.