From 93b011aeee74bab8a5cf5da75620b576f5939e01 Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Tue, 19 May 2026 09:50:10 +0200 Subject: [PATCH] sandboxes: add release notes page sourced from GitHub releases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add content/manuals/ai/sandboxes/release-notes.md as a single page listing the latest stable releases of Docker Sandboxes, with a link to the GitHub release history for older versions. The block between BEGIN/END GENERATED RELEASES markers is populated by hack/sbx-release-notes.py — a single-file uv-run script (PEP 723 inline deps, Jinja2 only) that hits the GitHub Releases API for docker/sbx-releases, filters to strict-semver stable tags with non-empty bodies, and emits H2-per-release sections for the latest N minor releases (default 3) and their patches. Re-runs are idempotent and only touch content between the markers. The script also invokes `prettier --write` so the output stays lint-clean. Extend the existing release-notes Vale skip rule with content/manuals/**/release-notes.md so the new file under ai/sandboxes/ picks up the same lint exclusions as engine/desktop/build release notes. Co-Authored-By: Claude Opus 4.7 (1M context) Signed-off-by: David Karlsson <35727626+dvdksn@users.noreply.github.com> --- .vale.ini | 2 +- content/manuals/ai/sandboxes/release-notes.md | 173 ++++++++++++++++++ hack/sbx-release-notes.py | 155 ++++++++++++++++ 3 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 content/manuals/ai/sandboxes/release-notes.md create mode 100755 hack/sbx-release-notes.py diff --git a/.vale.ini b/.vale.ini index 088fb33572a5..646e4dc5a900 100644 --- a/.vale.ini +++ b/.vale.ini @@ -10,7 +10,7 @@ Vale.Spelling = NO Vale.Terms = NO # Skip release notes and old desktop changelog content entirely -[{content/manuals/*/release-notes.md,content/manuals/*/release-notes/**,content/manuals/desktop/previous-versions/**}] +[{content/manuals/*/release-notes.md,content/manuals/*/release-notes/**,content/manuals/**/release-notes.md,content/manuals/desktop/previous-versions/**}] Docker.Avoid = NO Docker.Capitalization = NO Docker.Exclamation = NO diff --git a/content/manuals/ai/sandboxes/release-notes.md b/content/manuals/ai/sandboxes/release-notes.md new file mode 100644 index 000000000000..0ad247b00f64 --- /dev/null +++ b/content/manuals/ai/sandboxes/release-notes.md @@ -0,0 +1,173 @@ +--- +title: Docker Sandboxes release notes +linkTitle: Release notes +description: New features, bug fixes, and changes in Docker Sandboxes +keywords: docker sandboxes, sbx, release notes, changelog +toc_min: 1 +toc_max: 2 +tags: + - Release notes +--- + +This page lists changes in recent stable releases of Docker Sandboxes. For +the full release history, including pre-releases and downloads, see the +[Docker Sandboxes releases on GitHub](https://github.com/docker/sbx-releases/releases). + + + +## 0.30.0 + +{{< release-date date="2026-05-19" >}} + +[GitHub release](https://github.com/docker/sbx-releases/releases/tag/v0.30.0) + +### Highlights + +The CLI gets **non-interactive Docker Hub login** for scripted workflows, and sandboxes now have **a configurable grace period before auto-stopping** when the last session exits. Plus a wave of fixes covering Linux packaging, macOS worktree compatibility, Windows installer paths, network isolation, and recoverable sandbox state when host directories vanish. + +### What's New + +#### Governance & Policy + +- Allow `sbx policy` setup before login + +#### Kits & Agents + +- Re-run `commands.startup` on every container start so init hooks are idempotent across restarts +- Per-kit memory files for progressive disclosure +- Enumerate installed kits in the AI memory file's Kits section + +#### CLI & Auth + +- Add non-interactive Docker Hub login for scripted workflows +- Migrate `/reset` to `/daemon/reset`; state-dir wipe is now daemon-side +- Print "Git repository detected" once when using `--branch` +- Skip implicit run options when the user provides explicit args + +#### Networking & Sandboxd + +- Bind both loopback stacks by default when publishing ports +- Allow raw TCP to `host.docker.internal` when localhost is allowed in policy +- Add grace period before auto-stopping a sandbox when the last session exits + +#### Bug Fixes + +- Build sailor's `ffi` crate instead of `ffi-krun` for packaged Linux release artifacts +- Keep sandboxes recoverable when workspace or worktree is deleted on the host +- Add macOS `/private` path compatibility for worktrees +- Probe canonical socket path for `sun_path` budget — fixes `krun_start_enter failed` on macOS with long usernames +- Namespace gVisor socket dir and auth/secret stores by `--app-name` so concurrent daemons don't collide +- Sanitize runtime ID when looking up gVisor network +- Check database version before starting the daemon; surface an instructive error instead of crashing +- Report Docker daemon startup time instead of the pre-start message in DinD +- Harden `BuildFileCredential` to check more than just file existence +- Open a sentinel connection in `cp` and `kit add` to prevent auto-stop race +- Remove redundant `ContainerKill` before `ContainerRemove` in sandboxlib +- Use a safe Windows `start` invocation for `OpenURL` in the TUI +- Rename WiX install directory id to `INSTALLFOLDER` + +#### Documentation + +- Warn agents about worktree path traps with `--branch` +- Improve consistency and wording in CLI help strings + +## 0.29.0 + +{{< release-date date="2026-05-13" >}} + +[GitHub release](https://github.com/docker/sbx-releases/releases/tag/v0.29.0) + +### Highlights + +This release brings **per-sandbox network policies**, giving callers fine-grained control over which domains each sandbox can reach, including an explicit `deniedDomains` list and allowance for binary TCP protocols like SSH. Sandboxes now carry **daemon-assigned UUIDs**, enabling reliable identification across restarts and telemetry. Several **agent improvements** land in this release: Gemini gets SSO browser relay, Codex auth is more robust, and the OpenAI OAuth flow now auto-opens the browser. A round of **bug fixes** improves daemon robustness on macOS (long-username `sun_path` overflow), gVisor isolation under `--app-name`, and database-version handling. + +### What's New + +#### Networking & Policy + +- Support per-sandbox scoped network policies +- Add `deniedDomains` to network kit policy +- Allow binary TCP protocols (e.g. SSH) through domain allow rules +- Pipe in policykit error handler for better diagnostics + +#### Sandboxes + +- Add daemon-assigned UUID to sandbox runtimes + +#### Agents + +- Enable SSO browser relay for Gemini +- Auto-open browser during OpenAI OAuth flow +- Skip auth.json placeholder for Codex when no host credentials +- Expose Claude guidance to Codex sandboxes + +#### CLI + +- Require confirmation for `sbx rm ` to prevent accidental deletion +- Unhide `kit` command in help output + +#### Bug Fixes + +- Namespace gVisor socket dir by `--app-name` so concurrent daemons don't share state +- Probe canonical socket path for `sun_path` budget — fixes `krun_start_enter failed` for macOS users with long usernames +- Check database version before starting the daemon and surface an instructive error instead of crashing +- Route gVisor sockets to a persistent, sandboxd-owned location +- Delete stranded tracker after failed auto-stop with no active sessions +- Clean up DinD volume even when container inspect fails +- Apply `SANDBOXES_STORAGE_ROOT` override to storage config +- Report running binary (not first `sbx` on PATH) in `diagnose` +- Explain how to configure OpenAI credentials in no-creds warning +- Allow MCR layer-blob CDN in default-code-and-containers policy +- Improve empty state of `sbx ls` with actionable guidance + +## 0.28.2 + +{{< release-date date="2026-04-29" >}} + +[GitHub release](https://github.com/docker/sbx-releases/releases/tag/v0.28.2) + +### What's New + +#### CLI + +- Auto-open browser during login flow + +#### Templates + +- Install `ssh-add` and SSH client tools in the `main` template + +#### Bug Fixes + +- Prefer Codex OAuth over discovered API-key credentials +- Propagate host TTY size when running `sbx exec -it` +- Reveal trailing characters in masked secrets + +## 0.28.1 + +{{< release-date date="2026-04-28" >}} + +[GitHub release](https://github.com/docker/sbx-releases/releases/tag/v0.28.1) + +### Highlights + +A small release that wires **custom agent kits** through the CLI — discoverable in `--help` and invocable via `--kit` — and brings +**in-process sandbox run/exec** with launch-mode and settings dialogs to the TUI. Two bug fixes round it out: private Docker Hub image pulls work again via `--template`, and the secrets-masking path is tightened. + +### What's New + +#### CLI + +- Make custom agent kits invocable and surface `--kit` in help +- TUI: in-process sandbox run/exec with launch mode dialog, settings dialog + misc fixes + +#### Bug Fixes + +- Enable private Docker Hub image pulls via `--template` +- Tighten secrets masking and emphasize `set-custom` warning + + + +## Earlier releases + +For older versions, see the +[Docker Sandboxes releases on GitHub](https://github.com/docker/sbx-releases/releases). diff --git a/hack/sbx-release-notes.py b/hack/sbx-release-notes.py new file mode 100755 index 000000000000..07c61e415c93 --- /dev/null +++ b/hack/sbx-release-notes.py @@ -0,0 +1,155 @@ +#!/usr/bin/env -S uv run --quiet --script +# /// script +# requires-python = ">=3.11" +# dependencies = ["jinja2"] +# /// +""" +Fetch recent stable releases from docker/sbx-releases and splice them into +content/manuals/ai/sandboxes/release-notes.md between the BEGIN/END markers. + +Usage (from repo root): + + ./hack/sbx-release-notes.py + GITHUB_TOKEN=$(gh auth token) ./hack/sbx-release-notes.py + ./hack/sbx-release-notes.py --minor-releases 3 +""" + +from __future__ import annotations + +import argparse +import json +import os +import re +import shutil +import subprocess +import sys +import urllib.request +from collections import defaultdict +from pathlib import Path + +from jinja2 import Template + +DEFAULT_REPO = "docker/sbx-releases" +DEFAULT_FILE = Path("content/manuals/ai/sandboxes/release-notes.md") +DEFAULT_MINOR_RELEASES = 3 + +BEGIN = "" +END = "" + +SEMVER = re.compile(r"^v(\d+)\.(\d+)\.(\d+)$") + +TEMPLATE = Template( + """\ +{% for r in releases -%} +## {{ r.version }} + +{{ '{{<' }} release-date date="{{ r.date }}" {{ '>}}' }} + +[GitHub release]({{ r.url }}) + +{{ r.body }} + +{% endfor -%} +""" +) + + +def fetch(repo: str) -> list[dict]: + url = f"https://api.github.com/repos/{repo}/releases?per_page=100" + req = urllib.request.Request( + url, + headers={ + "Accept": "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + }, + ) + if token := os.environ.get("GITHUB_TOKEN"): + req.add_header("Authorization", f"Bearer {token}") + with urllib.request.urlopen(req) as resp: + return json.load(resp) + + +def parse_stable(raw: list[dict]) -> list[dict]: + out = [] + for r in raw: + if r.get("prerelease") or r.get("draft"): + continue + m = SEMVER.match(r["tag_name"]) + if not m: + continue + body = (r.get("body") or "").strip() + if not body: + continue + major, minor, patch = (int(x) for x in m.groups()) + out.append( + { + "major": major, + "minor": minor, + "patch": patch, + "version": f"{major}.{minor}.{patch}", + "date": r["published_at"][:10], + "url": r["html_url"], + "body": shift_headings(body), + } + ) + out.sort(key=lambda r: (r["major"], r["minor"], r["patch"]), reverse=True) + return out + + +def pick_minor_releases(releases: list[dict], n: int) -> list[dict]: + by_minor: dict[tuple[int, int], list[dict]] = defaultdict(list) + for r in releases: + by_minor[(r["major"], r["minor"])].append(r) + latest_keys = sorted(by_minor.keys(), reverse=True)[:n] + keep = set(latest_keys) + return [r for r in releases if (r["major"], r["minor"]) in keep] + + +def shift_headings(body: str) -> str: + """Demote ATX headings by one level so body H2s become H3 under the + version's H2. Skips fenced code blocks.""" + lines = body.splitlines() + in_fence = False + for i, line in enumerate(lines): + stripped = line.lstrip(" \t") + if stripped.startswith(("```", "~~~")): + in_fence = not in_fence + continue + if in_fence: + continue + if stripped.startswith("#"): + indent = line[: len(line) - len(stripped)] + lines[i] = f"{indent}#{stripped}" + return "\n".join(lines) + + +def splice(path: Path, generated: str) -> None: + src = path.read_text() + try: + before, rest = src.split(BEGIN, 1) + _, after = rest.split(END, 1) + except ValueError: + sys.exit(f"markers {BEGIN!r} / {END!r} not found in {path}") + path.write_text(f"{before}{BEGIN}\n\n{generated}{END}{after}") + + +def main() -> None: + p = argparse.ArgumentParser(description=__doc__) + p.add_argument("--repo", default=DEFAULT_REPO) + p.add_argument("--file", type=Path, default=DEFAULT_FILE) + p.add_argument("--minor-releases", type=int, default=DEFAULT_MINOR_RELEASES) + args = p.parse_args() + + releases = pick_minor_releases(parse_stable(fetch(args.repo)), args.minor_releases) + if not releases: + sys.exit("no stable releases found") + + generated = TEMPLATE.render(releases=releases) + splice(args.file, generated) + if shutil.which("npx"): + subprocess.run(["npx", "--no-install", "prettier", "--write", str(args.file)], check=False) + print(f"Wrote {len(releases)} releases (latest {args.minor_releases} minor releases) to {args.file}") + + +if __name__ == "__main__": + main()