diff --git a/AGENTS.md b/AGENTS.md index 1f259de..4318097 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -55,6 +55,12 @@ used ref is stamped as `KIT_CHANNEL` in `install.conf` and followed by - The security model is deliberately **soft-only** — never re-introduce OS-level deny ACLs. Background: `docs/design/ddev-working.md`, current model: `docs/concepts/security-model.md`. +- **The kit never edits user-owned system config on its own — above all + `/etc/wsl.conf`.** Install/update only *show* snippets or name opt-in + commands the user runs themselves; `opk wsl-add-opencode-1-fix` is the + only command that writes wsl.conf, and only on explicit invocation. + `opk uninstall` asks (or `--yes`) before removing kit-owned wsl.conf + content. Rationale: `docs/design/wsl-conf-consent.md`. ## Testing diff --git a/docs/concepts/security-model.md b/docs/concepts/security-model.md index 6a4da63..0a02fbf 100644 --- a/docs/concepts/security-model.md +++ b/docs/concepts/security-model.md @@ -70,11 +70,13 @@ do not distinguish WSL users, so with the default world-readable mount (`.ssh/`, `NTUSER.DAT`, browser data). The kit's UID separation only covers the Linux side. -The kit surfaces this everywhere: `install.sh` warns and offers to restrict -the mount to your user (recommended; applies after `wsl --shutdown` from -Windows), `update.sh` prints a warning, `status.sh` reports the exposure, -and the **wrapper warns on every `opencode` start** until the restriction is -applied. +The kit surfaces this everywhere: `install.sh` prints the ready-to-run +restriction snippet (the kit never edits `/etc/wsl.conf` itself — you +apply it; takes effect after `wsl --shutdown` from Windows), +`update.sh` prints a warning with the same snippet, `status.sh` reports +the exposure, and the **wrapper warns on every `opencode` start** until +the restriction is applied. Rationale for the no-write rule: +[design: wsl.conf consent](../design/wsl-conf-consent.md). Manual fix via `/etc/wsl.conf`: @@ -109,12 +111,15 @@ printing URL and device code. The kit solves this **without granting the agent anything**: -- install.sh/update.sh keep a kit-managed **comment block** at the **top** - of `/etc/wsl.conf`. Its last line is a carrier for `open`'s scan: the - line starts with `#` (so WSL treats it as a comment) and contains a raw - carriage return before `root = ` — `open`'s regex cannot - see the `#` across that CR, so its first-match scan resolves into the - kit library and the computed powershell path lands there. +- `sudo opk wsl-add-opencode-1-fix` — run by you, the only kit command + that ever writes `/etc/wsl.conf` (install/update deploy just the + stand-in tree; the kit never edits the file implicitly) — writes a + kit-managed **comment block** at the **top** of `/etc/wsl.conf`. Its + last line is a carrier for `open`'s scan: the line starts with `#` + (so WSL treats it as a comment) and contains a raw carriage return + before `root = ` — `open`'s regex cannot see the `#` + across that CR, so its first-match scan resolves into the kit library + and the computed powershell path lands there. - there, `wsl/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe` is a harmless stand-in (`bin/browser-bridge`): it forwards to the **real** powershell.exe whenever the calling user may execute it (you, the @@ -142,9 +147,10 @@ no-op path triggers, it prints a one-line hint to the terminal (`auto-open unavailable for the agent — open the printed URL manually`); `OPK_BROWSER_BRIDGE_DEBUG=1` traces the decision for direct invocations (self-test: [troubleshooting](../troubleshooting.md)). -`opk status` reports the bridge state; the wrapper warns when the mount is -restricted but the bridge is missing (hand-edited `wsl.conf`, partial -deploy). Uninstall removes the block and the stand-in tree. +`opk status` reports the bridge state and names +`opk wsl-add-opencode-1-fix` when the carrier is missing; the wrapper +warns the same way on every start. Uninstall asks before removing the +block (or assumes yes with `--yes`) and always removes the stand-in tree. ## Other root-equivalent surfaces (audit) diff --git a/docs/design/wsl-conf-consent.md b/docs/design/wsl-conf-consent.md new file mode 100644 index 0000000..995c4fa --- /dev/null +++ b/docs/design/wsl-conf-consent.md @@ -0,0 +1,64 @@ +# wsl.conf consent — the kit never writes /etc/wsl.conf implicitly + +> Status: **CURRENT.** Decided after +> [issue #100](https://github.com/steffenmaechtel/opencode-permissions-kit/issues/100) +> (kit 0.0.36 wrote an invalid section into `/etc/wsl.conf` and, on WSL +> ≤ 2.9.12, silently disabled *every* WSL setting in the file). Branch: +> `feature/wsl-conf-user-consent`. + +## 1. Problem + +Two kit features used to write into `/etc/wsl.conf` during +install/update: + +- the `[automount]` /mnt/c hardening (install.sh, after a yes/no prompt), +- the browser-bridge carrier that keeps opencode 1.x device logins alive + on a hardened /mnt/c (install.sh/update.sh, no prompt at all). + +`/etc/wsl.conf` is user-owned system configuration with boot-time +effects on the whole distro. Issue #100 showed what an implicit write +costs when it goes wrong: WSL aborted parsing the file and the user's +own settings (systemd, automount restriction) silently stopped applying. +A yes/no prompt inside a long install flow is not informed consent for +that class of change — and there was no prompt at all for the carrier. + +## 2. Decision + +The kit **never writes `/etc/wsl.conf` on its own**. The only paths that +ever touch it: + +1. **Explicit opt-in command:** `sudo opk wsl-add-opencode-1-fix` — + deploys the browser-bridge stand-in tree and writes the kit comment + block (pure comments, WSL-silent; see + [security model](../concepts/security-model.md)). Called by the user, + nothing else. status.sh and the wrapper advertise it whenever the + mount is hardened but the carrier is missing. +2. **Printed snippets:** install.sh prints the ready-to-run + `[automount]` hardening snippet (with the resolved uid/gid); applying + it is the user's manual step, like the documented manual fix. +3. **Removal of kit-owned content only, with consent:** update.sh strips + the broken legacy 0.0.36 section (regression cleanup — it restores the + user's file to a WSL-parseable state; never writes anything). + `opk uninstall` asks before removing the kit comment block (or assumes + yes with `--yes`); declining prints the exact line range so the user + can delete it by hand — after uninstall there is no `opk` left to do + it for them. Neither path ever writes kit content into the file. + +An existing carrier (written by earlier kit versions or the opt-in +command) is deliberately left untouched by updates — removing it would +break working device logins. `AGENTS.md` carries this as a repo rule for +future features: user-owned system config is only ever changed through +opt-in commands or printed instructions. + +## 3. Consequences + +- Fresh installs land with `opk status` showing the bridge as + stand-in-only until the user opts in — one explicit command, and the + wrapper/status explain exactly when it is needed (hardened /mnt/c, + opencode 1.x). opencode 2.x never needs it (its `open` access-checks + powershell and falls back to xdg-open). +- The /mnt/c hardening UX loses one prompt and gains a printed snippet — + the wrapper already repeats the snippet on every start until applied. +- Uninstall behavior: the stand-in tree always goes; the kit block is + removed only after an explicit yes (or `--yes`), otherwise the exact + lines to delete are printed. User content never goes. diff --git a/docs/how-to/uninstall.md b/docs/how-to/uninstall.md index 87ba657..86cc66e 100644 --- a/docs/how-to/uninstall.md +++ b/docs/how-to/uninstall.md @@ -29,6 +29,12 @@ Run it as your default user (it asks for `sudo` where needed). Options: **Project file contents are untouched** — only owner and group revert. +On WSL, the uninstaller also **asks** whether to remove the kit's +browser-bridge block from `/etc/wsl.conf` (kit-owned comment lines only; +default: yes). Decline and it prints the exact line range to delete by +hand — the kit never edits `wsl.conf` without your say-so +([why](../design/wsl-conf-consent.md)). + ## What stays behind (harmless) - Shell RC hook lines (`~/.bashrc` / `~/.zshrc` / `~/.profile`, tagged diff --git a/docs/reference/cli.md b/docs/reference/cli.md index cfba229..b46bc3b 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -2,6 +2,30 @@ This page lists the kit's commands and flags. +## wsl-add-opencode-1-fix + +Write the WSL browser-bridge carrier into `/etc/wsl.conf` (sudo — the +command elevates itself): + +```bash +sudo opk wsl-add-opencode-1-fix +``` + +Keeps opencode 1.x device logins (`console login`) alive on a hardened +`/mnt/c`: the carrier is a comment block at the top of your `wsl.conf` +whose `root =` line wins the bundled `open` package's scan, redirecting +its powershell.exe lookup to the kit stand-in (forwards for you, exits 0 +for the agent). Takes effect immediately — no `wsl --shutdown` needed, +WSL only ever sees comments. opencode 2.x needs no bridge (its `open` +access-checks powershell and falls back to xdg-open). + +This is the **only** kit command that writes `/etc/wsl.conf` — install +and update never touch the file; they only deploy the stand-in tree and +tell you about this command ([why](../design/wsl-conf-consent.md)). The +command is idempotent and also cleans up the broken kit-0.0.36 section if +one is present. `opk uninstall` asks before removing the block again +(`--yes` assumes yes). + ## The `opk` command After installation, one command manages everything (works from anywhere in @@ -14,6 +38,7 @@ opk update --binary opk upgrade-opencode # just the opencode binary opk ddev-hosts-add # in a ddev project dir opk handover me .gotmp # mixed-owner tree -> yours again +opk wsl-add-opencode-1-fix # opt in to the WSL browser-bridge carrier opk uninstall opk help # commands + arguments overview ``` diff --git a/docs/reference/files.md b/docs/reference/files.md index 8396db9..f2c5bda 100644 --- a/docs/reference/files.md +++ b/docs/reference/files.md @@ -5,14 +5,18 @@ This page lists every file and directory the kit manages, and every key in ## /etc/wsl.conf -Not kit-owned — your WSL configuration. The kit appends the `[automount]` -hardening (on your confirmation, applies after `wsl --shutdown`) and keeps -a kit-managed **comment block** at the top (WSL only: its carrier line — -a `#` comment containing a raw carriage return before `root = …` — wins -the `open` package's scan and redirects opencode's powershell lookup to -the browser bridge stand-in; WSL itself only ever sees comments, so no -warning, no restart needed). Uninstall removes exactly that block and -leaves your own entries untouched. +Never written by the kit — your WSL configuration stays yours: install +and update only *print* snippets for you to apply yourself +([why](../design/wsl-conf-consent.md)). One optional kit artifact can +live in it, written by a single explicit command: +`sudo opk wsl-add-opencode-1-fix` places the browser-bridge **comment +block** at the top (WSL only; its carrier line — a `#` comment containing +a raw carriage return before `root = …` — wins the `open` package's scan +and redirects opencode's powershell lookup to the browser bridge +stand-in; WSL itself only ever sees comments, so no warning, no restart +needed). The `[automount]` hardening is yours to add manually. +Uninstall asks before removing the kit block (or assumes yes with +`--yes`); your own entries always stay. ## /etc/opencode-permissions-kit/ @@ -76,7 +80,7 @@ The deployed library mirrors the repository layout | `/usr/local/lib/opencode-permissions-kit/bin/setup-container-backend` | Rootless backend provisioning | | `/usr/local/lib/opencode-permissions-kit/bin/socket-check` | Rootless socket probe (`test -S` only) | | `/usr/local/lib/opencode-permissions-kit/bin/cwd-check` | Headless serve cwd probe (readable-for-opencode check) | -| `/usr/local/lib/opencode-permissions-kit/bin/browser-bridge` | WSL browser bridge stand-in (deployed into the `wsl/` tree, see below) | +| `/usr/local/lib/opencode-permissions-kit/bin/browser-bridge` | WSL browser bridge stand-in source — deployed into the `wsl/` tree (see below); re-deployed by `opk wsl-add-opencode-1-fix` | | `/usr/local/lib/opencode-permissions-kit/wsl/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe` | WSL browser bridge: stand-in the bundled `open` package spawns for device logins — forwards to the real powershell.exe when the caller may execute it, `exit 0` otherwise (WSL only; see [security model](../concepts/security-model.md#wsl2-the-browser-bridge-login-survival-on-a-hardened-mntc)) | | `/usr/local/lib/opencode-permissions-kit/sh/ddev-terminal.sh` | Sourced `ddev()` terminal function (hooked into the default user's rc files) | | `/usr/local/lib/opencode-permissions-kit/sh/ddev-handover.sh` | Shared helper: `.ddev` + settings-dir chown | diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 5690f97..0883626 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -376,7 +376,9 @@ options = "uid=1000,gid=1000,dmask=027,fmask=037" ``` (replace `uid`/`gid` with your default WSL user's), then from Windows run -`wsl --shutdown` and restart WSL. Details: [security model](concepts/security-model.md). +`wsl --shutdown` and restart WSL. The kit never edits `/etc/wsl.conf` +itself — applying the snippet is yours ([why](design/wsl-conf-consent.md)). +Details: [security model](concepts/security-model.md). ## `opencode console login` / `opencode auth login` crashes with EACCES on powershell.exe @@ -395,13 +397,21 @@ file itself is executable; plain `execve` is unaffected. The wrapper moves starting the binary, so this is handled automatically; a crash means an old wrapper is deployed. -**Fix:** re-run the install or `opk update` — both (re)apply the kit -comment block in `/etc/wsl.conf`, the stand-in under -`/usr/local/lib/opencode-permissions-kit/wsl/`, and the current wrapper. -The wsl.conf block takes effect immediately (only `open` reads it; WSL -sees comments — no `wsl --shutdown` needed). Without a bridge, the login -still works when you open the printed URL yourself: the crash happens -*after* URL and device code are displayed. Details: [security +**Fix:** run + +```bash +sudo opk wsl-add-opencode-1-fix +``` + +It (re)deploys the stand-in under +`/usr/local/lib/opencode-permissions-kit/wsl/` and writes the kit comment +block into `/etc/wsl.conf` — the one kit command that touches the file, +and only because you ran it ([why](design/wsl-conf-consent.md)). The +block takes effect immediately (only `open` reads it; WSL sees comments +— no `wsl --shutdown` needed). If the stand-in itself is missing, run +`sudo opk update` first. Without a bridge, the login still works when you +open the printed URL yourself: the crash happens *after* URL and device +code are displayed. Details: [security model](concepts/security-model.md). **Diagnose — expected vs. broken:** when the login runs with the agent @@ -432,12 +442,18 @@ stops applying: the `[automount]` restriction stays pending, `[boot]` `systemd=true` (and with it the docker-rootless backend) disappears after the next `wsl --shutdown` (issue #100). -**Fix:** update the kit (`sudo opk update`, ≥ 0.0.37) — the rewrite -replaces the broken section with a pure comment block that WSL never -complains about — then run `wsl --shutdown` from Windows and reopen your -distro so the dropped settings apply again. `opk status` should show the -backend socket reachable, `/mnt/c` restricted, and the browser bridge -deployed. Details: [security model](concepts/security-model.md). +**Fix:** update the kit (`sudo opk update`, ≥ 0.0.38) — it strips the +broken section (restoring WSL's ability to parse the file) — then opt in +to the login-fix carrier yourself: + +```bash +sudo opk wsl-add-opencode-1-fix +``` + +Finally run `wsl --shutdown` from Windows and reopen your distro so the +dropped settings apply again. `opk status` should show the backend socket +reachable, `/mnt/c` restricted, and the browser bridge deployed. Details: +[security model](concepts/security-model.md). ## Group membership (opencode group) not applied diff --git a/files/install.sh b/files/install.sh index dd55b57..9d5f8a9 100755 --- a/files/install.sh +++ b/files/install.sh @@ -502,11 +502,11 @@ for loc in "/home/$DEFAULT_USER/.opencode/bin/opencode" "/root/.opencode/bin/ope done [ -n "$OC_BINARY_FOUND" ] || ui_add "opencode binary" "official installer will fetch it" -# WSL2 /mnt/c exposure preview (question + fix come later in step 4). +# WSL2 /mnt/c exposure preview (hardening snippet comes later in step 4). if [ -d /mnt/c ]; then _pm=$(stat -c %a /mnt/c 2>/dev/null || echo "") if [ -n "$_pm" ] && [ $((0$_pm & 0004)) -ne 0 ]; then - ui_atten "/mnt/c" "world-readable (mode $_pm) — restriction offered in step 4" + ui_atten "/mnt/c" "world-readable (mode $_pm) — hardening snippet shown in step 4" else ui_have "/mnt/c" "restricted (mode ${_pm:-?})" fi @@ -641,7 +641,7 @@ _plan "secure the opencode binary + wrapper" "root:opencode 750" if [ -d /mnt/c ]; then _pm=$(stat -c %a /mnt/c 2>/dev/null || echo "") if [ -n "$_pm" ] && [ $((0$_pm & 0004)) -ne 0 ]; then - _plan "restrict /mnt/c via /etc/wsl.conf" "(takes effect after wsl --shutdown)" + _plan "print /mnt/c hardening snippet" "(manual — the kit never writes /etc/wsl.conf)" fi fi _pps=$(cat /proc/sys/net/ipv4/ip_unprivileged_port_start 2>/dev/null || echo "?") @@ -649,7 +649,7 @@ _pps=$(cat /proc/sys/net/ipv4/ip_unprivileged_port_start 2>/dev/null || echo "?" _plan "deny-all config for your user" "(self-update bypass guard)" _plan "deploy library, sudoers, audit log" "/usr/local/lib/opencode-permissions-kit" if browser_bridge_is_wsl; then - _plan "deploy WSL browser bridge" "(opencode login fix: /etc/wsl.conf bridge block)" + _plan "deploy WSL browser bridge stand-in" "(login fix; wsl.conf carrier via 'opk wsl-add-opencode-1-fix')" fi if [ "$INTERACTIVE" = true ]; then @@ -871,12 +871,13 @@ if [ "${port_start:-1024}" -gt 80 ] 2>/dev/null; then fi fi -# WSL2 /mnt/c restriction: the drvfs mount runs with the Windows session -# token, so NTFS ACLs do NOT distinguish WSL users — the world-readable -# default (mode 777) exposes the whole Windows profile (.ssh, NTUSER.DAT, -# browser data) to every WSL user, including the agent's. Offer to restrict -# the mount to the default user. Takes effect only after 'wsl --shutdown' -# from Windows (the kit cannot reboot the distro). +# WSL2 /mnt/c restriction (report-only — the kit NEVER writes /etc/wsl.conf, +# docs/design/wsl-conf-consent.md): the drvfs mount runs with the Windows +# session token, so NTFS ACLs do NOT distinguish WSL users — the +# world-readable default (mode 777) exposes the whole Windows profile +# (.ssh, NTUSER.DAT, browser data) to every WSL user, including the +# agent's. Print the ready-to-run snippet; applying it is the user's +# explicit call and takes effect only after 'wsl --shutdown' from Windows. if [ -d /mnt/c ]; then mnt_mode=$(stat -c %a /mnt/c 2>/dev/null || echo "") if [ -z "$mnt_mode" ] || [ $((0$mnt_mode & 0004)) -eq 0 ]; then @@ -885,29 +886,22 @@ if [ -d /mnt/c ]; then echo " ${UI_YELLOW}NOTE: /etc/wsl.conf already has an [automount] section — left untouched.${UI_NC}" echo " /mnt/c is world-readable (mode $mnt_mode); every WSL user incl. the agent" echo " can read the Windows profile. Restrict it manually if unintended." - log "wsl.conf has a pre-existing [automount] section — /mnt/c restriction skipped" + log "wsl.conf has a pre-existing [automount] section — no hardening snippet printed" else + d_uid=$(id -u "$DEFAULT_USER" 2>/dev/null || echo "") + d_gid=$(id -g "$DEFAULT_USER" 2>/dev/null || echo "") echo " ${UI_YELLOW}WARNING: /mnt/c is world-readable (mode $mnt_mode) — every WSL user incl. the${UI_NC}" echo " ${UI_YELLOW}agent can read the Windows profile (.ssh, NTUSER.DAT, browser data).${UI_NC}" - ans=$(prompt "Restrict /mnt/c to your user? (WSL2 drvfs is world-readable by default; recommended)" "Y" "N" "") - if [ "$ans" = "y" ]; then - d_uid=$(id -u "$DEFAULT_USER" 2>/dev/null || echo "") - d_gid=$(id -g "$DEFAULT_USER" 2>/dev/null || echo "") - if [ -n "$d_uid" ] && [ -n "$d_gid" ]; then - printf '\n[automount]\nenabled = true\noptions = "uid=%s,gid=%s,dmask=027,fmask=037"\n' "$d_uid" "$d_gid" | sudo tee -a /etc/wsl.conf >/dev/null - echo " /etc/wsl.conf: [automount] restricted to uid=$d_uid/gid=$d_gid (dmask=027,fmask=037)" - echo " ${UI_YELLOW}Takes effect after 'wsl --shutdown' (Windows PowerShell) and reopening the distro.${UI_NC}" - log "wsl.conf automount restricted to uid=$d_uid gid=$d_gid" - else - echo " ${UI_YELLOW}Could not resolve uid/gid for '$DEFAULT_USER' — add manually to /etc/wsl.conf:${UI_NC}" - echo " [automount]" - echo " enabled = true" - echo ' options = "uid=,gid=,dmask=027,fmask=037"' - fi + echo " ${UI_YELLOW}The kit does not edit /etc/wsl.conf — apply the restriction yourself:${UI_NC}" + echo " [automount]" + echo " enabled = true" + if [ -n "$d_uid" ] && [ -n "$d_gid" ]; then + echo " options = \"uid=$d_uid,gid=$d_gid,dmask=027,fmask=037\"" else - echo " Skipped — /mnt/c stays world-readable; status.sh will keep reporting the exposure." - log "wsl.conf /mnt/c restriction declined" + echo ' options = "uid=,gid=,dmask=027,fmask=037"' fi + echo " ${UI_YELLOW}(append to /etc/wsl.conf, then 'wsl --shutdown' from Windows + reopen the distro).${UI_NC}" + log "/mnt/c hardening snippet printed (kit never writes wsl.conf)" fi fi @@ -1319,6 +1313,9 @@ sudo cp "$SCRIPT_DIR/opencode-permissions-kit-lib/sh/ddev-migrate.sh" "$LIBDIR/ sudo cp "$SCRIPT_DIR/opencode-permissions-kit-lib/bin/ddev-migrate" "$LIBDIR/bin/ddev-migrate" sudo cp "$SCRIPT_DIR/opencode-permissions-kit-lib/sh/fs-baseline.sh" "$LIBDIR/sh/fs-baseline.sh" sudo cp "$SCRIPT_DIR/opencode-permissions-kit-lib/sh/ddev-hosts.sh" "$LIBDIR/sh/ddev-hosts.sh" +# browser-bridge stand-in source (deploys into the wsl/ tree; source of +# 'opk wsl-add-opencode-1-fix' re-runs) +sudo cp "$SCRIPT_DIR/opencode-permissions-kit-lib/bin/browser-bridge" "$LIBDIR/bin/browser-bridge" # TUI mode display (docs/_archive/design/plan-ui-tui-opencode.md): plugin + templates 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/kit-mode-2x.tsx" "$LIBDIR/tui/kit-mode-2x.tsx" @@ -1332,7 +1329,8 @@ sudo chmod 644 "$LIBDIR/sh/ddev-terminal.sh" "$LIBDIR/sh/ddev-handover.sh" "$LIB sudo chmod 755 "$LIBDIR/bin/opencode-as-opencode" "$LIBDIR/bin/opk" "$LIBDIR/py/jsonc-parser.py" \ "$LIBDIR/sh/log.sh" "$LIBDIR/sh/ui.sh" "$LIBDIR/sh/shell-warn.sh" "$LIBDIR/bin/setup-container-backend" \ "$LIBDIR/management/config.sh" "$LIBDIR/management/update.sh" "$LIBDIR/management/status.sh" "$LIBDIR/management/uninstall.sh" \ - "$LIBDIR/bin/socket-check" "$LIBDIR/bin/cwd-check" "$LIBDIR/bin/ddev-as-opencode" "$LIBDIR/bin/ddev-migrate" + "$LIBDIR/bin/socket-check" "$LIBDIR/bin/cwd-check" "$LIBDIR/bin/ddev-as-opencode" "$LIBDIR/bin/ddev-migrate" \ + "$LIBDIR/bin/browser-bridge" log "library deployed to $LIBDIR" ui_success "kit library deployed: $LIBDIR" @@ -1364,18 +1362,26 @@ else fi # WSL browser bridge (issues #91, #100): the helper library is always -# deployed; the stand-in tree + /etc/wsl.conf comment block only materialize -# on WSL (the helper no-ops elsewhere). Installed on hardened AND unhardened -# /mnt/c alike — the developer who hardens later (status.sh hint) is already -# covered. +# deployed; the stand-in tree only materializes on WSL (the helper no-ops +# elsewhere). The /etc/wsl.conf carrier is NOT written here — the kit +# never edits wsl.conf implicitly (docs/design/wsl-conf-consent.md); the +# user opts in explicitly via 'sudo opk wsl-add-opencode-1-fix'. A legacy +# 0.0.36 section is stripped (kit-owned regression cleanup). sudo cp "$SCRIPT_DIR/opencode-permissions-kit-lib/sh/wsl-browser-bridge.sh" "$LIBDIR/sh/wsl-browser-bridge.sh" sudo chmod 644 "$LIBDIR/sh/wsl-browser-bridge.sh" if browser_bridge_is_wsl; then browser_bridge_install "$SCRIPT_DIR" "$LIBDIR" - ui_success "WSL browser bridge deployed (opencode console/auth login fix)" + ui_success "WSL browser bridge stand-in deployed" + if grep -q '^# opencode permissions kit browser bridge -- begin$' /etc/wsl.conf 2>/dev/null; then + ui_detail "wsl.conf carrier already present — bridge fully active" + log "wsl browser bridge stand-in deployed: $LIBDIR/wsl (carrier present)" + else + ui_detail "enable the login fix yourself (the kit does not edit /etc/wsl.conf):" + ui_detail " sudo opk wsl-add-opencode-1-fix" + log "wsl browser bridge stand-in deployed: $LIBDIR/wsl (carrier left to 'opk wsl-add-opencode-1-fix')" + fi ui_detail "on a hardened /mnt/c the browser cannot auto-open for the agent;" ui_detail "opencode prints URL + code — open them from your own browser" - log "wsl browser bridge deployed: $LIBDIR/wsl + comment block in /etc/wsl.conf" else log "wsl browser bridge skipped (not WSL)" fi @@ -1662,17 +1668,17 @@ else fi ui_kv "Backup" "$BACKUP_DIR" echo "" -# WSL2 final exposure warning: the wsl.conf restriction only takes effect -# after 'wsl --shutdown' — until then /mnt/c stays world-readable and the -# wrapper warns on every opencode start. Covers both "declined" and -# "configured but pending". +# WSL2 final exposure warning: a manually-applied wsl.conf restriction only +# takes effect after 'wsl --shutdown' — until then /mnt/c stays +# world-readable and the wrapper warns on every opencode start. if [ -d /mnt/c ]; then mnt_mode=$(stat -c %a /mnt/c 2>/dev/null || echo "") if [ -n "$mnt_mode" ] && [ $((0$mnt_mode & 0004)) -ne 0 ]; then echo " ${UI_YELLOW}WARNING: /mnt/c is still world-readable (mode $mnt_mode) — the agent${UI_NC}" - echo " ${UI_YELLOW}can read your Windows profile. If the wsl.conf restriction was just${UI_NC}" - echo " ${UI_YELLOW}configured, it needs 'wsl --shutdown' from Windows + reopening the distro${UI_NC}" - echo " ${UI_YELLOW}to take effect. opencode will warn on every start until then.${UI_NC}" + echo " ${UI_YELLOW}can read your Windows profile. If you just applied the [automount]${UI_NC}" + echo " ${UI_YELLOW}snippet to /etc/wsl.conf, it needs 'wsl --shutdown' from Windows${UI_NC}" + echo " ${UI_YELLOW}+ reopening the distro to take effect.${UI_NC}" + echo " ${UI_YELLOW}opencode will warn on every start until then.${UI_NC}" echo "" fi fi diff --git a/files/opencode-permissions-kit-lib/bin/opencode-as-opencode b/files/opencode-permissions-kit-lib/bin/opencode-as-opencode index 373820c..0eb84c1 100755 --- a/files/opencode-permissions-kit-lib/bin/opencode-as-opencode +++ b/files/opencode-permissions-kit-lib/bin/opencode-as-opencode @@ -196,22 +196,22 @@ if [ -d /mnt/c ]; then note "${YELLOW} [automount]${NC}" note "${YELLOW} enabled = true${NC}" note "${YELLOW} options = \"uid=,gid=,dmask=027,fmask=037\"${NC}" - note "${YELLOW}then 'wsl --shutdown' from Windows (install.sh can apply this for you).${NC}" + note "${YELLOW}then 'wsl --shutdown' from Windows (apply it yourself — the kit never edits wsl.conf).${NC}" fi note "" fi - # Browser bridge sanity (issue #91): on a hardened /mnt/c the opencode - # user cannot execute powershell.exe, and opencode's device-login flows - # (`console login`, `auth login`) die on that spawn error. The kit's - # browser bridge redirects the bundled `open` package's powershell - # lookup to a harmless stand-in via the kit comment block in - # /etc/wsl.conf. Warn when the mount is restricted but the bridge is - # not in place (broken deploy, hand-edited wsl.conf). + # Browser bridge sanity (issues #91, #100): on a hardened /mnt/c the + # opencode user cannot execute powershell.exe, and opencode's device-login + # flows (`console login`, `auth login`) die on that spawn error. The kit's + # browser bridge redirects the bundled `open` package's powershell lookup + # to a harmless stand-in via the kit comment block in /etc/wsl.conf + # (explicit opt-in — the kit never edits wsl.conf implicitly). Warn when + # the mount is restricted but the bridge is not in place. if [ -n "$mnt_mode" ] && [ $((0$mnt_mode & 0004)) -eq 0 ]; then if ! [ -x /usr/local/lib/opencode-permissions-kit/wsl/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe ] \ || ! grep -q '^# opencode permissions kit browser bridge -- begin$' /etc/wsl.conf 2>/dev/null; then note "${YELLOW}WARNING: browser bridge missing — 'console login'/'auth login' would fail${NC}" - note "${YELLOW}on this restricted /mnt/c. Re-run install.sh (or 'opk update') to set it up.${NC}" + note "${YELLOW}on this restricted /mnt/c. Enable it: sudo opk wsl-add-opencode-1-fix${NC}" note "" fi fi diff --git a/files/opencode-permissions-kit-lib/bin/opk b/files/opencode-permissions-kit-lib/bin/opk index 12b09c1..30ef61c 100755 --- a/files/opencode-permissions-kit-lib/bin/opk +++ b/files/opencode-permissions-kit-lib/bin/opk @@ -10,6 +10,9 @@ # opk uninstall -> management/uninstall.sh (runs as your user, # asks for sudo itself) # +# wsl-add-opencode-1-fix is implemented inline (explicit-consent wsl.conf +# write, docs/design/wsl-conf-consent.md). +# # Everything after the subcommand is passed through to the script, so all # script flags work unchanged (see docs/reference/cli.md). set -u @@ -73,10 +76,16 @@ Commands: ddev-hosts-check [dir] List this project's ddev hostnames missing from the Windows hosts file handover ... Recursively hand file ownership to you (me) - or the agent user (opencode) — the sharing - group stays, so both sides keep access. For - mixed-owner trees, e.g. after builds by - both users. --dry-run shows the plan + or the agent user (opencode) — the sharing + group stays, so both sides keep access. For + mixed-owner trees, e.g. after builds by + both users. --dry-run shows the plan + wsl-add-opencode-1-fix Write the WSL browser-bridge carrier into + /etc/wsl.conf (sudo). The only kit command + that touches wsl.conf — explicit consent: + install/update never do. Keeps opencode + 1.x device logins alive on a hardened + /mnt/c (opencode 2.x needs no bridge) uninstall [--yes] [--dry-run] Remove the kit (runs as your user) help Show this help @@ -160,6 +169,39 @@ case "$cmd" in ;; esac ;; + wsl-add-opencode-1-fix) + # Explicit consent for the /etc/wsl.conf browser-bridge carrier + # (docs/design/wsl-conf-consent.md): install/update never write + # wsl.conf — only THIS command, run by the user, does. It (re)deploys + # the stand-in tree from the library and writes the kit comment + # block (pure comments for WSL, issue #100); a legacy 0.0.36 + # section is cleaned up on the way, and only kit content is ever + # removed or added. Auto-sudo like config/update. + [ -f "$LIBDIR/sh/wsl-browser-bridge.sh" ] || { ui_error "$LIBDIR/sh/wsl-browser-bridge.sh not found — is the kit installed?"; exit 1; } + if [ "$(id -u)" -ne 0 ]; then + exec sudo sh "$LIBDIR/bin/opk" "$cmd" + fi + # Running as root: the bridge helper's writes need no sudo prefix. + OPK_WSL_SUDO="" + export OPK_WSL_SUDO + # shellcheck disable=SC1090 + . "$LIBDIR/sh/wsl-browser-bridge.sh" + browser_bridge_is_wsl || { ui_error "not WSL — the browser bridge is WSL-only"; exit 1; } + # Redeploy the stand-in when the source is in the library; older + # deploys (update before browser-bridge joined the library) may only + # have the tree — fine, the carrier is the missing piece then. + if [ -f "$LIBDIR/bin/browser-bridge" ]; then + browser_bridge_deploy_tree "$LIBDIR/bin/browser-bridge" "$LIBDIR" + elif [ ! -x "$LIBDIR/wsl/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe" ]; then + ui_error "stand-in missing and no $LIBDIR/bin/browser-bridge — run 'sudo opk update' first" + exit 1 + fi + browser_bridge_write_conf "$LIBDIR" + ui_success "WSL browser bridge active — opencode 1.x device logins survive a hardened /mnt/c" + ui_detail "carrier written to /etc/wsl.conf as a comment block (WSL ignores it;" + ui_detail "takes effect immediately, no 'wsl --shutdown' needed)" + ui_detail "'opk uninstall' removes the block again" + ;; handover) # Generic ownership switch between the two kit users, for # mixed-owner trees (e.g. a checkout where BOTH the developer and diff --git a/files/opencode-permissions-kit-lib/management/status.sh b/files/opencode-permissions-kit-lib/management/status.sh index ae59d44..e82c08f 100755 --- a/files/opencode-permissions-kit-lib/management/status.sh +++ b/files/opencode-permissions-kit-lib/management/status.sh @@ -466,13 +466,17 @@ if [ -d /mnt/c ]; then # Browser bridge (issues #91, #100): on the restricted mount the # opencode user cannot execute powershell.exe — without the bridge, # device logins (`console login` / `auth login`) die on the spawn - # error. + # error. The wsl.conf carrier is an explicit opt-in (the kit never + # edits wsl.conf implicitly). if [ -x "$LIBDIR/wsl/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe" ] \ && grep -q '^# opencode permissions kit browser bridge -- begin$' /etc/wsl.conf 2>/dev/null; then ui_kv "browser bridge" "deployed (opencode logins survive the hardened mount)" "$UI_GREEN" + elif [ -x "$LIBDIR/wsl/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe" ]; then + ui_kv "browser bridge" "stand-in only — no wsl.conf carrier ('console login' fails on this mount)" "$UI_RED" + ui_detail "fix: sudo opk wsl-add-opencode-1-fix (explicit consent — the kit never edits wsl.conf itself)" else ui_kv "browser bridge" "missing — 'console login'/'auth login' fails on this mount" "$UI_RED" - ui_detail "fix: re-run install.sh or 'opk update' (deploys the wsl.conf bridge block + stand-in)" + ui_detail "fix: sudo opk update (deploys the stand-in), then sudo opk wsl-add-opencode-1-fix (wsl.conf carrier)" fi fi fi diff --git a/files/opencode-permissions-kit-lib/management/uninstall.sh b/files/opencode-permissions-kit-lib/management/uninstall.sh index a50fc5a..4cceb23 100755 --- a/files/opencode-permissions-kit-lib/management/uninstall.sh +++ b/files/opencode-permissions-kit-lib/management/uninstall.sh @@ -186,18 +186,36 @@ fi echo "" echo "--- Removing WSL browser bridge ---" -# (issues #91, #100) Strip the kit-managed comment block — and the legacy -# 0.0.36 [opencode-permissions-kit] section — from /etc/wsl.conf (every -# other line is preserved) and drop the stand-in tree under the library -# before it goes. Safe no-op when neither exists (non-WSL / pre-bridge -# installs). +# (issues #91, #100) Consent policy (docs/design/wsl-conf-consent.md): the +# kit never edits /etc/wsl.conf on its own — not even here. When kit-owned +# content is present (the bridge comment block, or the legacy 0.0.36 +# section), ASK before removing it; --yes assumes yes, declining leaves +# the file untouched and prints exactly what to delete by hand (after this +# uninstall 'opk' is gone — there is no cleanup command anymore). The +# stand-in tree under the library always goes (it dies with the library). if [ "$DRY_RUN" = true ]; then - echo " [DRY] remove kit comment block from /etc/wsl.conf (rewrite)" + echo " [DRY] ask: remove the kit-owned wsl.conf bridge block? (default: yes)" echo " [DRY] sudo rm -rf /usr/local/lib/opencode-permissions-kit/wsl" else - browser_bridge_remove "/usr/local/lib/opencode-permissions-kit" - echo "WSL browser bridge removed." - log "wsl browser bridge removed (/etc/wsl.conf bridge block + library wsl/ tree)" + if grep -q '^# opencode permissions kit browser bridge -- begin$' /etc/wsl.conf 2>/dev/null \ + || grep -q '^\[opencode-permissions-kit\]$' /etc/wsl.conf 2>/dev/null; then + if [ "$(prompt_yn "Remove the kit's wsl.conf bridge block? (kit-owned comments only; your own entries stay)" "y")" = "y" ]; then + browser_bridge_remove "/usr/local/lib/opencode-permissions-kit" + echo "WSL browser bridge removed (wsl.conf block + stand-in tree)." + log "wsl browser bridge removed (/etc/wsl.conf bridge block + library wsl/ tree, consented)" + else + sudo rm -rf /usr/local/lib/opencode-permissions-kit/wsl + echo "wsl.conf left untouched — remove the kit block yourself (WSL never" + echo "warns about it; no 'wsl --shutdown' needed). It spans these lines:" + grep -n '^# opencode permissions kit browser bridge -- begin$\|^# opencode permissions kit browser bridge -- end$' /etc/wsl.conf 2>/dev/null | sed 's/^/ /' + grep -q '^\[opencode-permissions-kit\]$' /etc/wsl.conf 2>/dev/null \ + && echo " plus the legacy [opencode-permissions-kit] section (through its root = line)" + log "wsl browser bridge: wsl.conf block left in place (user declined)" + fi + else + browser_bridge_remove "/usr/local/lib/opencode-permissions-kit" + log "wsl browser bridge removed (no wsl.conf content found; library wsl/ tree dropped)" + fi fi echo "" diff --git a/files/opencode-permissions-kit-lib/management/update.sh b/files/opencode-permissions-kit-lib/management/update.sh index 7cb4519..f74bd65 100755 --- a/files/opencode-permissions-kit-lib/management/update.sh +++ b/files/opencode-permissions-kit-lib/management/update.sh @@ -333,6 +333,9 @@ sudo cp "$FILES_ROOT/opencode-permissions-kit-lib/sh/ddev-migrate.sh" "$LIBDIR/ sudo cp "$FILES_ROOT/opencode-permissions-kit-lib/bin/ddev-migrate" "$LIBDIR/bin/ddev-migrate" sudo cp "$FILES_ROOT/opencode-permissions-kit-lib/sh/fs-baseline.sh" "$LIBDIR/sh/fs-baseline.sh" sudo cp "$FILES_ROOT/opencode-permissions-kit-lib/sh/ddev-hosts.sh" "$LIBDIR/sh/ddev-hosts.sh" +# browser-bridge stand-in source (deploys into the wsl/ tree; source of +# 'opk wsl-add-opencode-1-fix' re-runs) +sudo cp "$FILES_ROOT/opencode-permissions-kit-lib/bin/browser-bridge" "$LIBDIR/bin/browser-bridge" # WSL browser bridge (issues #91, #100): the deploy helper joins the # library; the stand-in tree + /etc/wsl.conf comment block are (re)applied # below after the library is in place. @@ -349,7 +352,8 @@ sudo chmod 755 "$LIBDIR/py/tui-register.py" sudo chmod 755 "$LIBDIR/bin/opencode-as-opencode" "$LIBDIR/bin/opk" "$LIBDIR/py/jsonc-parser.py" \ "$LIBDIR/sh/log.sh" "$LIBDIR/sh/ui.sh" "$LIBDIR/sh/shell-warn.sh" "$LIBDIR/bin/setup-container-backend" \ "$LIBDIR/management/config.sh" "$LIBDIR/management/update.sh" "$LIBDIR/management/status.sh" "$LIBDIR/management/uninstall.sh" \ - "$LIBDIR/bin/socket-check" "$LIBDIR/bin/cwd-check" "$LIBDIR/bin/ddev-as-opencode" "$LIBDIR/bin/ddev-migrate" + "$LIBDIR/bin/socket-check" "$LIBDIR/bin/cwd-check" "$LIBDIR/bin/ddev-as-opencode" "$LIBDIR/bin/ddev-migrate" \ + "$LIBDIR/bin/browser-bridge" # --- old-layout cleanup (0.0.29 streamline, docs/design/streamline.md §5) -------- # Remove the union of pre-0.0.29 deployed paths after the new layout is in @@ -370,18 +374,26 @@ ui_success "library re-deployed: $LIBDIR" log "library re-deployed: $LIBDIR (old-layout cleanup applied)" # --- WSL browser bridge (issues #91, #100) ------------------------------------- -# (Re)apply the stand-in tree + /etc/wsl.conf comment block so existing WSL -# installs pick the fix up on `opk update` (write_conf also heals a stale -# root = value and migrates the broken 0.0.36 section away). No-op on -# non-WSL hosts; inert on unhardened /mnt/c. +# (Re)apply the stand-in tree and strip the legacy 0.0.36 section (kit-owned +# regression cleanup). The /etc/wsl.conf carrier is NEVER written here — +# the kit does not edit wsl.conf implicitly (docs/design/wsl-conf-consent.md); +# the user opts in via 'sudo opk wsl-add-opencode-1-fix'. An existing +# carrier stays untouched and keeps working. [ -f "$FILES_ROOT/opencode-permissions-kit-lib/sh/wsl-browser-bridge.sh" ] && . "$FILES_ROOT/opencode-permissions-kit-lib/sh/wsl-browser-bridge.sh" [ -f "$LIBDIR/sh/wsl-browser-bridge.sh" ] && . "$LIBDIR/sh/wsl-browser-bridge.sh" command -v browser_bridge_is_wsl >/dev/null 2>&1 || browser_bridge_is_wsl() { return 1; } command -v browser_bridge_install >/dev/null 2>&1 || browser_bridge_install() { :; } if browser_bridge_is_wsl; then browser_bridge_install "$FILES_ROOT" "$LIBDIR" - ui_success "WSL browser bridge re-applied (opencode console/auth login fix)" - log "wsl browser bridge re-applied: $LIBDIR/wsl + comment block in /etc/wsl.conf" + if grep -q '^# opencode permissions kit browser bridge -- begin$' /etc/wsl.conf 2>/dev/null; then + ui_success "WSL browser bridge re-applied (carrier present — fully active)" + log "wsl browser bridge re-applied: $LIBDIR/wsl (carrier present)" + else + ui_success "WSL browser bridge stand-in re-applied (no wsl.conf carrier)" + ui_detail "enable the opencode 1.x login fix yourself (the kit does not edit /etc/wsl.conf):" + ui_detail " sudo opk wsl-add-opencode-1-fix" + log "wsl browser bridge stand-in re-applied: $LIBDIR/wsl (carrier left to 'opk wsl-add-opencode-1-fix')" + fi fi # --- re-link wrapper + cli dispatcher ------------------------------------------ @@ -512,7 +524,8 @@ if [ -d /mnt/c ]; then echo " [automount]" echo " enabled = true" echo " options = \"uid=$(id -u "$DEFAULT_USER" 2>/dev/null || echo ''),gid=$(id -g "$DEFAULT_USER" 2>/dev/null || echo ''),dmask=027,fmask=037\"" - echo " ${UI_YELLOW}then 'wsl --shutdown' from Windows. install.sh can apply this for you (interactive).${UI_NC}" + echo " ${UI_YELLOW}then 'wsl --shutdown' from Windows. The kit never edits /etc/wsl.conf —${UI_NC}" + echo " ${UI_YELLOW}apply the snippet yourself.${UI_NC}" fi fi fi diff --git a/files/opencode-permissions-kit-lib/sh/wsl-browser-bridge.sh b/files/opencode-permissions-kit-lib/sh/wsl-browser-bridge.sh index 3e592cb..4d1e0fb 100755 --- a/files/opencode-permissions-kit-lib/sh/wsl-browser-bridge.sh +++ b/files/opencode-permissions-kit-lib/sh/wsl-browser-bridge.sh @@ -17,6 +17,14 @@ # forwards to the real powershell.exe whenever the calling user may # execute it and exits 0 otherwise — see bin/browser-bridge. # +# Consent policy (docs/design/wsl-conf-consent.md): the kit NEVER writes +# /etc/wsl.conf implicitly. install.sh/update.sh deploy only the stand-in +# tree and strip kit-owned legacy content (the broken 0.0.36 section); +# writing the carrier block is reserved for the explicit user command +# `sudo opk wsl-add-opencode-1-fix` (bin/opk), which calls +# browser_bridge_write_conf. Existing carriers are never touched by +# updates — removal happens only through `opk uninstall`. +# # Why a comment block and not an INI section (issue #100): WSL's wsl.conf # parser (src/shared/configfile/configfile.cpp) only accepts section names # matching [A-Za-z][A-Za-z0-9]* and keys matching the same charset — the @@ -95,15 +103,14 @@ browser_bridge_write_conf() { rm -f "$_bb_tmp" } -# Deploy the stand-in at the path open() computes and register it in -# /etc/wsl.conf. is the kit files/ tree holding -# opencode-permissions-kit-lib/bin/browser-bridge; is the deployed -# library root (/usr/local/lib/opencode-permissions-kit). -browser_bridge_install() { - _bb_files_root="$1" +# Deploy the stand-in tree at the path open() computes. is the +# browser-bridge script (files/ tree or deployed library); is the +# deployed library root (/usr/local/lib/opencode-permissions-kit). +# Conf-free by design — this touches only kit-owned paths. +browser_bridge_deploy_tree() { + _bb_src="$1" _bb_libdir="$2" browser_bridge_is_wsl || return 0 - _bb_src="$_bb_files_root/opencode-permissions-kit-lib/bin/browser-bridge" [ -f "$_bb_src" ] || return 0 ${OPK_WSL_SUDO-sudo} mkdir -p "$_bb_libdir/wsl/c/Windows/System32/WindowsPowerShell/v1.0" # shellcheck disable=SC2174 @@ -111,7 +118,41 @@ browser_bridge_install() { "$_bb_libdir/wsl/c/Windows/System32" "$_bb_libdir/wsl/c/Windows/System32/WindowsPowerShell" \ "$_bb_libdir/wsl/c/Windows/System32/WindowsPowerShell/v1.0" ${OPK_WSL_SUDO-sudo} install -m 755 "$_bb_src" "$_bb_libdir/wsl/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe" - browser_bridge_write_conf "$_bb_libdir" +} + +# Strip ONLY the legacy 0.0.36 hyphen section (kit-owned regression +# cleanup, issue #100 — restores WSL's ability to parse the file). Never +# writes anything new; a valid carrier block is left untouched. +browser_bridge_strip_legacy() { + _bb_conf="${OPK_WSL_CONF-/etc/wsl.conf}" + [ -f "$_bb_conf" ] || return 0 + grep -q '^\[opencode-permissions-kit\]$' "$_bb_conf" || return 0 + _bb_tmp="$(mktemp)" + awk ' + in_legacy { + if ($0 ~ /^\[/) { in_legacy = 0; print; next } + next + } + /^\[opencode-permissions-kit\]$/ { in_legacy = 1; next } + { print } + ' "$_bb_conf" > "$_bb_tmp" + ${OPK_WSL_SUDO-sudo} cp "$_bb_tmp" "$_bb_conf" + rm -f "$_bb_tmp" +} + +# Deploy the stand-in tree and clean up kit-owned legacy wsl.conf content +# (install.sh/update.sh entry point). Deliberately does NOT write the +# carrier block — that is the user's explicit call (`sudo opk +# wsl-add-opencode-1-fix`, see the consent policy above). +# is the kit files/ tree holding +# opencode-permissions-kit-lib/bin/browser-bridge; is the deployed +# library root. +browser_bridge_install() { + _bb_files_root="$1" + _bb_libdir="$2" + browser_bridge_is_wsl || return 0 + browser_bridge_deploy_tree "$_bb_files_root/opencode-permissions-kit-lib/bin/browser-bridge" "$_bb_libdir" + browser_bridge_strip_legacy } # Remove the kit block (and any legacy 0.0.36 section) from /etc/wsl.conf diff --git a/tests/unit/test-browser-bridge.sh b/tests/unit/test-browser-bridge.sh index f455959..1008377 100755 --- a/tests/unit/test-browser-bridge.sh +++ b/tests/unit/test-browser-bridge.sh @@ -3,14 +3,15 @@ # 1. bin/browser-bridge stand-in: forwards to the real powershell.exe # when the caller may execute it, exit 0 otherwise (the opencode user # on a hardened /mnt/c — the login flow must survive the spawn). -# 2. sh/wsl-browser-bridge.sh: the /etc/wsl.conf rewrite (driven through -# the REAL functions via the OPK_WSL_CONF/OPK_WSL_SUDO/OPK_WSL_FORCE -# overrides, same convention as DDEV_WIN_HOSTS/FS_SUDO) prepends a +# 2. sh/wsl-browser-bridge.sh: install deploys ONLY the stand-in tree and +# never writes /etc/wsl.conf (consent policy, +# docs/design/wsl-conf-consent.md) — it merely strips the broken +# legacy 0.0.36 section. The carrier write (the opk +# wsl-add-opencode-1-fix path, browser_bridge_write_conf) prepends a # pure-comment kit block (WSL-silent by construction, issue #100) # whose raw-CR carrier line wins open@<=10's `root =` scan, preserves -# every foreign line, is idempotent, migrates the broken 0.0.36 -# section away, and the remove path restores the original file -# byte-for-byte. +# every foreign line, is idempotent, and the remove path restores the +# original file byte-for-byte. # Run: sh tests/unit/test-browser-bridge.sh set -u @@ -143,9 +144,9 @@ fi chmod 755 "$WORK/fakec/Windows/System32/WindowsPowerShell/v1.0/powershell.exe" echo "" -echo "--- wsl.conf bridge block rewrite (real functions, overridden paths) ---" +echo "--- wsl-browser-bridge.sh: install/update path (never touches wsl.conf) ---" -# Pre-existing user conf the bridge must preserve byte-for-byte: +# Pre-existing user conf install must leave alone, byte-for-byte: cat > "$CONF" <<'CONF' [boot] systemd=true @@ -159,7 +160,7 @@ options = "uid=1000,gid=1000,dmask=027,fmask=037" CONF cp "$CONF" "$WORK/orig.conf" -# 4. full install: stand-in tree + conf block +# 4. full install: stand-in tree deployed BB_CALL='browser_bridge_install "$1" "$2"' bb "$FILES_ROOT" "$LIBDIR" _SHIM="$LIBDIR/wsl/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe" if [ -f "$_SHIM" ] && [ -x "$_SHIM" ]; then @@ -175,82 +176,97 @@ else fail "deployed stand-in differs from bin/browser-bridge" fi -# 6. the kit block lands at the TOP (open's scan is first-match) +# 6. consent policy: install NEVER writes /etc/wsl.conf +if diff -q "$CONF" "$WORK/orig.conf" >/dev/null 2>&1; then + pass "install leaves wsl.conf byte-for-byte untouched (consent policy)" +else + fail "install wrote to wsl.conf — only 'opk wsl-add-opencode-1-fix' may" +fi + +# 7. install still strips the broken legacy 0.0.36 section (kit-owned +# regression cleanup, issue #100) without adding the carrier block +printf '[opencode-permissions-kit]\n# Managed by the opencode permissions kit. WSL ignores this section; it\n# redirects the powershell.exe lookup (issue #91).\nroot = /usr/local/lib/opencode-permissions-kit/wsl\n\n[boot]\nsystemd=true\n' > "$CONF" +BB_CALL='browser_bridge_install "$1" "$2"' bb "$FILES_ROOT" "$LIBDIR" +printf '[boot]\nsystemd=true\n' > "$WORK/strip.expect" +if ! grep -q 'opencode-permissions-kit\]' "$CONF" \ + && ! grep -q '^# opencode permissions kit browser bridge -- begin$' "$CONF" \ + && diff -q "$CONF" "$WORK/strip.expect" >/dev/null 2>&1; then + pass "install strips the legacy 0.0.36 section, adds nothing (issue #100)" +else + fail "legacy-section cleanup on install broken" +fi + +echo "" +echo "--- wsl.conf carrier write (opk wsl-add-opencode-1-fix path) ---" + +# Fresh user conf; the carrier write (write_conf) runs it through the REAL +# functions via the OPK_WSL_CONF/OPK_WSL_SUDO/OPK_WSL_FORCE overrides. +cat > "$CONF" <<'CONF' +[boot] +systemd=true + +[user] +default=infotest + +[automount] +enabled = true +options = "uid=1000,gid=1000,dmask=027,fmask=037" +CONF +cp "$CONF" "$WORK/orig.conf" +BB_CALL='browser_bridge_write_conf "$1"' bb "$LIBDIR" + +# 8. the kit block lands at the TOP (open's scan is first-match) if head -1 "$CONF" | grep -q '^# opencode permissions kit browser bridge -- begin$'; then pass "kit block is the first thing in wsl.conf (wins open's first-match scan)" else fail "kit block is not at the top of wsl.conf" fi -# 7. the carrier line is a raw-CR comment: '# ---...\rroot = /wsl' +# 9. the carrier line is a raw-CR comment: '# ---...\rroot = /wsl' if printf '%s\n' "$(sed -n '9p' "$CONF" | cat -v)" | grep -q '\^Mroot = '"$LIBDIR"'/wsl$'; then pass "carrier line is comment + raw CR + root = /wsl" else fail "carrier line malformed (must be # ---...root = /wsl)" fi -# 8. open@10.1.2 scan resolves to the stand-in path +# 10. open@10.1.2 scan resolves to the stand-in path if opk_scan_ok "$CONF" "$LIBDIR"; then pass "open@10.1.2 scan resolves powershell.exe to the kit stand-in" else fail "open@10.1.2 scan does not resolve to the kit stand-in" fi -# 9. newer open (wsl-utils, line-based) skips the whole block +# 11. newer open (wsl-utils, line-based) skips the whole block if wslutils_ignores_block "$CONF"; then pass "wsl-utils line parser ignores the kit block (access-check fallback)" else fail "wsl-utils parser picks up the kit carrier (would break its fallback)" fi -# 10. WSL itself sees no section and no key from the kit +# 12. WSL itself sees no section and no key from the kit if wsl_sees_nothing "$CONF"; then pass "WSL sees only comments from the kit (no section, no key, no warning)" else fail "kit content would be parsed by WSL (warning/abort risk, issue #100)" fi -# 11. every foreign line survives below the block (10 block lines + blank) +# 13. every foreign line survives below the block (10 block lines + blank) if tail -n +12 "$CONF" | diff -q - "$WORK/orig.conf" >/dev/null 2>&1; then pass "foreign wsl.conf content preserved byte-for-byte below the block" else fail "foreign wsl.conf content altered by the rewrite" fi -# 12. idempotent: a second install run changes nothing -cp "$CONF" "$WORK/before-second.conf" -BB_CALL='browser_bridge_install "$1" "$2"' bb "$FILES_ROOT" "$LIBDIR" -if diff -q "$CONF" "$WORK/before-second.conf" >/dev/null 2>&1; then - pass "rewrite is idempotent (second run is a no-op)" -else - fail "rewrite is not idempotent (block duplicated or content changed)" -fi - -# 13. migration from the broken 0.0.36 section (issue #100): the hyphenated -# section is replaced by the comment block; user content stays intact -cat > "$CONF" <<'CONF' -[opencode-permissions-kit] -# Managed by the opencode permissions kit. WSL ignores this section; it -# redirects the powershell.exe lookup of the `open` npm package (bundled in -# opencode) to the kit stand-in so device logins survive a hardened /mnt/c -# (issue #91). Do not add other keys here — uninstall removes the section. -root = /usr/local/lib/opencode-permissions-kit/wsl - -[boot] -systemd=true -CONF +# 14. idempotent: a second carrier write changes nothing +cp "$CONF" "$WORK/after-first.conf" BB_CALL='browser_bridge_write_conf "$1"' bb "$LIBDIR" -printf '[boot]\nsystemd=true\n' > "$WORK/migrate.expect" -if ! grep -q 'opencode-permissions-kit\]' "$CONF" \ - && grep -q '^# opencode permissions kit browser bridge -- begin$' "$CONF" \ - && opk_scan_ok "$CONF" "$LIBDIR" \ - && tail -n +12 "$CONF" | diff -q - "$WORK/migrate.expect" >/dev/null 2>&1; then - pass "0.0.36 hyphen section migrated to the comment block (issue #100)" +if diff -q "$CONF" "$WORK/after-first.conf" >/dev/null 2>&1; then + pass "carrier write is idempotent (second run is a no-op)" else - fail "0.0.36 section migration broken" + fail "carrier write is not idempotent (block duplicated or content changed)" fi -# 14. a stale root = value heals (old kit block replaced, not appended) +# 15. a stale root = value heals (old kit block replaced, not appended) printf '# opencode permissions kit browser bridge -- begin\n# stale deploy\n# ----\rroot = /old/lib/wsl\n# opencode permissions kit browser bridge -- end\n\n[boot]\nsystemd=true\n' > "$CONF" BB_CALL='browser_bridge_write_conf "$1"' bb "$LIBDIR" if ! grep -q '/old/lib' "$CONF" && opk_scan_ok "$CONF" "$LIBDIR"; then @@ -259,7 +275,7 @@ else fail "stale kit block survives the rewrite" fi -# 15. the user's own [automount] root = (if they ever set one) stays intact +# 16. the user's own [automount] root = (if they ever set one) stays intact # BELOW the kit block — open's first-match still resolves to the kit printf '[automount]\nroot = /mnt/\nenabled = true\n' > "$WORK/orig-automount.conf" cp "$WORK/orig-automount.conf" "$CONF" @@ -271,7 +287,21 @@ else fail "rewrite damages a foreign automount root = line" fi -# 16. fresh install without any pre-existing wsl.conf works (no read crash) +# 17. carrier write on a legacy 0.0.36 conf (opk wsl-add-opencode-1-fix on a +# broken install): hyphen section migrated away, user content intact +printf '[opencode-permissions-kit]\n# Managed by the opencode permissions kit. WSL ignores this section; it\n# redirects the powershell.exe lookup of the `open` npm package (bundled in\n# opencode) to the kit stand-in so device logins survive a hardened /mnt/c\n# (issue #91). Do not add other keys here — uninstall removes the section.\nroot = /usr/local/lib/opencode-permissions-kit/wsl\n\n[boot]\nsystemd=true\n' > "$CONF" +BB_CALL='browser_bridge_write_conf "$1"' bb "$LIBDIR" +printf '[boot]\nsystemd=true\n' > "$WORK/migrate.expect" +if ! grep -q 'opencode-permissions-kit\]' "$CONF" \ + && grep -q '^# opencode permissions kit browser bridge -- begin$' "$CONF" \ + && opk_scan_ok "$CONF" "$LIBDIR" \ + && tail -n +12 "$CONF" | diff -q - "$WORK/migrate.expect" >/dev/null 2>&1; then + pass "0.0.36 hyphen section migrated to the comment block (issue #100)" +else + fail "0.0.36 section migration broken" +fi + +# 18. fresh install without any pre-existing wsl.conf works (no read crash) rm -f "$CONF" BB_CALL='browser_bridge_write_conf "$1"' bb "$LIBDIR" if [ -f "$CONF" ] && opk_scan_ok "$CONF" "$LIBDIR"; then @@ -280,7 +310,7 @@ else fail "fresh-install path broken" fi -# 17. remove restores the original conf byte-for-byte and drops the tree +# 19. remove restores the original conf byte-for-byte and drops the tree cat > "$CONF" <<'CONF' [boot] systemd=true @@ -304,7 +334,7 @@ else fail "stand-in tree survives browser_bridge_remove" fi -# 18. remove also strips a legacy 0.0.36 section (uninstall migration) +# 20. remove also strips a legacy 0.0.36 section (uninstall migration) printf '[opencode-permissions-kit]\n# Managed by the opencode permissions kit. WSL ignores this section; it\n# redirects the powershell.exe lookup (issue #91). Do not add other keys\n# here — uninstall removes the section.\nroot = /usr/local/lib/opencode-permissions-kit/wsl\n\n[boot]\nsystemd=true\n' > "$CONF" BB_CALL='browser_bridge_remove "$1"' bb "$LIBDIR" if printf '[boot]\nsystemd=true\n' | diff -q - "$CONF" >/dev/null 2>&1; then @@ -313,7 +343,7 @@ else fail "remove leaves legacy section residue" fi -# 19. remove is safe on a bridge-free conf (no kit block, no tree) +# 21. remove is safe on a bridge-free conf (no kit block, no tree) BB_CALL='browser_bridge_remove "$1"' bb "$LIBDIR" if printf '[boot]\nsystemd=true\n' | diff -q - "$CONF" >/dev/null 2>&1; then pass "remove is a no-op when no kit block exists" @@ -324,21 +354,23 @@ fi echo "" echo "--- helper contract ---" -# 20. sourcing defines the API and executes nothing (deploy is caller-side) +# 22. sourcing defines the API and executes nothing (deploy is caller-side) _api=$(OPK_WSL_CONF="$CONF" OPK_WSL_SUDO="" sh -c ' . "$1" command -v browser_bridge_is_wsl >/dev/null 2>&1 \ + && command -v browser_bridge_deploy_tree >/dev/null 2>&1 \ + && command -v browser_bridge_strip_legacy >/dev/null 2>&1 \ && command -v browser_bridge_write_conf >/dev/null 2>&1 \ && command -v browser_bridge_install >/dev/null 2>&1 \ && command -v browser_bridge_remove >/dev/null 2>&1 \ && echo ok' _ "$BRIDGE_SH") if [ "$_api" = "ok" ]; then - pass "helper exports the four bridge functions (sourced, not executed)" + pass "helper exports the six bridge functions (sourced, not executed)" else fail "helper API incomplete" fi -# 21. without OPK_WSL_FORCE the is_wsl probe delegates to /proc/version +# 23. without OPK_WSL_FORCE the is_wsl probe delegates to /proc/version # (whatever that says on the host — WSL dev boxes included) _raw=no grep -qi microsoft /proc/version 2>/dev/null && _raw=yes @@ -353,7 +385,7 @@ else fail "is_wsl ignores /proc/version without FORCE (probe=$_probe raw=$_raw)" fi -# 22. install no-ops (gracefully) when the stand-in source is absent — +# 24. install no-ops (gracefully) when the stand-in source is absent — # e.g. a partial files/ tree if OPK_WSL_CONF="$CONF" OPK_WSL_SUDO="" OPK_WSL_FORCE=1 \ sh -c '. "$1" && browser_bridge_install "$2" "$3"' _ "$BRIDGE_SH" "$WORK/no-such-files-root" "$LIBDIR" >/dev/null 2>&1; then @@ -365,7 +397,7 @@ fi echo "" echo "--- diagnostics (debug trace + tty hint) ---" -# 23. without the debug env the no-op path is completely silent (opencode's +# 25. without the debug env the no-op path is completely silent (opencode's # spawn would show any stdout/stderr noise in the login dialog) _out="$(OPK_WSL_C_ROOT="$WORK/nowhere" "$BRIDGE_BIN" -EncodedCommand X 2>&1)" if [ -z "$_out" ]; then @@ -374,7 +406,7 @@ else fail "no-op path leaks output without the debug env (got: '$_out')" fi -# 24. with the debug env the no-op path traces the decision +# 26. with the debug env the no-op path traces the decision if OPK_BROWSER_BRIDGE_DEBUG=1 OPK_WSL_C_ROOT="$WORK/nowhere" "$BRIDGE_BIN" -EncodedCommand X 2>&1 \ | grep -q "browser-bridge\[debug\]: no real powershell reachable"; then pass "debug env traces the no-op decision" @@ -382,7 +414,7 @@ else fail "debug env does not trace the no-op decision" fi -# 25. with the debug env the forwarding path traces target + decision +# 27. with the debug env the forwarding path traces target + decision if OPK_BROWSER_BRIDGE_DEBUG=1 OPK_WSL_C_ROOT="$WORK/fakec" "$BRIDGE_BIN" 2>&1 \ | grep -q "browser-bridge\[debug\]: forwarding to $WORK/fakec"; then pass "debug env traces the forwarding decision" @@ -390,7 +422,7 @@ else fail "debug env does not trace the forwarding decision" fi -# 26. the no-op hint goes to the controlling terminal, not stdout/stderr: +# 28. the no-op hint goes to the controlling terminal, not stdout/stderr: # under a pty (script -e) it must appear, detached it must not. if command -v script >/dev/null 2>&1; then _pty_out="$(script -qec "OPK_WSL_C_ROOT='$WORK/nowhere' '$BRIDGE_BIN'" /dev/null 2>&1 | tr -d '\r')" diff --git a/tests/unit/test-wsl-exposure.sh b/tests/unit/test-wsl-exposure.sh index 6b6374c..b620fc8 100755 --- a/tests/unit/test-wsl-exposure.sh +++ b/tests/unit/test-wsl-exposure.sh @@ -22,6 +22,7 @@ STATUS="$SCRIPT_DIR/../../files/opencode-permissions-kit-lib/management/status.s INSTALL="$SCRIPT_DIR/../../files/install.sh" UPDATE="$SCRIPT_DIR/../../files/opencode-permissions-kit-lib/management/update.sh" WRAPPER="$SCRIPT_DIR/../../files/opencode-permissions-kit-lib/bin/opencode-as-opencode" +OPK="$SCRIPT_DIR/../../files/opencode-permissions-kit-lib/bin/opk" failures=0 passed=0 @@ -67,16 +68,20 @@ check "status.sh stays silent when the mount is restricted" \ check "status.sh detects a configured-but-pending fix (wsl --shutdown)" \ sh -c "grep -q 'pending' \"\$1\" && grep -q \"options *=.*dmask\" \"\$1\"" _ "$STATUS" -# --- install.sh interactive restriction ---------------------------------------- -check "install.sh gates the restriction on /mnt/c existing" \ +# --- install.sh report-only restriction (consent policy) ------------------------ +# The kit never writes /etc/wsl.conf (docs/design/wsl-conf-consent.md): +# install prints the snippet, the user applies it. +check "install.sh gates the exposure info on /mnt/c existing" \ sh -c "grep -qF '[ -d /mnt/c ]' \"\$1\"" _ "$INSTALL" -check "install.sh warns about the exposure before prompting" \ +check "install.sh warns about the exposure" \ sh -c "grep -q 'WARNING: /mnt/c is world-readable' \"\$1\"" _ "$INSTALL" -check "install.sh prompts before touching wsl.conf" \ - sh -c "grep -q 'Restrict /mnt/c to your user' \"\$1\"" _ "$INSTALL" -check "install.sh writes the [automount] section with dmask/fmask" \ +check "install.sh never writes /etc/wsl.conf (no tee/append)" \ + sh -c "! grep -q 'tee -a /etc/wsl.conf' \"\$1\"" _ "$INSTALL" +check "install.sh no longer prompts for the restriction (explicit consent)" \ + sh -c "! grep -q 'Restrict /mnt/c to your user' \"\$1\"" _ "$INSTALL" +check "install.sh prints the [automount] snippet with dmask/fmask" \ sh -c "grep -qF '[automount]' \"\$1\" && grep -q 'dmask=027,fmask=037' \"\$1\"" _ "$INSTALL" -check "install.sh derives uid/gid from the default user (not hardcoded)" \ +check "install.sh resolves uid/gid for the snippet from the default user" \ sh -c "grep -qF 'id -u \"\$DEFAULT_USER\"' \"\$1\" && grep -qF 'id -g \"\$DEFAULT_USER\"' \"\$1\"" _ "$INSTALL" check "install.sh leaves a pre-existing [automount] section untouched" \ sh -c "grep -q 'already has an \[automount\]' \"\$1\"" _ "$INSTALL" @@ -86,6 +91,8 @@ check "install.sh skips silently when the mount is already restricted" \ sh -c "grep -qF '/mnt/c already restricted' \"\$1\"" _ "$INSTALL" check "install.sh warns again in the final summary while /mnt/c is exposed" \ sh -c "grep -q 'still world-readable' \"\$1\" && grep -q 'warn on every start' \"\$1\"" _ "$INSTALL" +check "install.sh points WSL users at the opt-in bridge command" \ + sh -c "grep -q 'opk wsl-add-opencode-1-fix' \"\$1\"" _ "$INSTALL" # --- wrapper startup warning ------------------------------------------------------ check "wrapper passes sh -n" sh -n "$WRAPPER" @@ -99,6 +106,8 @@ check "wrapper shows the wsl.conf automount fix" \ sh -c "grep -qF '[automount]' \"\$1\" && grep -q 'dmask=027,fmask=037' \"\$1\"" _ "$WRAPPER" check "wrapper names the activation step (wsl --shutdown)" \ sh -c "grep -q 'wsl --shutdown' \"\$1\"" _ "$WRAPPER" +check "wrapper points at the opt-in bridge command" \ + sh -c "grep -q 'opk wsl-add-opencode-1-fix' \"\$1\"" _ "$WRAPPER" check "wrapper detects a configured-but-pending restriction" \ sh -c "grep -q 'PENDING' \"\$1\" && grep -q \"options *=.*dmask\" \"\$1\"" _ "$WRAPPER" check "wrapper stays silent when the mount is restricted (other bit off)" \ @@ -111,6 +120,28 @@ check "update.sh prints the recommended wsl.conf options" \ sh -c "grep -qF '[automount]' \"\$1\" && grep -q 'dmask=027' \"\$1\"" _ "$UPDATE" check "update.sh reminds about a configured-but-pending restriction" \ sh -c "grep -q \"pending 'wsl --shutdown'\" \"\$1\"" _ "$UPDATE" +check "update.sh never writes /etc/wsl.conf (carrier left to the user)" \ + sh -c "! grep -q 'browser_bridge_write_conf' \"\$1\" && grep -q 'opk wsl-add-opencode-1-fix' \"\$1\"" _ "$UPDATE" + +# --- the explicit-consent command (opk wsl-add-opencode-1-fix) ------------------- +check "opk implements the wsl-add-opencode-1-fix subcommand" \ + sh -c "grep -q 'wsl-add-opencode-1-fix)' \"\$1\"" _ "$OPK" +check "opk wsl-add-opencode-1-fix elevates via sudo and runs write_conf" \ + sh -c "awk '/wsl-add-opencode-1-fix\)/,/;;/' \"\$1\" | grep -q 'sudo' && awk '/wsl-add-opencode-1-fix\)/,/;;/' \"\$1\" | grep -q 'browser_bridge_write_conf'" _ "$OPK" +check "opk wsl-add-opencode-1-fix is the ONLY shipped entry point calling write_conf" \ + sh -c "! grep -rl 'browser_bridge_write_conf' \"\$1\" 2>/dev/null | grep -v 'sh/wsl-browser-bridge.sh$' | grep -v 'bin/opk$' | grep -q ." _ "$SCRIPT_DIR/../../files" +check "status.sh points at the opt-in bridge command" \ + sh -c "grep -q 'opk wsl-add-opencode-1-fix' \"\$1\"" _ "$STATUS" + +# --- uninstall consent (docs/design/wsl-conf-consent.md) -------------------------- +UNINSTALL="$SCRIPT_DIR/../../files/opencode-permissions-kit-lib/management/uninstall.sh" +check "uninstall.sh passes sh -n" sh -n "$UNINSTALL" +check "uninstall.sh asks before removing kit-owned wsl.conf content" \ + sh -c "grep -q \"Remove the kit's wsl.conf bridge block?\" \"\$1\" && grep -q 'prompt_yn' \"\$1\"" _ "$UNINSTALL" +check "uninstall.sh prints the manual line range when declined" \ + sh -c "grep -q 'browser bridge -- begin\\\$' \"\$1\" && grep -q 'left untouched' \"\$1\"" _ "$UNINSTALL" +check "install.sh and update.sh deploy bin/browser-bridge into the library" \ + sh -c "grep -q 'bin/browser-bridge\" \"\$LIBDIR/bin/browser-bridge' \"\$1\" && grep -q 'bin/browser-bridge\" \"\$LIBDIR/bin/browser-bridge' \"\$2\"" _ "$INSTALL" "$UPDATE" # --- mode-mask arithmetic (the exact check status.sh performs) ----------------- mode_allows_other() { diff --git a/tests/ux/example-install-advanced.sh b/tests/ux/example-install-advanced.sh index c66e8ca..b094aa9 100644 --- a/tests/ux/example-install-advanced.sh +++ b/tests/ux/example-install-advanced.sh @@ -39,7 +39,6 @@ fi PROJECT=$(ui_ask "Project directory?" "/var/www/vhosts") PORTS=$(ui_menu "Lower unprivileged port start to 80 (ddev-router 80/443)?" "yes" "yes|host-wide sysctl, recommended" "no|use higher router ports") -WSLC=$(ui_menu "Restrict /mnt/c to your user (needs wsl --shutdown)?" "yes" "yes|recommended" "no|leave world-readable") GIT=$(ui_menu "Allow opencode git access?" "no" "no|block git for the agent (recommended)" "yes|allow (soft-only .git/config deny)") DENY=$(ui_menu "Existing default-user opencode config found — replace with deny-all?" "backup" "backup|back up as opencode.jsonc_BAK_ (recommended)" "keep|keep my config" "overwrite|overwrite without backup") @@ -50,7 +49,7 @@ ui_section "Plan" ui_kv "Backend" "$BACKEND" ui_kv "Projects" "$PROJECT" ui_kv "Router" "$([ "$PORTS" = "yes" ] && echo "ports 80/443 (sysctl)" || echo "higher ports (8080/8443)")" -ui_kv "/mnt/c" "$([ "$WSLC" = "yes" ] && echo "restrict via wsl.conf" || echo "leave world-readable (not recommended)")" +ui_kv "/mnt/c" "hardening snippet printed (manual — the kit never writes wsl.conf)" ui_kv "Git" "$([ "$GIT" = "yes" ] && echo "allowed (soft deny only)" || echo "blocked")" ui_kv "Old config" "$DENY" ui_kv "Migration" "v0.0.9 kit detected — legacy ACLs will be removed" diff --git a/tests/ux/example-install-standard.sh b/tests/ux/example-install-standard.sh index 1b15e7d..ae7645b 100644 --- a/tests/ux/example-install-standard.sh +++ b/tests/ux/example-install-standard.sh @@ -51,7 +51,7 @@ ui_have "curl / acl / ddev v1.25.2" "present" ui_add "user 'opencode' + sharing group" "will be created" ui_add "docker-rootless backend" "will be provisioned" ui_add "wrapper /usr/local/bin/opencode" "will be installed" -ui_add "/mnt/c restriction" "wsl.conf (pending wsl --shutdown)" +ui_add "/mnt/c hardening snippet" "printed (manual, applies after wsl --shutdown)" ui_atten "~/.opencode/bin/opencode" "will be moved into the kit (backup kept)" # --- the two Standard questions --------------------------------------------------- @@ -70,7 +70,7 @@ ui_plan 1 "Create user 'opencode' + sharing group" "(dev user 'dev' added)" ui_plan 2 "Provision docker-rootless for 'opencode'" "(mandatory — aborts on failure)" ui_plan 3 "Group + setgid + default ACLs" "on $PROJECT" ui_plan 4 "Secure the opencode binary + wrapper" "root:opencode 750" -ui_plan 5 "Restrict /mnt/c via /etc/wsl.conf" "takes effect after wsl --shutdown" +ui_plan 5 "Print /mnt/c hardening snippet" "manual — the kit never writes wsl.conf" ui_plan 6 "Lower ip_unprivileged_port_start to 80" "ddev-router 80/443" ui_plan 7 "Deny-all config for your user" "self-update bypass guard" ui_plan 8 "Deploy library, sudoers, audit log" "/usr/local/lib/opencode-permissions-kit" @@ -98,10 +98,10 @@ ui_info "Securing the opencode binary..." sim 0.3 ui_success "binary secured, wrapper installed at /usr/local/bin/opencode" ui_success "user-local copy removed (backup in /tmp/opencode-install-backup-...)" -ui_info "Restricting /mnt/c..." +ui_info "Printing /mnt/c hardening snippet..." sim 0.3 -ui_success "wsl.conf written" -ui_warn "takes effect after 'wsl --shutdown' from Windows" +ui_success "snippet printed — apply it yourself, then 'wsl --shutdown'" +ui_detail "login fix (optional): sudo opk wsl-add-opencode-1-fix" ui_info "Lowering unprivileged port start to 80..." sim 0.3 ui_success "sysctl applied + persisted (/etc/sysctl.d/99-ddev-rootless.conf)"