From 64ab2379976748d3e2635c774ee32c48eb559553 Mon Sep 17 00:00:00 2001 From: Steffen Maechtel Date: Mon, 24 Aug 2026 21:34:15 +0200 Subject: [PATCH 1/5] docs(design): mark ddev-e2e-test implemented; history summary 0.0.20-0.0.22 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ddev-e2e-test.md header said 'PLANNED — not implemented' but the suite shipped with 0.0.21 (make e2e-ddev, run-ddev.sh, DD0-DD14, camino + bare-origin tiers, first green CI run 2026-08-23). Status now reflects that, records the two as-built deltas (golden image save/load tar instead of docker commit v1; weekly CI schedule still pending) and the phase table is marked complete. project-history-summary.md gains the 0.0.20 (dev-owned mode, #23/#24), 0.0.21 (real-ddev e2e suite, TUI mode line, prompt-free wrapper) and 0.0.22 (headless ecosystem mode #42, root-equivalent audit #37, skills-only .claude migration) rows — problem -> decision, sourced from the tag ranges. --- docs/design/ddev-e2e-test.md | 54 ++++++++++++++++---------- docs/design/project-history-summary.md | 3 ++ 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/docs/design/ddev-e2e-test.md b/docs/design/ddev-e2e-test.md index 457c7e0..92b1ad7 100644 --- a/docs/design/ddev-e2e-test.md +++ b/docs/design/ddev-e2e-test.md @@ -1,15 +1,26 @@ # PLAN-DDEV-E2E: real-ddev e2e suite with a cached golden container -> Status: **PLANNED — not implemented.** Design record for a third e2e suite -> (`make e2e-ddev`) that runs the REAL ddev against the REAL docker-rootless -> daemon inside the systemd test container, answering the recurring burn-in -> problem: most ddev issues (#18, #20, #21, #25, the §10a TODOs in -> ddev-sandbox.md) only surfaced on productive WSL installs because both -> existing suites exercise ddev through the `fake-ddev` stub — kit plumbing -> only, never ddev itself. The core enabler is a **golden-image cache**: the -> provisioned rootless daemon + ddev + pulled images are snapshotted once via -> `docker commit`, so repeat runs skip the 3–4+ minute download/install phase. -> Where wording differs from the code, the code wins. +> Status: **IMPLEMENTED — shipped with 0.0.21.** `make e2e-ddev` / +> `make e2e-ddev-fresh` run `tests/e2e/run-ddev.sh` (68 checks, full +> DD0–DD14 catalog incl. the camino real-site tier and the bare-origin +> git tier; manual CI workflow since 2026-08-22, first green GitHub run +> 2026-08-23 — §8). The text below is the original design record; where +> wording differs from the code, the code wins. Two deltas since the +> plan: the golden image moved from the §4.2 `docker commit` flow to a +> **save/load tar** flow (the inner docker store on the container +> filesystem produced whiteouts that `docker commit` dropped — golden +> image v2 keeps no inner store and loads images from a tar on boot), +> and the weekly CI `schedule` (§8) is still pending — the workflow is +> `workflow_dispatch`-only. +> +> Original framing, kept for context: a third e2e suite answering the +> recurring burn-in problem — most ddev issues (#18, #20, #21, #25, the +> §10a TODOs in ddev-sandbox.md) only surfaced on productive WSL +> installs because both existing suites exercise ddev through the +> `fake-ddev` stub — kit plumbing only, never ddev itself. The core +> enabler is a **golden-image cache**: the provisioned rootless daemon + +> ddev + pulled images are snapshotted once, so repeat runs skip the +> 3–4+ minute download/install phase. ## 1. Problem & goal @@ -248,8 +259,8 @@ re-done per run: `trustedHostsPattern` in `config/system/settings.php`, to the burn-in commits except three documented deviations: `name:` line dropped from `.ddev/config.yaml`, `!/public/index.php` gitignore exception so clones boot, dump as sidecar `db.sql.gz`) plus the fixture -README with the regeneration recipe. Phase 2b fixture intake is done; -the golden-image warm-up integration remains. +README with the regeneration recipe. Phase 2b complete — DD10/DD11 ship +in the runner (shipped 0.0.21). Location: `tests/e2e/fixtures/camino/` in the repo (shipped — installs stream from the same repo, and CI needs the files). If the dump ever @@ -283,7 +294,8 @@ another baked artifact and keeps branch content in reviewable script form — the maintainer's real project bare repo is the template for what the branches must contain). **Status: generator implemented and verified** (2026-08-22; ShellCheck-clean, clone + all branch switches tested against -the harvested camino fixture) — reconciled with the real burn-in bare repo: +the harvested camino fixture; DD12 shipped 0.0.21) — reconciled with the +real burn-in bare repo: the tracked set there is `.ddev/config.yaml` + the composer/TYPO3 tree (11 files, no AGENTS.md), so `feature/top-level` works on README.md/LICENSE instead. Branch layout: @@ -414,15 +426,17 @@ bare-origin flow (DD12). ## 9. Rollout & effort +All phases shipped with 0.0.21; estimates below are the original plan. + | Phase | Content | Estimate | |---|---|---| -| 1 | runner skeleton + golden-image build/quiesce/commit/boot (§4) + DD0–DD2 | 1–2 d | -| 2 | full catalog DD3–DD9 + `--fresh`/TTL knobs | 1 d | -| 2b | real-site tier: fixture intake (**done 2026-08-22** — `tests/e2e/fixtures/camino/` harvested), remaining: warm-up integration + DD10–DD11 | 1 d | -| 2c | git-flow tier: `make-bare-origin.sh` generator (**done 2026-08-22**, verified) + DD12 | 0.5 d | -| 2d | burn-in findings §7.3: DD13 (config-on-empty-dir tripwires) + DD14 (create-project) — DD13/DD14 need no site fixture, only ddev + composer | 0.5 d | -| 3 | Makefile/CI workflow + test-workflows + docs | 0.5–1 d | -| 4 | burn-in on this workspace (rootful + rootless outer layouts), matrix knob `DDEV_VERSION` for new ddev releases | 0.5 d | +| 1 ✅ | runner skeleton + golden-image build/quiesce/commit/boot (§4) + DD0–DD2 | 1–2 d | +| 2 ✅ | full catalog DD3–DD9 + `--fresh`/TTL knobs | 1 d | +| 2b ✅ | real-site tier: fixture intake (`tests/e2e/fixtures/camino/`, harvested 2026-08-22), warm-up integration + DD10–DD11 | 1 d | +| 2c ✅ | git-flow tier: `make-bare-origin.sh` generator + DD12 | 0.5 d | +| 2d ✅ | burn-in findings §7.3: DD13 + DD14 (tripwire pairs) | 0.5 d | +| 3 ✅ | Makefile/CI workflow + test-workflows + docs | 0.5–1 d | +| 4 ✅ | burn-in on this workspace (rootful + rootless outer layouts), matrix knob `DDEV_VERSION` | 0.5 d | Expected wall times (workspace, rootless outer daemon): first build 5–12 min (image pulls dominate; real-site warm-up adds `composer install` + import, diff --git a/docs/design/project-history-summary.md b/docs/design/project-history-summary.md index 1e6d229..cbbbeb5 100644 --- a/docs/design/project-history-summary.md +++ b/docs/design/project-history-summary.md @@ -20,6 +20,9 @@ the linked design docs and commit history. | 0.0.16 | Third-party UIs (OpenChamber) spawn `opencode serve` non-interactively — banner + `Press Enter` + `[Y/n]` broke the managed startup, and sudo's env reset silently dropped `OPENCODE_SERVER_PASSWORD` | **Headless serve mode** in the wrapper (`serve` subcommand: no banner/prompts, diagnostics on stderr, no project-dir check, silent container attach); sudoers `env_keep` adds `OPENCODE_SERVER_PASSWORD` | | 0.0.17 | Project review findings: predictable `/tmp` backup path (root writes), incomplete project-path blocklist (`/var/log` etc.), TOCTOU on the get.docker.com download, wrapper died on EOF stdin, status.sh crashed on unknown backend under `set -u`, flags after `--projects` were silently dropped, `~` broke under `sudo bash` | **Hardening + quality pass**: mktemp for backup dir + downloads, `/var/cache\|lib\|log\|mail\|spool` blocked, EOF-safe wrapper reads, `sudo -n` socket probes; install flags in any order with fail-fast validation; `PROJECT_TILDE_HOME` for tilde expansion; ShellCheck gate (`make lint`) + contributor pre-flight (`check-host.sh`) + drift/canary guards in CI; exec bits tracked in git; uninstall/status unit suites; verify.sh retired | | 0.0.18 → 0.0.19 | First field reports from installed machines (issues #17–#21, #14): agent git failed with "dubious ownership" and could not read `.git` (pruned from every baseline); vendor scripts (`vendor/bin/runTests.sh`) bypassed the `ddev()` shell function → ddev ran as the developer → EPERM on `.ddev/`; `ddev launch`/`mailpit`/`phpmyadmin` died on WSL interop (and a dev-side run restarted the project on every launch); the status hosts scan reported vendored `.ddev` dirs and useless `*.ddev.site` names; the developer's `~/.agents`/`~/.claude` skills never reached the opencode home; group baseline over 500+ repos ran minutes in silence | **Field-feedback batch**: `.git` in the group baseline + `safe.directory '*'` for opencode (agent git works, #17); `ddev()` exported + `BASH_ENV` second transport so bash children — even behind `#!/bin/sh` wrappers — run ddev as opencode (#18); browser-command class (`launch`, `mailpit`, `phpmyadmin`, `adminer`, `xhgui`, extensible via `ddev-browser-cmds.conf`) computes the URL as opencode with `DDEV_DEBUG` and opens it as the developer (#20); hosts bridge skips `vendor/`/`node_modules/` and `*.ddev.site`, per-hostname add commands (#21); install migrates `~/.agents` + `~/.claude` into the opencode home (move/copy/skip, #19); shared `fs-baseline.sh` with live per-pass progress counters (#14) | +| 0.0.20 | ddev's settings management re-chmod'ed team projects on every start — the #25 pain persisted as handover churn; TYPO3 `ext_localconf.php` was caught by the broad `*conf.php` deny (#23); `update.sh --only-binary` deleted its own candidate before verification (#24) | **Dev-owned mode** (design: [ddev-dev-owned-projects.md](ddev-dev-owned-projects.md)): kit writes `disable_settings_management: true` into `.ddev/config.yaml` — settings dirs + project root permanently developer-owned (2775/664), no handover churn; TYPO3 carve-out for `ext_localconf.php`; binary-update fixes + `upgrade-opencode` shorthand (#24); least-privilege CI token | +| 0.0.21 | Both e2e suites exercised kit plumbing only (`fake-ddev` stub / raw docker probes) — ddev bugs (#18/#20/#21/#25 classes) surfaced only on real WSL installs; the kit's state was invisible inside the TUI; the wrapper paused on Press-Enter + `[Y/n]` before every start; dev-owned flag hid behind ddev's comment wall, testdata `.ddev` fixtures tripped scans (#28, #29) | **Real-ddev e2e suite** (design: [ddev-e2e-test.md](ddev-e2e-test.md)): `make e2e-ddev` with golden-image cache, DD0–DD14 catalog, camino real-site tier, bare-origin git tier, manual CI workflow (first green run 2026-08-23); flag insertion at config head + testdata skip (#28, #29); sudoers keeps `OPENCODE_SERVER_USERNAME`; **TUI mode line** (kit version + backend state, red danger theme on bypass) + in-TUI bypass warning; wrapper prompt-free | +| 0.0.22 | Ecosystem tools spawning `opencode run`/`acp`/queries broke on the wrapper — banner polluted parsed stdout, project-dir refusal blocked worktree checkouts (#42); no visibility into root-equivalent surfaces beyond docker (#37); review finding: the #19 migration moved `~/.claude` WHOLE, so Claude Code credentials landed in the agent's group-readable home | **Headless wrapper mode** for `serve`/`run`/`acp`/queries (stdout machine-clean, no CWD check, silent container attach) + ecosystem [compatibility matrix](../reference/compatibility.md); **root-equivalent access audit** in `status.sh` (danger groups, `/mnt/wsl` desktop sockets, interop, sudo rules — report-only); skills-only `.claude` migration (`~/.agents` whole, `~/.claude/skills` only — credentials stay with the developer) | ## Retired / backup branches From 0a27cbb81ef66daf04216e10aab5495dec3e569c Mon Sep 17 00:00:00 2001 From: Steffen Maechtel Date: Mon, 24 Aug 2026 21:41:25 +0200 Subject: [PATCH 2/5] docs: introduce docs/_archive/ for superseded records MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move legacy/superseded documents out of the live tree (mirroring the subfolder structure — _archive/design/, _archive/security/): - design/: container-tools (implemented; how-to is the user doc), ddev-sandbox (superseded by ddev-working), docker-rootless (superseded in parts), improve-docs (docs overhaul landed), plan-ui-tui-opencode (implemented), ux-improvement (decided; conventions.md is living), ddev-as-user-issues (planning record; issues realized via e2e suite) - security/: proof-1/2/3 (analyses of the pre-soft-model era; security-model.md is current) All inbound links and code-comment pointers updated to the _archive paths (wrapper.md, conventions.md, ddev-working.md, ddev-e2e-test.md, install.sh, update.sh, wrapper, ui.sh, setup-container-backend.sh, kit-mode.tsx, tests incl. tests/ux). docs/README.md and AGENTS.md describe the new layout; _archive/README.md states the convention. Link check, ShellCheck, and the full unit suite pass. --- AGENTS.md | 2 +- docs/README.md | 12 +++++++----- docs/_archive/README.md | 7 +++++++ docs/{ => _archive}/design/container-tools.md | 0 docs/{ => _archive}/design/ddev-as-user-issues.md | 0 docs/{ => _archive}/design/ddev-sandbox.md | 0 docs/{ => _archive}/design/docker-rootless.md | 0 docs/{ => _archive}/design/improve-docs.md | 0 docs/{ => _archive}/design/plan-ui-tui-opencode.md | 0 docs/{ => _archive}/design/ux-improvement.md | 0 docs/{ => _archive}/security/proof-1.md | 0 docs/{ => _archive}/security/proof-2.md | 0 docs/{ => _archive}/security/proof-3.md | 0 docs/concepts/wrapper.md | 4 +++- docs/design/conventions.md | 7 ++++--- docs/design/ddev-e2e-test.md | 7 ++++--- docs/design/ddev-working.md | 14 ++++++++------ files/install.sh | 4 ++-- .../setup-container-backend.sh | 2 +- .../opencode-permissions-kit-lib/tui/kit-mode.tsx | 2 +- files/opencode-permissions-kit-lib/ui.sh | 2 +- files/opencode-permissions-kit-lib/wrapper | 2 +- files/update.sh | 4 ++-- tests/e2e/run-docker-rootless.sh | 2 +- tests/e2e/run.sh | 2 +- tests/test-tui-mode.sh | 2 +- tests/ux/README.md | 2 +- tests/ux/example-install-advanced.sh | 2 +- tests/ux/example-install-standard.sh | 2 +- tests/ux/example-status.sh | 2 +- tests/ux/example-styles.sh | 2 +- tests/ux/example-update.sh | 2 +- tests/ux/lib/ux.sh | 2 +- 33 files changed, 52 insertions(+), 37 deletions(-) create mode 100644 docs/_archive/README.md rename docs/{ => _archive}/design/container-tools.md (100%) rename docs/{ => _archive}/design/ddev-as-user-issues.md (100%) rename docs/{ => _archive}/design/ddev-sandbox.md (100%) rename docs/{ => _archive}/design/docker-rootless.md (100%) rename docs/{ => _archive}/design/improve-docs.md (100%) rename docs/{ => _archive}/design/plan-ui-tui-opencode.md (100%) rename docs/{ => _archive}/design/ux-improvement.md (100%) rename docs/{ => _archive}/security/proof-1.md (100%) rename docs/{ => _archive}/security/proof-2.md (100%) rename docs/{ => _archive}/security/proof-3.md (100%) diff --git a/AGENTS.md b/AGENTS.md index ef001e7..174ff6b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,7 +25,7 @@ self-fetches its siblings from the same branch, and everything is deployed to - `tests/` — shell unit tests (`test-*.sh`), Docker e2e suites (`e2e/`), UX demos (`ux/`) - `docs/` — user documentation (concepts / how-to / reference), design - records (`design/`), security analyses (`security/`) + records (`design/`), superseded records (`_archive/`) ## Rules diff --git a/docs/README.md b/docs/README.md index d0ab2e1..22b2bfe 100644 --- a/docs/README.md +++ b/docs/README.md @@ -47,8 +47,10 @@ with ddev — see the [README](../README.md) whether the kit fits your setup. ## Design records (internal) -`design/` and `security/` hold the historical planning and analysis records. -They are not user documentation — where wording differs from the code, the -code wins. One exception is living, not historical: -[conventions.md](design/conventions.md) — the binding style guide for -shipped code (prompts, output, language). +`design/` holds planning and analysis records for CURRENT behavior — +where wording differs from the code, the code wins. Superseded or purely +historical records live in [`_archive/`](_archive/) (same subfolder +structure: `_archive/design/`, `_archive/security/`) — they document how +the kit got here, not how it works today. One exception is living, not +historical: [conventions.md](design/conventions.md) — the binding style +guide for shipped code (prompts, output, language). diff --git a/docs/_archive/README.md b/docs/_archive/README.md new file mode 100644 index 0000000..e94258d --- /dev/null +++ b/docs/_archive/README.md @@ -0,0 +1,7 @@ +# Archive + +Superseded or purely historical documents — they record how the kit got +here, not how it works today. The subfolder structure mirrors the live +tree at the time of archiving (`_archive/design/`, `_archive/security/`). +For current behavior see the [docs index](../README.md); where wording +differs from the code, the code wins. diff --git a/docs/design/container-tools.md b/docs/_archive/design/container-tools.md similarity index 100% rename from docs/design/container-tools.md rename to docs/_archive/design/container-tools.md diff --git a/docs/design/ddev-as-user-issues.md b/docs/_archive/design/ddev-as-user-issues.md similarity index 100% rename from docs/design/ddev-as-user-issues.md rename to docs/_archive/design/ddev-as-user-issues.md diff --git a/docs/design/ddev-sandbox.md b/docs/_archive/design/ddev-sandbox.md similarity index 100% rename from docs/design/ddev-sandbox.md rename to docs/_archive/design/ddev-sandbox.md diff --git a/docs/design/docker-rootless.md b/docs/_archive/design/docker-rootless.md similarity index 100% rename from docs/design/docker-rootless.md rename to docs/_archive/design/docker-rootless.md diff --git a/docs/design/improve-docs.md b/docs/_archive/design/improve-docs.md similarity index 100% rename from docs/design/improve-docs.md rename to docs/_archive/design/improve-docs.md diff --git a/docs/design/plan-ui-tui-opencode.md b/docs/_archive/design/plan-ui-tui-opencode.md similarity index 100% rename from docs/design/plan-ui-tui-opencode.md rename to docs/_archive/design/plan-ui-tui-opencode.md diff --git a/docs/design/ux-improvement.md b/docs/_archive/design/ux-improvement.md similarity index 100% rename from docs/design/ux-improvement.md rename to docs/_archive/design/ux-improvement.md diff --git a/docs/security/proof-1.md b/docs/_archive/security/proof-1.md similarity index 100% rename from docs/security/proof-1.md rename to docs/_archive/security/proof-1.md diff --git a/docs/security/proof-2.md b/docs/_archive/security/proof-2.md similarity index 100% rename from docs/security/proof-2.md rename to docs/_archive/security/proof-2.md diff --git a/docs/security/proof-3.md b/docs/_archive/security/proof-3.md similarity index 100% rename from docs/security/proof-3.md rename to docs/_archive/security/proof-3.md diff --git a/docs/concepts/wrapper.md b/docs/concepts/wrapper.md index e356855..0bfeaac 100644 --- a/docs/concepts/wrapper.md +++ b/docs/concepts/wrapper.md @@ -137,7 +137,9 @@ you manage your own `~/.config/opencode/tui.json` (for the opencode user), the kit leaves it alone — the file is only written when absent or previously kit-written (marker key `_opencode_permissions_kit`). -Design background: [plan-ui-tui-opencode](../design/plan-ui-tui-opencode.md). +Design background: +[plan-ui-tui-opencode](../_archive/design/plan-ui-tui-opencode.md) +(archived). ## Wrapper-bypass guard (detect, then warn loudly) diff --git a/docs/design/conventions.md b/docs/design/conventions.md index fda5496..68a2fda 100644 --- a/docs/design/conventions.md +++ b/docs/design/conventions.md @@ -39,9 +39,10 @@ purpose, they are not confirmations. ## Script output style -See `docs/design/ux-improvement.md` (decisions): labeled lines -`info`/`success`/`warn`/`error` via `ui.sh`, slim banner, Unicode symbols -with `UI_ASCII=1` fallback, `NO_COLOR` and non-tty honored. +See `docs/_archive/design/ux-improvement.md` (decisions, archived): +labeled lines `info`/`success`/`warn`/`error` via `ui.sh`, slim banner, +Unicode symbols with `UI_ASCII=1` fallback, `NO_COLOR` and non-tty +honored. ## Language diff --git a/docs/design/ddev-e2e-test.md b/docs/design/ddev-e2e-test.md index 92b1ad7..0a49e35 100644 --- a/docs/design/ddev-e2e-test.md +++ b/docs/design/ddev-e2e-test.md @@ -15,7 +15,8 @@ > > Original framing, kept for context: a third e2e suite answering the > recurring burn-in problem — most ddev issues (#18, #20, #21, #25, the -> §10a TODOs in ddev-sandbox.md) only surfaced on productive WSL +> §10a TODOs in ddev-sandbox.md, archived under +> `docs/_archive/design/`) only surfaced on productive WSL > installs because both existing suites exercise ddev through the > `fake-ddev` stub — kit plumbing only, never ddev itself. The core > enabler is a **golden-image cache**: the provisioned rootless daemon + @@ -29,7 +30,7 @@ | `make e2e` (run.sh, 250 checks) | ddev is `tests/e2e/fake-ddev` — a logging stub. Nothing of ddev itself runs: no containers, no mutagen, no chmod behavior, no router. | | `make e2e-rootless` (run-docker-rootless.sh, 54 checks) | REAL inner docker-rootless daemon (systemd container), but ddev is never installed — only raw `docker` probes (alpine). | -Every issue class from `ddev-as-user-issues.md` §1.1 — settings-dir chmod +Every issue class from `ddev-as-user-issues.md` §1.1 (archived: `docs/_archive/design/`) — settings-dir chmod (`#25`), the `ddev()` function transports (`#18`), describe-state mismatches, mutagen, first-start behavior — needs real ddev to reproduce. Goal: @@ -184,7 +185,7 @@ Same idioms as `e2e_resolve_cache` (lib.sh): resolve, compare, fall back. ## 7. Test catalog (DD sections) -Mapped to the documented issue classes (ddev-as-user-issues.md §1.1) — each +Mapped to the documented issue classes (ddev-as-user-issues.md §1.1, archived: docs/_archive/design/) — each row is the regression net for a real burn-in finding: | Section | Checks | Issue class | diff --git a/docs/design/ddev-working.md b/docs/design/ddev-working.md index 4fcb0da..49fbbff 100644 --- a/docs/design/ddev-working.md +++ b/docs/design/ddev-working.md @@ -253,11 +253,11 @@ stays). `DEFAULT_USER` stays auto-detected (`SUDO_USER`), never asked. model (soft-only)" section with the trade-offs from §2.1; updated install question walkthrough; migration instructions. - `README.md`: trim feature list to the four goals in §2. -- `docs/design/docker-rootless.md`: rationale section rewritten (UID - separation, not ACL-holding). -- `docs/design/ddev-sandbox.md` + `docs/security/PROOF-1/2/3.md`: add a - status banner "superseded by ddev-working.md (hard deny layer removed)"; - keep as historical records. +- `docs/_archive/design/docker-rootless.md` (archived): rationale section + rewritten (UID separation, not ACL-holding). +- `docs/_archive/design/ddev-sandbox.md` + + `docs/_archive/security/PROOF-1/2/3.md` (archived): superseded by + ddev-working.md (hard deny layer removed); kept as historical records. - `AGENTS.md`: ddev-shim section, self-block section, and audit-log wording ("documents the restrictions applied against it") all become obsolete — rewrite after implementation lands. @@ -314,7 +314,9 @@ Failed to start pc-database-v2: chmod /var/www/vhosts/pc-database-v2/.ddev/.webi Linux `chmod` requires ownership (or root), so the group-shared `.ddev` metadata cannot be written. The wrapper's `sudo -u opencode` has no passwordless root path. This was the collision inventory in -ddev-sandbox.md:56, now realized in the soft-only world. +ddev-sandbox.md:56 (archived: +`docs/_archive/design/ddev-sandbox.md`), now realized in the soft-only +world. **Decision: one owner for ddev, everywhere.** ddev runs as `opencode` in EVERY context — the agent natively, and the developer's terminal through a diff --git a/files/install.sh b/files/install.sh index c2abc2f..1c92f84 100755 --- a/files/install.sh +++ b/files/install.sh @@ -1240,7 +1240,7 @@ sudo cp "$SCRIPT_DIR/opencode-permissions-kit-lib/ddev-handover.sh" "$LIBDIR/dde sudo cp "$SCRIPT_DIR/opencode-permissions-kit-lib/ddev-migrate.sh" "$LIBDIR/ddev-migrate.sh" sudo cp "$SCRIPT_DIR/opencode-permissions-kit-lib/fs-baseline.sh" "$LIBDIR/fs-baseline.sh" sudo cp "$SCRIPT_DIR/opencode-permissions-kit-lib/ddev-hosts.sh" "$LIBDIR/ddev-hosts.sh" -# TUI mode display (docs/design/plan-ui-tui-opencode.md): plugin + templates +# TUI mode display (docs/_archive/design/plan-ui-tui-opencode.md): plugin + templates sudo mkdir -p "$LIBDIR/tui" sudo cp "$SCRIPT_DIR/opencode-permissions-kit-lib/tui/kit-mode.tsx" "$LIBDIR/tui/kit-mode.tsx" sudo cp "$SCRIPT_DIR/opencode-permissions-kit-lib/tui/opencode-danger.theme.json" "$LIBDIR/tui/opencode-danger.theme.json" @@ -1475,7 +1475,7 @@ if [ ! -f "$DEFAULT_OC_CONF" ]; then log "deny-all config installed for default user: $DEFAULT_OC_CONF" fi -# === Step 8c: TUI mode display (docs/design/plan-ui-tui-opencode.md) ============ +# === Step 8c: TUI mode display (docs/_archive/design/plan-ui-tui-opencode.md) ============ # Two HOME-keyed effects, no env transport (sudo's env_reset rewrites # HOME, the TUI loads its global config from $HOME): # opencode user -> kit tui.json registers the mode plugin (text row diff --git a/files/opencode-permissions-kit-lib/setup-container-backend.sh b/files/opencode-permissions-kit-lib/setup-container-backend.sh index ffb29b0..a2dacd4 100755 --- a/files/opencode-permissions-kit-lib/setup-container-backend.sh +++ b/files/opencode-permissions-kit-lib/setup-container-backend.sh @@ -23,7 +23,7 @@ # install.conf. For podman-rootless daemonless, nothing is printed. # # Must run as root. The opencode user must already exist. -# See docs/design/docker-rootless.md §6.4, §6.6, §9.3. +# See docs/_archive/design/docker-rootless.md §6.4, §6.6, §9.3. set -e GREEN='\033[0;32m' diff --git a/files/opencode-permissions-kit-lib/tui/kit-mode.tsx b/files/opencode-permissions-kit-lib/tui/kit-mode.tsx index c299883..8bf6fd2 100644 --- a/files/opencode-permissions-kit-lib/tui/kit-mode.tsx +++ b/files/opencode-permissions-kit-lib/tui/kit-mode.tsx @@ -1,5 +1,5 @@ /** @jsxImportSource @opentui/solid */ -// opencode permissions kit -- TUI mode display (docs/design/plan-ui-tui-opencode.md) +// opencode permissions kit -- TUI mode display (docs/_archive/design/plan-ui-tui-opencode.md) // // Renders the kit's container-tools state in the app_bottom slot // (append mode: own thin row, visible on home + session screens): diff --git a/files/opencode-permissions-kit-lib/ui.sh b/files/opencode-permissions-kit-lib/ui.sh index 217c981..7bae570 100755 --- a/files/opencode-permissions-kit-lib/ui.sh +++ b/files/opencode-permissions-kit-lib/ui.sh @@ -3,7 +3,7 @@ # # One visual language for every kit script (install/update/config/status). # POSIX sh, zero dependencies. Style decisions are recorded in -# docs/design/ux-improvement.md: labeled log lines (info/success/warn/error), +# docs/_archive/design/ux-improvement.md: labeled log lines (info/success/warn/error), # slim banner, Unicode symbols with ASCII fallback. # # Rules: diff --git a/files/opencode-permissions-kit-lib/wrapper b/files/opencode-permissions-kit-lib/wrapper index 18338bb..4321d9e 100755 --- a/files/opencode-permissions-kit-lib/wrapper +++ b/files/opencode-permissions-kit-lib/wrapper @@ -306,7 +306,7 @@ fi # No prompts, no pause (since 0.0.21): the banner prints, opencode # starts immediately. The [Y/n] container question was always answered # with yes in practice, and the kit's state is now visible INSIDE the -# TUI (mode row in app_bottom, docs/design/plan-ui-tui-opencode.md) — +# TUI (mode row in app_bottom, docs/_archive/design/plan-ui-tui-opencode.md) — # a pause before the TUI would only delay that information. Headless # mode was never allowed to prompt (0.0.16 contract); its advisory goes # to stderr only. diff --git a/files/update.sh b/files/update.sh index c09b634..a1853db 100755 --- a/files/update.sh +++ b/files/update.sh @@ -313,7 +313,7 @@ sudo cp "$SCRIPT_DIR/opencode-permissions-kit-lib/ddev-handover.sh" "$LIBDIR/dde sudo cp "$SCRIPT_DIR/opencode-permissions-kit-lib/ddev-migrate.sh" "$LIBDIR/ddev-migrate.sh" sudo cp "$SCRIPT_DIR/opencode-permissions-kit-lib/fs-baseline.sh" "$LIBDIR/fs-baseline.sh" sudo cp "$SCRIPT_DIR/opencode-permissions-kit-lib/ddev-hosts.sh" "$LIBDIR/ddev-hosts.sh" -# TUI mode display (docs/design/plan-ui-tui-opencode.md): plugin + templates. +# TUI mode display (docs/_archive/design/plan-ui-tui-opencode.md): plugin + templates. sudo mkdir -p "$LIBDIR/tui" sudo cp "$SCRIPT_DIR/opencode-permissions-kit-lib/tui/kit-mode.tsx" "$LIBDIR/tui/kit-mode.tsx" sudo cp "$SCRIPT_DIR/opencode-permissions-kit-lib/tui/opencode-danger.theme.json" "$LIBDIR/tui/opencode-danger.theme.json" @@ -616,7 +616,7 @@ rm -f "$NEW_INSTALL_CONF" ui_success "install.conf updated: VERSION=$VERSION OPENCODE_GROUP=$NEW_OPENCODE_GROUP" log "install.conf updated: VERSION=$VERSION OPENCODE_GROUP=$NEW_OPENCODE_GROUP" -# --- TUI mode display user files (docs/design/plan-ui-tui-opencode.md) --------- +# --- TUI mode display user files (docs/_archive/design/plan-ui-tui-opencode.md) --------- # Same only-if-absent-or-kit-written policy as install.sh (marker key # _opencode_permissions_kit): user edits survive updates. OC_TUI_DIR="/home/$OPENCODE_USER/.config/opencode" diff --git a/tests/e2e/run-docker-rootless.sh b/tests/e2e/run-docker-rootless.sh index c31788f..1251dbf 100755 --- a/tests/e2e/run-docker-rootless.sh +++ b/tests/e2e/run-docker-rootless.sh @@ -2,7 +2,7 @@ # e2e/run-docker-rootless.sh — docker-rootless daemon end-to-end test # Run from repo root: ./tests/e2e/run-docker-rootless.sh # -# Phase 3 of docs/design/docker-rootless.md. Unlike the podman-rootless suite +# Phase 3 of docs/_archive/design/docker-rootless.md. Unlike the podman-rootless suite # (run.sh section 12i), docker-rootless cannot run in the plain e2e container: # the kit's provisioning (setup-container-backend.sh) hard-requires a working # systemd --user manager (dockerd-rootless-setuptool.sh + `systemctl --user`). diff --git a/tests/e2e/run.sh b/tests/e2e/run.sh index 61b2784..d53ce2e 100755 --- a/tests/e2e/run.sh +++ b/tests/e2e/run.sh @@ -755,7 +755,7 @@ check_fail "opencode user cannot enter log dir" \ echo "" echo "--- 12i. Rootless container backend (podman) ---" -# Real-rootless environment test (docs/design/docker-rootless.md). In the +# Real-rootless environment test (docs/_archive/design/docker-rootless.md). In the # soft-only model the §9.1 proof flips: containers run as the opencode host # UID and CAN read the project files (the ddev-working goal) — but the uid_map # still proves they are NOT real root. diff --git a/tests/test-tui-mode.sh b/tests/test-tui-mode.sh index f8829e5..30aa79d 100755 --- a/tests/test-tui-mode.sh +++ b/tests/test-tui-mode.sh @@ -1,6 +1,6 @@ #!/bin/sh # Unit tests for the TUI mode display payload -# (docs/design/plan-ui-tui-opencode.md — option A' + B, spike-validated +# (docs/_archive/design/plan-ui-tui-opencode.md — option A' + B, spike-validated # 2026-08-23): # - files/opencode-permissions-kit-lib/tui/ contains the plugin # (kit-mode.tsx), the danger theme, and both tui.json templates diff --git a/tests/ux/README.md b/tests/ux/README.md index c3a99fe..160dd32 100644 --- a/tests/ux/README.md +++ b/tests/ux/README.md @@ -1,7 +1,7 @@ # tests/ux/ — UX playground (DEMO ONLY) Experimental preview of the proposed output styles and flows from -[docs/design/ux-improvement.md](../docs/design/ux-improvement.md). +[docs/_archive/design/ux-improvement.md](../docs/_archive/design/ux-improvement.md). **Nothing here changes anything on your system** — every script only prints simulated output (with short sleeps for realism). diff --git a/tests/ux/example-install-advanced.sh b/tests/ux/example-install-advanced.sh index beb9c60..c66e8ca 100644 --- a/tests/ux/example-install-advanced.sh +++ b/tests/ux/example-install-advanced.sh @@ -1,5 +1,5 @@ #!/bin/sh -# DEMO — proposed ADVANCED install flow (docs/design/ux-improvement.md §4.4). +# DEMO — proposed ADVANCED install flow (docs/_archive/design/ux-improvement.md §4.4). # Includes the docker-classic warning variant for the §5 discussion. # Simulated output only; nothing is executed. Run: sh tests/ux/example-install-advanced.sh set -u diff --git a/tests/ux/example-install-standard.sh b/tests/ux/example-install-standard.sh index 0df77c5..1b15e7d 100644 --- a/tests/ux/example-install-standard.sh +++ b/tests/ux/example-install-standard.sh @@ -1,5 +1,5 @@ #!/bin/sh -# DEMO — proposed STANDARD install flow (docs/design/ux-improvement.md §4.3). +# DEMO — proposed STANDARD install flow (docs/_archive/design/ux-improvement.md §4.3). # Simulated output only; nothing is executed. Run: sh tests/ux/example-install-standard.sh set -u . "$(dirname "$0")/lib/ux.sh" diff --git a/tests/ux/example-status.sh b/tests/ux/example-status.sh index fa5ce66..c88c956 100644 --- a/tests/ux/example-status.sh +++ b/tests/ux/example-status.sh @@ -1,5 +1,5 @@ #!/bin/sh -# DEMO — proposed status.sh output (docs/design/ux-improvement.md §6). +# DEMO — proposed status.sh output (docs/_archive/design/ux-improvement.md §6). # Simulated output only; nothing is executed. Run: sh tests/ux/example-status.sh set -u . "$(dirname "$0")/lib/ux.sh" diff --git a/tests/ux/example-styles.sh b/tests/ux/example-styles.sh index 18dadf1..6145e93 100644 --- a/tests/ux/example-styles.sh +++ b/tests/ux/example-styles.sh @@ -1,5 +1,5 @@ #!/bin/sh -# DEMO — style variants side by side (docs/design/ux-improvement.md §3). +# DEMO — style variants side by side (docs/_archive/design/ux-improvement.md §3). # Pick what you like; nothing is executed. Run: sh tests/ux/example-styles.sh set -u . "$(dirname "$0")/lib/ux.sh" diff --git a/tests/ux/example-update.sh b/tests/ux/example-update.sh index 4988611..ff62c70 100644 --- a/tests/ux/example-update.sh +++ b/tests/ux/example-update.sh @@ -1,5 +1,5 @@ #!/bin/sh -# DEMO — proposed update.sh output (docs/design/ux-improvement.md §6). +# DEMO — proposed update.sh output (docs/_archive/design/ux-improvement.md §6). # Simulated output only; nothing is executed. Run: sh tests/ux/example-update.sh set -u . "$(dirname "$0")/lib/ux.sh" diff --git a/tests/ux/lib/ux.sh b/tests/ux/lib/ux.sh index 9a0103f..6a4bc73 100644 --- a/tests/ux/lib/ux.sh +++ b/tests/ux/lib/ux.sh @@ -1,7 +1,7 @@ # opencode permissions kit — DEMO UI helpers (tests/ux/ playground) # # Candidate for files/opencode-permissions-kit-lib/ui.sh (see -# docs/design/ux-improvement.md). POSIX sh, zero dependencies. +# docs/_archive/design/ux-improvement.md). POSIX sh, zero dependencies. # # Rules: # - Colors off when NO_COLOR is set or stdout is not a tty. From 2aedfb3d1fbecc1d3fc3a5dd43cfe3d428e3e3ff Mon Sep 17 00:00:00 2001 From: Steffen Maechtel Date: Mon, 24 Aug 2026 21:43:27 +0200 Subject: [PATCH 3/5] =?UTF-8?q?docs(design):=20rootless-backend.md=20?= =?UTF-8?q?=E2=80=94=20carry=20the=20current=20parts=20out=20of=20the=20ar?= =?UTF-8?q?chive?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docker-rootless.md was archived wholesale, but its current sections (selection defaults, provisioning reference incl. subuid/subgid allocation, host impact, podman-CLI path, ddev gate) are live design reference the code points at. New docs/design/rootless-backend.md holds them, rewritten for the soft-only model (ACL rationale dropped, e2e proofs referenced). setup-container-backend.sh + both e2e runners now point at the living doc instead of _archive; docs/README.md lists it. Archived original stays untouched as history. --- docs/README.md | 14 +- docs/design/rootless-backend.md | 143 ++++++++++++++++++ .../setup-container-backend.sh | 2 +- tests/e2e/run-docker-rootless.sh | 2 +- tests/e2e/run.sh | 2 +- 5 files changed, 154 insertions(+), 9 deletions(-) create mode 100644 docs/design/rootless-backend.md diff --git a/docs/README.md b/docs/README.md index 22b2bfe..be49bd1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -48,9 +48,11 @@ with ddev — see the [README](../README.md) whether the kit fits your setup. ## Design records (internal) `design/` holds planning and analysis records for CURRENT behavior — -where wording differs from the code, the code wins. Superseded or purely -historical records live in [`_archive/`](_archive/) (same subfolder -structure: `_archive/design/`, `_archive/security/`) — they document how -the kit got here, not how it works today. One exception is living, not -historical: [conventions.md](design/conventions.md) — the binding style -guide for shipped code (prompts, output, language). +where wording differs from the code, the code wins. Notable living +references: [conventions.md](design/conventions.md) — the binding style +guide for shipped code (prompts, output, language) — and +[rootless-backend.md](design/rootless-backend.md) — the provisioning and +selection reference for the rootless container backends. Superseded or +purely historical records live in [`_archive/`](_archive/) (same +subfolder structure: `_archive/design/`, `_archive/security/`) — they +document how the kit got here, not how it works today. diff --git a/docs/design/rootless-backend.md b/docs/design/rootless-backend.md new file mode 100644 index 0000000..54ca405 --- /dev/null +++ b/docs/design/rootless-backend.md @@ -0,0 +1,143 @@ +# Rootless container backends — design reference + +> Status: **CURRENT (reference).** How the kit provisions and runs its +> rootless container backends (docker-rootless, podman-rootless) for the +> `opencode` user. Successor of the current sections of the archived +> `docs/_archive/design/docker-rootless.md` (historical plan, ACL-era +> rationale); usage docs: [switch the container +> backend](../how-to/switch-container-backend.md); the security +> guarantee: [security model](../concepts/security-model.md). + +## 1. Why rootless, and why per-user + +Containers must never give the agent a root-equivalent path: whoever can +talk to a docker socket can escalate to root on the host (a container +with `-v /:/host` reads and rewrites everything). Rootless runtimes run +containers in a **user namespace** — the container's root maps to an +unprivileged host UID via the daemon user's `/etc/subuid`//`/etc/subgid` +ranges, and the daemon itself runs unprivileged. + +The kit-specific payoff: **the backend is owned by the `opencode` +user**, so everything a container does on the host happens as the +`opencode` host UID — exactly what the agent itself could do, no more. +Pointing the agent at the *developer's* rootless socket would only +remove the root step, not the read-everything step; the legacy +`docker-group` backend (root-equivalent socket) is removed entirely. + +In the soft-only model this is deliberately *not* a file-protection +mechanism: bind-mounted containers run as the `opencode` host UID and +can read every project file (ddev's web container needs that — +`settings.php`, `.env`). The guarantee is UID separation only; proven by +e2e: podman in `tests/e2e/run.sh` §12i, real dockerd in +`tests/e2e/run-docker-rootless.sh` (RL4 asserts the container uid_map +maps root to the opencode host UID). + +| | docker-rootless | podman-rootless | +|---|---|---| +| Daemon | per-user `dockerd`, socket `unix:///run/user//docker.sock` | none (daemonless CLI); optional `podman.socket` | +| Lifecycle | `systemctl --user` + `loginctl enable-linger opencode` | on-demand | +| CLI the agent uses | `docker` (familiar semantics) | `podman` (no `docker` CLI parity — see §5) | +| Prerequisites | `uidmap`, `dbus-user-session`, `docker-ce-rootless-extras` | `uidmap`, `dbus-user-session`, `podman`, `slirp4netns` | + +ddev always runs as the `opencode` user against the SAME backend as the +agent (one daemon, one owner — see +[ddev-working](ddev-working.md)); the backend choice therefore only +governs which safe runtime the machine runs. + +## 2. Backend selection & defaults + +Decision flow at install time (auto-detect, priority order): + +1. **docker-rootless already active for `opencode`** → reuse it, no change. +2. **else podman available** (`command -v podman`) → podman-rootless + ("using your existing podman" — nothing new to install). +3. **else interactive choice**: `docker-rootless` (recommended), + `podman-rootless`, or none. The legacy `docker-group` is no longer + offered — the wrapper never falls back to a root-equivalent path. + +**Default: docker-rootless.** The target audience (DDEV/WSL2) +overwhelmingly runs Docker, the kit's permission rules speak `docker *`, +and docker-rootless keeps that CLI behind a safer socket. +podman-rootless is the right choice when the developer already runs +podman. + +**Non-interactive (`--yes`) safety:** provisioning installs packages and +edits `/etc/subuid` — never silently. `--yes` without an explicit +`--container-backend` asks nothing and provisions nothing; scripts pass +`--container-backend docker-rootless|podman-rootless` explicitly. + +Post-install switch: `opencode-permissions-kit config container-backend +` (reversible, re-provisions via §3). + +## 3. Provisioning reference (`setup-container-backend.sh`) + +Runs as root, as the `opencode` user where noted. Idempotent — every +step is a no-op when already in place. + +1. **Packages** (only if missing, via apt): `uidmap`, + `dbus-user-session`, plus `docker-ce-rootless-extras` (docker) or + `podman` + `slirp4netns` (podman). `docker-ce-rootless-extras` is the + only Docker-related package ever added — it ships + `dockerd-rootless-setuptool.sh`, not a second Docker. +2. **subuid/subgid auto-allocation:** a free contiguous range for + `opencode` in `/etc/subuid` + `/etc/subgid` — starting at 100000, + 65536 entries per user, skipping any overlap with existing entries, + never reusing another user's range. Existing entries for `opencode` + are kept as-is. +3. **docker-rootless only:** enable linger (starts the `systemd --user` + manager for a user that has no login session), then run + `dockerd-rootless-setuptool.sh install` **as `opencode`** (rootless + setup must never run as root), enable + start + `systemctl --user docker.service`. +4. **Router ports:** rootless containers cannot bind <1024 unless + `net.ipv4.ip_unprivileged_port_start <= 80` — applied before the + daemon starts (the netns inherits the value at start) via + `/etc/sysctl.d/99-ddev-rootless.conf` when needed, so a fresh install + is ddev-ready out of the box. +5. **Socket path** is printed for the caller to record in + `install.conf` (`OPENCODE_DOCKER_HOST`). podman-rootless prints + nothing (daemonless). + +The wrapper re-probes the socket on every start — from the `opencode` +user's own context via the `socket-check.sh` sudoers rule, because +`/run/user/` is mode 700 and a developer-running wrapper cannot +stat inside it. Unreachable socket → run WITHOUT container tools + loud +warning, never a downgrade to a root-equivalent path. + +## 4. Host impact — what the kit touches + +The kit does not reinstall or replace Docker and does not convert the +host to rootless. Rootless is per-user: opting in adds a backend for +`opencode` **alongside** whatever the developer runs. The developer's +docker (system daemon or Docker Desktop) is never stopped, disabled, +removed, or reconfigured. + +| What | docker-rootless | podman-rootless | +|---|---|---| +| Packages installed (only if missing) | `uidmap`, `dbus-user-session`, `docker-ce-rootless-extras` (via Docker's apt repo / get.docker.com when the distro doesn't ship it) | `uidmap`, `dbus-user-session`, `podman`, `slirp4netns` | +| System files | `/etc/subuid` + `/etc/subgid`: one range for `opencode` | same | +| Per-user artifacts | systemd `--user` unit, storage under `~opencode/.local/share/docker`, socket in `/run/user/` | storage under `~opencode/.local/share/containers` | +| Linger | `loginctl enable-linger opencode` | not needed (daemonless) | +| Developer's docker / images / containers | untouched | untouched | + +Tear-down on uninstall happens only for backends the kit created. + +## 5. The podman-CLI path (no docker shim) + +When podman is the backend, the agent uses `podman` — the kit never +installs a `docker` alias and never shadows a real `docker` binary. A +project's `docker *` permission rules therefore do NOT match on the +podman backend (documented asymmetry; use `podman *` rules). The +optional `OPENCODE_PODMAN_SOCKET` re-enables docker-CLI compatibility +(point a `docker` CLI at the podman socket); when set, the wrapper +verifies its reachability like the docker-rootless socket. + +## 6. ddev version gate and ports + +Docker Rootless and Podman require **ddev ≥ 1.25** (advisory, not an +install block — the agent's raw-container backend is independent of +ddev): install/update detect + record `DDEV_VERSION` in `install.conf` +(querying the real binary, never a PATH shim), `status.sh` flags a +< 1.25 ddev next to a rootless backend. Rootless router ports: either +the §3.4 sysctl (kit default) or ddev's documented high-port alternative +(`ddev config global --router-http-port 8080 --router-https-port 8443`). diff --git a/files/opencode-permissions-kit-lib/setup-container-backend.sh b/files/opencode-permissions-kit-lib/setup-container-backend.sh index a2dacd4..36f3f3a 100755 --- a/files/opencode-permissions-kit-lib/setup-container-backend.sh +++ b/files/opencode-permissions-kit-lib/setup-container-backend.sh @@ -23,7 +23,7 @@ # install.conf. For podman-rootless daemonless, nothing is printed. # # Must run as root. The opencode user must already exist. -# See docs/_archive/design/docker-rootless.md §6.4, §6.6, §9.3. +# See docs/design/rootless-backend.md (provisioning reference). set -e GREEN='\033[0;32m' diff --git a/tests/e2e/run-docker-rootless.sh b/tests/e2e/run-docker-rootless.sh index 1251dbf..a8b55e2 100755 --- a/tests/e2e/run-docker-rootless.sh +++ b/tests/e2e/run-docker-rootless.sh @@ -2,7 +2,7 @@ # e2e/run-docker-rootless.sh — docker-rootless daemon end-to-end test # Run from repo root: ./tests/e2e/run-docker-rootless.sh # -# Phase 3 of docs/_archive/design/docker-rootless.md. Unlike the podman-rootless suite +# Phase 3 of docs/design/rootless-backend.md. Unlike the podman-rootless suite # (run.sh section 12i), docker-rootless cannot run in the plain e2e container: # the kit's provisioning (setup-container-backend.sh) hard-requires a working # systemd --user manager (dockerd-rootless-setuptool.sh + `systemctl --user`). diff --git a/tests/e2e/run.sh b/tests/e2e/run.sh index d53ce2e..301279f 100755 --- a/tests/e2e/run.sh +++ b/tests/e2e/run.sh @@ -755,7 +755,7 @@ check_fail "opencode user cannot enter log dir" \ echo "" echo "--- 12i. Rootless container backend (podman) ---" -# Real-rootless environment test (docs/_archive/design/docker-rootless.md). In the +# Real-rootless environment test (docs/design/rootless-backend.md). In the # soft-only model the §9.1 proof flips: containers run as the opencode host # UID and CAN read the project files (the ddev-working goal) — but the uid_map # still proves they are NOT real root. From 954864e851c240863f739afaf1fc487f338daf72 Mon Sep 17 00:00:00 2001 From: Steffen Maechtel Date: Mon, 24 Aug 2026 22:03:13 +0200 Subject: [PATCH 4/5] docs(evaluate): evaluation records for sandbox tools (#40) and DDEV addons (#41) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New docs/evaluate/ section: one record per tool/addon plus per-area indexes and a verdict vocabulary. container-tools/ evaluates bubblewrap (conflicting — the mount-namespace boundary excludes ddev's shared filesystem), opencode-sandbox (conflicting as default; interesting only as a per-project network egress fence) and Docker sbx (not applicable — local-microVM alternative model, replaces the kit, WSL2 KVM blocker). ddev-addons/ evaluates trebormc/ddev-opencode (parallel model, config drift, in-container sudo), trebormc/ddev-ai-ssh (foreign-agent sidecar infra the one-owner model does not need) and e0ipso/ddev-assistant-opencode (agent inside the app container — inverts the separation goal, though notably the kit's soft-layer config would apply). Cross-cutting: every hard boundary lands on the layer the kit deliberately leaves soft for ddev. --- docs/README.md | 5 ++ docs/evaluate/README.md | 28 +++++++++ docs/evaluate/container-tools/README.md | 21 +++++++ docs/evaluate/container-tools/bubblewrap.md | 51 ++++++++++++++++ docs/evaluate/container-tools/docker-sbx.md | 60 +++++++++++++++++++ .../container-tools/opencode-sandbox.md | 57 ++++++++++++++++++ docs/evaluate/ddev-addons/README.md | 25 ++++++++ docs/evaluate/ddev-addons/ddev-ai-ssh.md | 46 ++++++++++++++ .../ddev-addons/ddev-assistant-opencode.md | 51 ++++++++++++++++ docs/evaluate/ddev-addons/ddev-opencode.md | 55 +++++++++++++++++ 10 files changed, 399 insertions(+) create mode 100644 docs/evaluate/README.md create mode 100644 docs/evaluate/container-tools/README.md create mode 100644 docs/evaluate/container-tools/bubblewrap.md create mode 100644 docs/evaluate/container-tools/docker-sbx.md create mode 100644 docs/evaluate/container-tools/opencode-sandbox.md create mode 100644 docs/evaluate/ddev-addons/README.md create mode 100644 docs/evaluate/ddev-addons/ddev-ai-ssh.md create mode 100644 docs/evaluate/ddev-addons/ddev-assistant-opencode.md create mode 100644 docs/evaluate/ddev-addons/ddev-opencode.md diff --git a/docs/README.md b/docs/README.md index be49bd1..2c05380 100644 --- a/docs/README.md +++ b/docs/README.md @@ -45,6 +45,11 @@ with ddev — see the [README](../README.md) whether the kit fits your setup. - [Troubleshooting](troubleshooting.md) — symptom → cause → fix +## Evaluations (internal) + +- [Evaluations](evaluate/README.md) — third-party tools and addons + assessed against the kit's model, with verdicts (issues #40, #41) + ## Design records (internal) `design/` holds planning and analysis records for CURRENT behavior — diff --git a/docs/evaluate/README.md b/docs/evaluate/README.md new file mode 100644 index 0000000..eae3cf7 --- /dev/null +++ b/docs/evaluate/README.md @@ -0,0 +1,28 @@ +# Evaluate + +Evaluation records for third-party tools and addons that could extend, +complement, or replace the kit's model. Each record answers one +question: **what would this add to (or change about) the opencode +permissions kit, and does it fit?** + +These are decision documents, not user documentation — verdicts are +recorded per tool with date and issue reference. Where a verdict says +"conflicting", the record explains which kit guarantee or requirement +collides. Re-evaluation is welcome: open an issue when a tool changed +materially. + +## Areas + +- [Container tools / sandboxes](container-tools/README.md) — OS-level + sandboxing for the agent (issue #40) +- [DDEV addons](ddev-addons/README.md) — running opencode inside DDEV + containers (issue #41) + +## Verdict vocabulary + +| Verdict | Meaning | +|---|---| +| Adopt | use it in the kit | +| Complementary | usable alongside the kit for specific projects/workflows | +| Conflicting | collides with a kit guarantee or the ddev-must-work requirement | +| Not applicable | targets a different problem or replaces the model entirely | diff --git a/docs/evaluate/container-tools/README.md b/docs/evaluate/container-tools/README.md new file mode 100644 index 0000000..ec9ff8e --- /dev/null +++ b/docs/evaluate/container-tools/README.md @@ -0,0 +1,21 @@ +# Container tools / sandboxes — evaluations + +> Issue [#40](https://github.com/steffenmaechtel/opencode-permissions-kit/issues/40): +> can a container sandbox tool add hard OS-level security to the kit's +> deliberately soft model? + +Context for all records here: the kit's boundary is UID separation (agent +≠ developer) + a rootless backend owned by the agent user. The soft layer +exists **because ddev's containers must read project files** +(`settings.php`, `.env`) through bind mounts — any sandbox that hides +files from the agent also hides them from ddev. + +| Tool | Record | Verdict | +|---|---|---| +| bubblewrap | [bubblewrap.md](bubblewrap.md) | Conflicting | +| opencode-sandbox (npm) | [opencode-sandbox.md](opencode-sandbox.md) | Conflicting as default; complementary as opt-in network fence | +| Docker `sbx` | [docker-sbx.md](docker-sbx.md) | Not applicable (alternative model, replaces the kit) | + +Cross-cutting conclusion: all three draw their hard boundary at exactly +the layer the kit deliberately leaves soft — the shared, +daemon-visible project filesystem that ddev requires. diff --git a/docs/evaluate/container-tools/bubblewrap.md b/docs/evaluate/container-tools/bubblewrap.md new file mode 100644 index 0000000..8219340 --- /dev/null +++ b/docs/evaluate/container-tools/bubblewrap.md @@ -0,0 +1,51 @@ +# Evaluate: bubblewrap + +> Evaluated: 2026-08-24 · Issue +> [#40](https://github.com/steffenmaechtel/opencode-permissions-kit/issues/40) +> · Verdict: **Conflicting with the model** +> · Source: (README/docs; v0.11.x era) + +## What it is + +Low-level unprivileged sandbox launcher (the backbone of Flatpak). +Composes isolation from namespaces: always a new **mount namespace** +with a tmpfs root you populate via **bind mounts** (ro/rw), optionally +unprivileged **user namespaces** (CLONE_NEWUSER), PID/IPC/NET/UTS +namespaces, **seccomp** filters, and `PR_SET_NO_NEW_PRIVS`. It is +deliberately not a ready-made sandbox — "Sandbox security … is entirely +determined by the arguments": the policy is your argv. + +## Requirements + +- Per-process launcher (`bwrap --ro-bind … cmd`); nothing persistent. +- Unprivileged user namespaces in the kernel. **Setuid mode is + deprecated and being removed** (0.11.2, Apr 2026, CVE-2026-41163). +- Ubuntu 24.04+ needs the AppArmor `bwrap-userns-restrict` fix or + `kernel.apparmor_restrict_unprivileged_userns=0`. WSL2: unprivileged + userns generally works; AppArmor typically not enforced. + +## Maturity + +~8.5k stars, maintained by Flatpak/Red Hat developers, active releases +(0.9.0 → 0.11.2). Very mature — as a *building block*. + +## Fit with the kit + +- **Would add:** true OS-level denies — read-only rootfs, hidden paths + (`--ro-bind` only what the agent may see), seccomp. +- **What breaks:** ddev containers bind-mount project files **from the + rootless daemon's mount view** — files visible only inside a bwrap + mount namespace are invisible to the daemon outside it, so the ddev + handover breaks. Keeping ddev working means rw-binding the project, + the rootless `DOCKER_HOST` socket, `~/.ddev`/`~/.docker`, and + near-open networking — which guts the deny surface. +- **History:** explored pre-0.0.11 (`local/IDEA-BUBBLEWRAP+DDEV.md`, + workspace-internal) and set aside when the kit moved to the soft-only + model — this evaluation confirms that outcome. + +## Verdict + +**Conflicting.** The confinement boundary (bwrap mount namespace) and +the ddev-shared-filesystem requirement are mutually exclusive at the +same host. Revisit only if the kit ever offers a non-ddev hard-mode +profile for untrusted one-off work. diff --git a/docs/evaluate/container-tools/docker-sbx.md b/docs/evaluate/container-tools/docker-sbx.md new file mode 100644 index 0000000..0bd45ff --- /dev/null +++ b/docs/evaluate/container-tools/docker-sbx.md @@ -0,0 +1,60 @@ +# Evaluate: Docker `sbx` (Docker Sandboxes) + +> Evaluated: 2026-08-24 · Issue +> [#40](https://github.com/steffenmaechtel/opencode-permissions-kit/issues/40) +> · Verdict: **Not applicable (alternative model — replaces the kit +> rather than hardening it)** +> · Source: and +> `docker/sandbox-templates:opencode` (CLI ~0.39.x era) + +## What it is + +Docker's sandbox CLI: each sandbox is a **local microVM** (KVM on Linux +/ Windows Hypervisor Platform / Apple Virtualization) with its **own +isolated Docker daemon**, filesystem, and network. Outbound TCP is +forced through a host-side proxy enforcing network policies + +credential injection. Local VMs, not Docker's cloud — but a mandatory +`sbx login` (free Docker account) and outbound access to Docker +infrastructure are required. CLI is free incl. commercial use; only org +governance is paid. Closed-source CLI (`docker/sbx-releases`), young +and rapidly evolving. + +## opencode support + +First-class: `sbx create opencode` / +[docker/sandbox-templates:opencode](https://docs.docker.com/reference/cli/sbx/create/opencode/), +TUI works, `sbx secret set` for provider credentials. Agents run with +approval prompts skipped — "the sandbox itself is the safety boundary". + +- **Files in/out:** default live passthrough bind mount (virtiofs) at + the same absolute path; alternative `--clone` mode does a private git + clone with commits flowing back via a `sandbox-` remote; plus + `sbx cp` for ad-hoc copies. +- **Nested containers:** each sandbox has its own Docker daemon, so + ddev could in principle run inside — against a *different* daemon + than anything on the host. + +## Requirements / WSL2 + +Standalone CLI (no Docker Desktop/Engine needed). Linux: Ubuntu 24.04+, +**KVM required** (`kvm` group). **WSL2 hard blocker:** no `/dev/kvm` by +default — needs Windows 11 + `nestedVirtualization=true` in +`.wslconfig`, which is flaky; the FAQ also calls out WSL keyring gaps. + +## Fit with the kit + +- **Would add:** full hypervisor isolation — stronger than UID + separation. +- **Why it does not fit:** it substitutes the kit's whole stack + (UID separation + rootless backend + soft config). User-level + opencode config is not imported into the sandbox (project-level + only), so the kit's global `opencode.jsonc` policy would not apply + inside; ddev would run against the VM-internal daemon, decoupled from + the developer's setup; and the WSL2 KVM blocker hits the kit's + primary platform. + +## Verdict + +**Not applicable** for enhancing the kit — a separate +disposable-agent track, not a hardening of this one. Revisit if the +kit ever grows an "untrusted task" mode on machines with KVM. diff --git a/docs/evaluate/container-tools/opencode-sandbox.md b/docs/evaluate/container-tools/opencode-sandbox.md new file mode 100644 index 0000000..f9574d9 --- /dev/null +++ b/docs/evaluate/container-tools/opencode-sandbox.md @@ -0,0 +1,57 @@ +# Evaluate: opencode-sandbox (npm plugin) + +> Evaluated: 2026-08-24 · Issue +> [#40](https://github.com/steffenmaechtel/opencode-permissions-kit/issues/40) +> · Verdict: **Conflicting as a default; complementary only as a +> per-project opt-in network fence** +> · Source: (v0.5.1; +> engine: Anthropic `@anthropic-ai/sandbox-runtime`) + +## What it is + +An opencode plugin wrapping every `bash` tool call via Anthropic's +`sandbox-runtime` (srt). On Linux: bubblewrap + bind mounts + seccomp +(AF_UNIX blocked by default) with the network namespace removed — all +traffic is forced through host HTTP/SOCKS proxies (over unix sockets via +`socat`) enforcing a **domain allowlist**. macOS uses Seatbelt; Windows +unsupported through opencode's hook. **Fail-open by design.** + +Default policy: writes only to project + `/tmp`; deny-read `~/.ssh`, +`~/.docker/config.json`, `~/.npmrc`, `~/.env`; network allowlist (npmjs, +pypi, github, api.anthropic.com, …) — everything else blocked. +Configurable via `~/.config/opencode-sandbox/` (env / per-project / +global). + +## Requirements + +`bubblewrap`, `socat`, `ripgrep`; Ubuntu 24.04+ AppArmor fix (see the +bubblewrap record). First-class opencode integration +(`tool.execute.before/after` hooks; the UI hides the wrapper). + +## Maturity + +v0.5.1 (24 releases since Feb 2026), ~204 downloads/week, 44 stars, +single primary maintainer. The underlying srt engine has 5.1k stars but +is a self-described "Beta Research Preview". + +## Fit with the kit + +- **Would add:** per-command **network allowlisting** — the one layer + the kit genuinely lacks (the kit confines files/UIDs, not network + egress). +- **What breaks:** allow-write (project + `/tmp` only) kills `~/.ddev`, + `~/.docker`, and rootless daemon state; the network allowlist blocks + ddev image pulls and Packagist/Composer; seccomp AF_UNIX blocking + cuts the `DOCKER_HOST` connection to the rootless daemon + (re-enabling sockets = `allowAllUnixSockets`, which disables that + layer). +- **Degradation:** fail-open means a broken setup silently falls back + to the kit's existing soft model — no error signal. + +## Verdict + +**Not as a kit default** — it breaks the ddev workflow the kit exists +to keep working. **Possibly complementary** as a per-project opt-in for +non-ddev work that wants an egress fence (a project without container +tools could enable it independently of the kit; the kit does not need +to ship or configure it). diff --git a/docs/evaluate/ddev-addons/README.md b/docs/evaluate/ddev-addons/README.md new file mode 100644 index 0000000..82589b4 --- /dev/null +++ b/docs/evaluate/ddev-addons/README.md @@ -0,0 +1,25 @@ +# DDEV addons — evaluations + +> Issue +> [#41](https://github.com/steffenmaechtel/opencode-permissions-kit/issues/41): +> do DDEV plugins that run opencode INSIDE the project's containers +> provide additional security or a viable alternative to the kit's +> host-UID model? + +Context for all records here: the kit separates agent and developer by +OS UID on the host, and ddev runs **as the agent user** (one-owner +model, soft permission layer in the agent's `opencode.jsonc`). The +addons below invert that: the agent lives inside ddev's containers. + +| Addon | Record | Verdict | +|---|---|---| +| trebormc/ddev-opencode | [ddev-opencode.md](ddev-opencode.md) | Conflicting (parallel model) | +| trebormc/ddev-ai-ssh | [ddev-ai-ssh.md](ddev-ai-ssh.md) | Not applicable (solves a foreign-agent sidecar problem the kit does not have) | +| e0ipso/ddev-assistant-opencode | [ddev-assistant-opencode.md](ddev-assistant-opencode.md) | Conflicting (collapses agent into the app container) | + +Cross-cutting: none of the addons isolates agent-from-project — the +kit's actual boundary. They isolate agent-from-host, at the cost of a +second opencode home (config drift), no host tooling/git/IDE, and no +agent-side container runtime. Ideas worth stealing regardless: +per-project credential isolation, a read-only agents/skills volume, the +socket-less exec pattern. diff --git a/docs/evaluate/ddev-addons/ddev-ai-ssh.md b/docs/evaluate/ddev-addons/ddev-ai-ssh.md new file mode 100644 index 0000000..f1d18b1 --- /dev/null +++ b/docs/evaluate/ddev-addons/ddev-ai-ssh.md @@ -0,0 +1,46 @@ +# Evaluate: trebormc/ddev-ai-ssh + +> Evaluated: 2026-08-24 · Issue +> [#41](https://github.com/steffenmaechtel/opencode-permissions-kit/issues/41) +> · Verdict: **Not applicable (complement only for foreign-agent +> sidecar setups the kit does not have)** +> · Source: (README; last +> commit 2026-07-09, active) + +## What it does + +Not an opencode runner — an infrastructure addon: installs +openssh-server into the **web container**, generates per-project +ed25519 keys in `.ddev/.agent-ssh-keys/` (gitignored), rewrites +`authorized_keys` on every `ddev start`, runs sshd under supervisord. +Consumers (opencode/claude-code sidecars) run `ssh web `; +`ForceCommand` does `cd /var/www/html` + `eval +"$SSH_ORIGINAL_COMMAND"` — full arbitrary shell as the web user. + +## Identity / secrets + +- SSH sessions land as the ddev-mapped web user; `PermitRootLogin no`, + key-only, no TCP/X11 forwarding. +- No API keys involved — but the per-project unpassphrased private key + lives in the project tree, readable by anything that can read the + project directory (including the agent itself). + +## Lifecycle + +sshd lives/dies with the web container; keys survive even addon +uninstall (documented). + +## Fit with the kit + +It exists so a **differently-owned agent container** can exec in the +web container without the Docker socket. The kit's one-owner model +(agent = ddev owner, direct wrapper handover) needs no indirection — +`ddev exec` already works for the agent. The `eval`-based +ForceCommand is deliberately full-access (soft-only at best), and the +addon adds a second network-reachable daemon (sshd) to the web +container. + +## Verdict + +**Not applicable.** Solves a problem the kit does not have; would only +matter if the kit ever shipped a sidecar-agent variant. diff --git a/docs/evaluate/ddev-addons/ddev-assistant-opencode.md b/docs/evaluate/ddev-addons/ddev-assistant-opencode.md new file mode 100644 index 0000000..e4bfaa5 --- /dev/null +++ b/docs/evaluate/ddev-addons/ddev-assistant-opencode.md @@ -0,0 +1,51 @@ +# Evaluate: e0ipso/ddev-assistant-opencode + +> Evaluated: 2026-08-24 · Issue +> [#41](https://github.com/steffenmaechtel/opencode-permissions-kit/issues/41) +> · Verdict: **Conflicting (collapses the agent into the app container +> — the opposite of the kit's separation goal)** +> · Source: +> (README; last commit 2026-05-18, minimal docs) + +## What it does + +Installs opencode **into the web container image** +(`curl opencode.ai/install | bash` → `/usr/local/bin/opencode`); usage +is plain `ddev exec opencode` — agent and project stack share one +container. Bind-mounts the host `~/.config/opencode`, +`~/.cache/opencode`, `~/.local/share/opencode` into the web container +at identical paths; pre/post-start hooks chown them to the web user. + +## Identity / secrets + +- Runs as the web container's default user (ddev's host-uid mapping; + under the kit's rootless backend = the `opencode` UID). +- **Weakest secret posture of the three addons:** the host user's + entire opencode auth/config/cache is mounted inside the **application + container** — arbitrary project code (vendor scripts, CMS plugins) + can read the API keys and poison config. + +## Lifecycle + +ddev-bound: `ddev stop` kills the agent; the binary is baked into the +image (addon changes need a rebuild). + +## Fit with the kit + +- **Unique property:** because it mounts the host opencode home, the + kit's `/home/opencode/.config/opencode/opencode.jsonc` **would be + picked up — the soft-layer denies would still apply** (the kit runs + ddev as `opencode`, so `~` resolves to the kit's config). +- **But the threat model inverts:** the kit separates the agent from + the developer; this puts the agent *inside the attack surface* (the + app container) — agent, PHP app, and any `composer install` + post-install script share one filesystem and process user. State + writes flow both ways (container agent mutates host config/cache). +- No agent-side containers (no DinD provided; a DinD addon inside the + web container would be an untested combination). + +## Verdict + +**Conflicting.** Interesting proof that the kit's config layer is +portable into a container, but the containment direction is wrong for +the kit's guarantees. diff --git a/docs/evaluate/ddev-addons/ddev-opencode.md b/docs/evaluate/ddev-addons/ddev-opencode.md new file mode 100644 index 0000000..3f3095a --- /dev/null +++ b/docs/evaluate/ddev-addons/ddev-opencode.md @@ -0,0 +1,55 @@ +# Evaluate: trebormc/ddev-opencode + +> Evaluated: 2026-08-24 · Issue +> [#41](https://github.com/steffenmaechtel/opencode-permissions-kit/issues/41) +> · Verdict: **Conflicting (viable replacement only for Drupal-only +> workflows that accept no host tooling and no agent-side docker)** +> · Source: +> (README; last commit 2026-07-08, active, single maintainer) + +## What it does + +Dedicated sidecar service `ddev--opencode` (Ubuntu 24.04 + +Node 22, opencode via npm), `sleep infinity`, entrypoint hook with +optional self-update. The agent reaches the web container via SSH +(`ssh web` → drush/composer/phpunit helpers); Playwright-MCP and Beads +sidecars via HTTP; **deliberately no Docker socket** in the agent +container. Interaction: `ddev opencode` / `ddev oc` → TUI. Part of the +"DDEV AI Workspace" ecosystem (auto-installs ai-ssh, agents-sync, +beads, playwright-mcp); Drupal-centric (syncs 10 agents / 12 rules / +24 skills). + +## Identity / files / secrets + +- Runs as `user: ${DDEV_UID:-1000}:${DDEV_GID:-1000}` — under the kit + that would be the `opencode` UID, consistent file ownership on the + bind-mounted `/var/www/html`. +- **But:** the image grants the container user `NOPASSWD:ALL` sudo + inside the container — the agent is effectively root within its + sidecar. +- Credentials: host `~/.ddev/opencode/auth.json` bind-mounted in — + **one credential file shared across all ddev projects** on the host. + +## Lifecycle + +Fully ddev-bound: `ddev stop` kills the session, `ddev destroy` +removes containers/volumes. Config cascade deep-merged per start +(project `opencode.json` > host `~/.ddev/opencode/config/` > synced +agents volume > baked defaults). + +## Fit with the kit + +- **Would add:** zero agent footprint on the host outside + `~/.ddev`; project-scoped agent. +- **What breaks:** a second opencode home inside the container — the + kit's `/home/opencode/.config/opencode/opencode.jsonc` denies are not + mounted, not applied (only `auth.json` is shared) → guaranteed + permission-config drift between two agents. No Docker socket → the + agent cannot run its own containers (the kit gives it a rootless + daemon). No host tooling/git/IDE integration. + +## Verdict + +**Conflicting.** A parallel model, not an extension — adopting it means +abandoning the kit's single-agent, single-config, host-integrated +approach. From 2109c77670b4ced7b16dbaa6f6b24e454020c401 Mon Sep 17 00:00:00 2001 From: Steffen Maechtel Date: Mon, 24 Aug 2026 22:05:08 +0200 Subject: [PATCH 5/5] docs(evaluate): verify records against local source clones MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All five evaluable repos now checked out under github/ (workspace) — re-verify each record against the pinned commits and stamp the source lines: - bubblewrap @ 2f55bae: setuid-deprecation + userns claims hold; note the new upstream --not-a-security-boundary fail-open flag - opencode-sandbox @ 328c963: hooks, config keys and explicit fail-open (Windows + swallowed setup errors) confirmed in src/ - ddev-opencode @ b972091: sleep-infinity sidecar, DDEV_UID user, passwordless-ALL sudoers line, auth.json-only credential mount, ro host config deep-merge cascade, vendored sshd confirmed - ddev-ai-ssh @ cf26f3e: eval ForceCommand, PermitRootLogin no, per-project ed25519 keys confirmed - ddev-assistant-opencode @ 5cb552b: correction — the FULL ~/.local/share/opencode is mounted (auth.json included), secret posture even weaker than first recorded docker-sbx stays research-only (closed source, no repo). --- docs/evaluate/container-tools/bubblewrap.md | 8 ++++- .../container-tools/opencode-sandbox.md | 14 +++++++-- docs/evaluate/ddev-addons/ddev-ai-ssh.md | 5 ++-- .../ddev-addons/ddev-assistant-opencode.md | 30 ++++++++++++------- docs/evaluate/ddev-addons/ddev-opencode.md | 28 ++++++++++++----- 5 files changed, 62 insertions(+), 23 deletions(-) diff --git a/docs/evaluate/container-tools/bubblewrap.md b/docs/evaluate/container-tools/bubblewrap.md index 8219340..293cf92 100644 --- a/docs/evaluate/container-tools/bubblewrap.md +++ b/docs/evaluate/container-tools/bubblewrap.md @@ -3,7 +3,8 @@ > Evaluated: 2026-08-24 · Issue > [#40](https://github.com/steffenmaechtel/opencode-permissions-kit/issues/40) > · Verdict: **Conflicting with the model** -> · Source: (README/docs; v0.11.x era) +> · Source: — verified +> against a local clone @ `2f55bae` (README, docs) ## What it is @@ -23,6 +24,11 @@ determined by the arguments": the policy is your argv. - Ubuntu 24.04+ needs the AppArmor `bwrap-userns-restrict` fix or `kernel.apparmor_restrict_unprivileged_userns=0`. WSL2: unprivileged userns generally works; AppArmor typically not enforced. +- Notable upstream signal (verified @ `2f55bae`): a + `--not-a-security-boundary` flag was added for callers that use bwrap + purely for filesystem layout and want **fail-open** behavior — the + project itself is explicit that the boundary is only as strong as the + caller's argv. ## Maturity diff --git a/docs/evaluate/container-tools/opencode-sandbox.md b/docs/evaluate/container-tools/opencode-sandbox.md index f9574d9..f50989d 100644 --- a/docs/evaluate/container-tools/opencode-sandbox.md +++ b/docs/evaluate/container-tools/opencode-sandbox.md @@ -4,8 +4,10 @@ > [#40](https://github.com/steffenmaechtel/opencode-permissions-kit/issues/40) > · Verdict: **Conflicting as a default; complementary only as a > per-project opt-in network fence** -> · Source: (v0.5.1; -> engine: Anthropic `@anthropic-ai/sandbox-runtime`) +> · Source: — verified +> against a local clone of the plugin repo @ `328c963` +> (`src/index.ts`, `src/config.ts`; engine: Anthropic +> `@anthropic-ai/sandbox-runtime` 0.0.73) ## What it is @@ -22,6 +24,14 @@ pypi, github, api.anthropic.com, …) — everything else blocked. Configurable via `~/.config/opencode-sandbox/` (env / per-project / global). +Source-verified details (@ `328c963`): the hooks are +`tool.execute.before`/`tool.execute.after` wrapping only `bash` calls; +the config surface is exactly `denyRead/allowRead/allowWrite/denyWrite` ++ `allowedDomains/allowUnixSockets/allowAllUnixSockets/ +allowLocalBinding`; **fail-open is explicit in the code** — Windows +logs "commands will run without sandbox" and every setup error is +caught and swallowed. + ## Requirements `bubblewrap`, `socat`, `ripgrep`; Ubuntu 24.04+ AppArmor fix (see the diff --git a/docs/evaluate/ddev-addons/ddev-ai-ssh.md b/docs/evaluate/ddev-addons/ddev-ai-ssh.md index f1d18b1..5be312f 100644 --- a/docs/evaluate/ddev-addons/ddev-ai-ssh.md +++ b/docs/evaluate/ddev-addons/ddev-ai-ssh.md @@ -4,8 +4,9 @@ > [#41](https://github.com/steffenmaechtel/opencode-permissions-kit/issues/41) > · Verdict: **Not applicable (complement only for foreign-agent > sidecar setups the kit does not have)** -> · Source: (README; last -> commit 2026-07-09, active) +> · Source: — verified +> against a local clone @ `cf26f3e` (`docker-compose.ai-ssh.yaml`, +> `web-build/Dockerfile.ai-ssh`, `install.yaml`) ## What it does diff --git a/docs/evaluate/ddev-addons/ddev-assistant-opencode.md b/docs/evaluate/ddev-addons/ddev-assistant-opencode.md index e4bfaa5..85634a4 100644 --- a/docs/evaluate/ddev-addons/ddev-assistant-opencode.md +++ b/docs/evaluate/ddev-addons/ddev-assistant-opencode.md @@ -4,26 +4,34 @@ > [#41](https://github.com/steffenmaechtel/opencode-permissions-kit/issues/41) > · Verdict: **Conflicting (collapses the agent into the app container > — the opposite of the kit's separation goal)** -> · Source: -> (README; last commit 2026-05-18, minimal docs) +> · Source: — +> verified against a local clone @ `5cb552b` +> (`docker-compose.assistant-opencode.yaml`, +> `web-build/Dockerfile.assistant-opencode`, `install.yaml`) ## What it does Installs opencode **into the web container image** -(`curl opencode.ai/install | bash` → `/usr/local/bin/opencode`); usage -is plain `ddev exec opencode` — agent and project stack share one -container. Bind-mounts the host `~/.config/opencode`, -`~/.cache/opencode`, `~/.local/share/opencode` into the web container -at identical paths; pre/post-start hooks chown them to the web user. +(`curl -fsSL https://opencode.ai/install | bash` → +`/usr/local/bin/opencode`); usage is plain `ddev exec opencode` — agent +and project stack share one container. The compose file bind-mounts +the host `${HOME}/.config/opencode`, `~/.cache/opencode`, **and the +full `~/.local/share/opencode`** into the web container at identical +paths; a pre-start hook pre-creates the paths with the right type and +post-start hooks chown them to the web user (the README's own comment +notes Docker would otherwise create directories at missing file +paths). ## Identity / secrets - Runs as the web container's default user (ddev's host-uid mapping; under the kit's rootless backend = the `opencode` UID). -- **Weakest secret posture of the three addons:** the host user's - entire opencode auth/config/cache is mounted inside the **application - container** — arbitrary project code (vendor scripts, CMS plugins) - can read the API keys and poison config. +- **Weakest secret posture of the three addons (source-verified):** the + host user's **entire** opencode home is mounted inside the + **application container** — config, cache, *and* the full + `~/.local/share/opencode` including `auth.json` — so arbitrary + project code (vendor scripts, CMS plugins) can read the API keys and + poison config. ## Lifecycle diff --git a/docs/evaluate/ddev-addons/ddev-opencode.md b/docs/evaluate/ddev-addons/ddev-opencode.md index 3f3095a..bd77ea6 100644 --- a/docs/evaluate/ddev-addons/ddev-opencode.md +++ b/docs/evaluate/ddev-addons/ddev-opencode.md @@ -4,21 +4,35 @@ > [#41](https://github.com/steffenmaechtel/opencode-permissions-kit/issues/41) > · Verdict: **Conflicting (viable replacement only for Drupal-only > workflows that accept no host tooling and no agent-side docker)** -> · Source: -> (README; last commit 2026-07-08, active, single maintainer) +> · Source: — +> verified against a local clone @ `b972091` +> (`docker-compose.opencode.yaml`, `opencode-build/Dockerfile.opencode`, +> `install.yaml`) ## What it does -Dedicated sidecar service `ddev--opencode` (Ubuntu 24.04 + -Node 22, opencode via npm), `sleep infinity`, entrypoint hook with -optional self-update. The agent reaches the web container via SSH -(`ssh web` → drush/composer/phpunit helpers); Playwright-MCP and Beads -sidecars via HTTP; **deliberately no Docker socket** in the agent +Dedicated sidecar service `opencode` (container +`ddev--opencode`, `command: ["sleep", "infinity"]`, `restart: +"no"`, `depends_on` web + playwright-mcp + beads + agents-sync). +opencode is installed in the image via npm; an entrypoint hook does +optional self-update. The agent reaches the web container via SSH — the +sidecar image **vendors its own sshd** with an `ai-ssh-command.sh` +ForceCommand wrapper (same pattern as ddev-ai-ssh); Playwright-MCP and +Beads sidecars via HTTP; **deliberately no Docker socket** in the agent container. Interaction: `ddev opencode` / `ddev oc` → TUI. Part of the "DDEV AI Workspace" ecosystem (auto-installs ai-ssh, agents-sync, beads, playwright-mcp); Drupal-centric (syncs 10 agents / 12 rules / 24 skills). +Source-verified details (@ `b972091`): `user: +"${DDEV_UID:-1000}:${DDEV_GID:-1000}"`; the Dockerfile writes a +sudoers line granting the container user passwordless ALL; only +`auth.json` is shared from the host ("only auth.json, not the full +directory"); the host config dir is mounted **read-only** and +deep-merged by the entrypoint (cascade: project `opencode.json` > host +`~/.ddev/opencode/config/` > synced agents volume > baked defaults); +agents/rules arrive via a read-only volume with subpath mounts. + ## Identity / files / secrets - Runs as `user: ${DDEV_UID:-1000}:${DDEV_GID:-1000}` — under the kit