Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,11 @@ Publishable versions are `vMAJOR.MINOR.PATCH[-prerelease]`; build metadata is
excluded so the GitHub Release and OCI aliases share one unambiguous identity.

Both adapters persist effective settings. Rebuild functions locate adjacent
state and reuse paths/runtime/volume/UID/GID/build/channel choices. Uninstallers
parse that state, verify resource labels, and refuse unrelated fixed-name
resources. On Windows, `FORMAT=1` has an aligned closed field set but native
state and reuse paths/runtime/volume/UID/GID/build/channel/SSH-mount choices.
Uninstallers parse that state, verify resource labels, and refuse unrelated
fixed-name resources. Writers emit `FORMAT=2`; readers also accept `FORMAT=1`,
which lacks `MOUNT_SSH` (read as `0`). See ADR 0010 before adding a field.
On Windows, the format has an aligned closed field set but native
PowerShell and Git Bash path/profile values are adapter-native; only the creating
adapter may consume that state. Pre-v1.1 installs need explicit adoption; an
adopted unlabeled volume also needs force before purge.
Expand Down Expand Up @@ -154,8 +156,11 @@ artifact, signing, and reporting details.

PowerShell 7 is the supported native Windows adapter. Use
CurrentUserAllHosts-compatible integration and validate the final Box start.
It mounts the native user's `.ssh` directory read-only when present and does not
forward `SSH_AUTH_SOCK`. Git Bash uses the separate Bash adapter and owns its
It does not forward `SSH_AUTH_SOCK` and mounts the native user's `.ssh`
directory read-only only on explicit opt-in (`-MountSsh` or
`SQUAREBOX_MOUNT_SSH=1`, persisted as `MOUNT_SSH`). Both adapters default to no
`.ssh` directory mount; the Bash adapter's agent forwarding mounts only
`config`/`known_hosts`. Git Bash uses the separate Bash adapter and owns its
MSYS shell integration and agent-socket translation. Keep the shared state field
names and semantic intent aligned, but fail closed rather than cross-consuming
adapter-native lifecycle state.
Expand Down
4 changes: 2 additions & 2 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ _Avoid_: Raw tag, latest commit

**Install identity**:
The durable record of the runtime, paths, resource names, source revision,
host identity, and observed image identity managed by one Squarebox
installation. Release pulls record an immutable digest; source/edge builds
host identity, observed image identity, and lifecycle preferences (such as the
opt-in SSH-directory mount) managed by one Squarebox installation. Release pulls record an immutable digest; source/edge builds
record their local image ID and reference.
_Avoid_: Installer environment, defaults

Expand Down
35 changes: 28 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ Flags: `--build` (build from source), `--edge` (latest `main`), `--adopt`
| `SQUAREBOX_RUNTIME` | auto | Force `docker` or `podman`. |
| `SQUAREBOX_HOME_VOLUME` | `squarebox-home` | Name of the named volume backing `/home/dev`. |
| `SQUAREBOX_EDGE` | `0` | `1` is equivalent to `--edge`. |
| `SQUAREBOX_MOUNT_SSH` | `0` | `1` mounts your host `~/.ssh` directory, **private keys included**, read-only into the Box when no SSH agent is forwarded. Recorded for rebuilds; set `0` to turn it back off. See **SSH access** below. |

**Non-interactive provisioning** — set any of these to a comma-separated list to
pre-select a toolset and install it without prompts (handy for servers and
Expand Down Expand Up @@ -136,6 +137,23 @@ code. Release pulls record an immutable image digest; source/edge builds record
their local image ID/reference. Existing pre-v1.1 checkouts require a one-time
reviewed `--adopt`/`-Adopt`.

**SSH access**

The Box does not receive your SSH private-key files by default, because anything
running inside it, including unattended `*-yolo` AI agents, could read them.

- **SSH agent (recommended).** When `SSH_AUTH_SOCK` points at a running agent,
the Bash/POSIX and Git Bash adapters forward the agent socket and mount only
`~/.ssh/config` and `~/.ssh/known_hosts` read-only. Key files stay on the
host, though Box processes can ask the agent to sign while the socket is
available.
- **No agent.** `~/.ssh` is not mounted, and install prints a note. To mount
the directory read-only instead, opt in with `SQUAREBOX_MOUNT_SSH=1` (native
PowerShell: `.\install.ps1 -MountSsh` or `$env:SQUAREBOX_MOUNT_SSH = '1'`).
The choice is recorded in the Install identity and reused by
`sqrbx-rebuild`; set `SQUAREBOX_MOUNT_SSH=0` (or `-MountSsh:$false`) on a
rebuild to turn it back off.

**Windows (PowerShell 7+)**

Windows users can install directly from PowerShell - no Git Bash required.
Expand All @@ -150,18 +168,21 @@ Once installed, you can re-run or pass flags from the local copy:
.\install.ps1 -Edge # latest main instead of latest release
.\install.ps1 -Build # build the resolved source locally
.\install.ps1 -Adopt # migrate a legacy pre-v1.1 installation
.\install.ps1 -MountSsh # opt in to mounting %USERPROFILE%\.ssh read-only

> **Note:** `irm ... | iex` does not support flags - PowerShell interprets them
> as arguments to `Invoke-Expression`, not the script. Use the local
> `.\install.ps1` form for `-Edge`, `-Build`, or `-Adopt`. PowerShell streams
> runtime and Git failures directly by default.
> `.\install.ps1` form for `-Edge`, `-Build`, `-Adopt`, or `-MountSsh`.
> PowerShell streams runtime and Git failures directly by default.

> **Windows adapter boundary:** Keep install, rebuild, and uninstall on the
> adapter that created the Install identity. Native PowerShell and Git Bash
> use the same `FORMAT=1` field names, but their native path and shell-profile
> values are not interchangeable; cross-adapter state consumption is rejected.
> Native PowerShell mounts `%USERPROFILE%\.ssh` read-only when it exists and
> does not forward `SSH_AUTH_SOCK`. The separate Git Bash adapter supports SSH
> use the same Install identity field names, but their native path and
> shell-profile values are not interchangeable;
> cross-adapter state consumption is rejected.
> Native PowerShell does not forward `SSH_AUTH_SOCK` and mounts
> `%USERPROFILE%\.ssh` read-only only when you opt in with `-MountSsh` or
> `SQUAREBOX_MOUNT_SSH=1`. The separate Git Bash adapter supports SSH
> agent-socket forwarding with its Bash lifecycle.
> Use `./scripts/migrate-windows-adapter.ps1 -Target PowerShell` or
> `-Target GitBash` from PowerShell 7 for an explicit cross-adapter migration.
Expand Down Expand Up @@ -500,7 +521,7 @@ Manually installed apt packages are still lost, since the image is rebuilt.
| Workspace code on the host | Selected tmux/Zsh/Fish packages are reconciled into the new Box |
| Managed home: history, auth, assistant data, mise toolchains | Manually installed, unselected APT packages are lost |
| Selection state in `/workspace/.squarebox` | Image-tier binaries are replaced by the Candidate digest |
| Host SSH access exposed by the selected lifecycle adapter | Image-managed dotfiles are safely refreshed |
| Host SSH access (agent forwarding, or the recorded `SQUAREBOX_MOUNT_SSH` opt-in) | Image-managed dotfiles are safely refreshed |

Use `sqrbx-uninstall --purge` to wipe recorded state. Do not remove a volume by
name alone; lifecycle commands verify the Install identity and ownership
Expand Down
20 changes: 14 additions & 6 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,14 +272,22 @@ Treat code and tools run inside it as having access to:
- the Workspace, read-write;
- the Managed home, including persisted tool credentials;
- the installation-private Git name/email config;
- an SSH agent socket when the Bash/POSIX or Git Bash adapter forwards it, or
explicitly mounted read-only SSH files (the native PowerShell path);
- an SSH agent socket, plus read-only `~/.ssh/config` and `~/.ssh/known_hosts`,
when the Bash/POSIX or Git Bash adapter forwards an available agent;
- the read-only `~/.ssh` directory, including private keys, only when the
operator opts in with `SQUAREBOX_MOUNT_SSH=1` (or `-MountSsh` on native
PowerShell) and no agent is forwarded;
- any additional mounts the operator supplies.

Agent forwarding keeps private-key files on the host, but processes inside the
Box can ask the forwarded agent to sign while the socket is available. Mounting
SSH files as a fallback—or as native PowerShell's current SSH path—exposes their
contents read-only to Box processes.
Box can ask the forwarded agent to sign while the socket is available. Because
AI assistants may run unattended in the Box (for example the `*-yolo` aliases),
the `~/.ssh` directory is never mounted by default: without an agent the Box
gets no SSH material and install prints how to opt in. The opt-in exposes every
file in that directory, private keys included, read-only to all Box processes.
It is recorded as `MOUNT_SSH` in the Install identity and reused on rebuild
until a rebuild sets `SQUAREBOX_MOUNT_SSH=0`. Prefer an agent, ideally with
per-use confirmation, or a dedicated key with narrow scope.

The entrypoint validates numeric UID/GID inputs and refuses unsafe Managed-home
dotfile symlinks. On native Linux, an unprivileged lifecycle install also
Expand Down Expand Up @@ -347,7 +355,7 @@ verify ownership before removal. Recorded directories require a Squarebox
marker; an unrelated directory or resource with a familiar fixed name is not
authority to delete it.

`FORMAT=1` versions each lifecycle adapter's native state contract; it does not
`FORMAT` versions each lifecycle adapter's native state contract; it does not
make Bash/Git Bash and PowerShell Install-identity files interchangeable. Use
the matching adapter family for rebuild and uninstall operations, or explicitly
convert it with `scripts/migrate-windows-adapter.ps1`. The converter parses the
Expand Down
58 changes: 58 additions & 0 deletions docs/adr/0010-opt-in-ssh-directory-mount.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# ADR 0010: Make the SSH directory mount an opt-in Install identity setting

Status: Accepted

## Context

When no SSH agent socket was available, the Bash adapter mounted the host's
entire `~/.ssh` directory read-only into the Box. The native PowerShell adapter
never forwards an agent and always mounted `%USERPROFILE%\.ssh` when present.
That directory normally contains private keys. AI assistants can run unattended
inside the Box (the `*-yolo` aliases), so any Box process could read those keys
without the operator having chosen that exposure.

The choice must survive rebuilds like other lifecycle settings, so it belongs
in the Install identity. ADR 0007 requires a new format, a documented migration,
and cross-language fixtures before a field is added.

## Decision

Neither adapter mounts the `~/.ssh` directory by default. Agent forwarding in
the Bash adapter, with its read-only `~/.ssh/config` and `~/.ssh/known_hosts`
mounts, is unchanged because those files contain no private keys. When no agent
is forwarded and the opt-in is off, install prints how to enable it or use an
agent.

`SQUAREBOX_MOUNT_SSH=1` (both adapters) or `-MountSsh` (native PowerShell)
opts in to the previous read-only directory mount when no agent is forwarded.
`SQUAREBOX_MOUNT_SSH=0` or `-MountSsh:$false` opts out. Any other value fails
before lifecycle mutation.

The effective preference is recorded as `MOUNT_SSH=0|1` in a new `FORMAT=2`
Install identity, appended after `HOME_VOLUME_ADOPTED`. It records the operator's
preference, not whether a mount occurred on that run, because agent availability
can differ between rebuilds.

- Writers emit only `FORMAT=2`.
- Readers accept `FORMAT=2`, where `MOUNT_SSH` is required, and `FORMAT=1`,
where `MOUNT_SSH` must be absent and reads as `0`. Every other format fails
closed.
- The next successful rebuild republishes a `FORMAT=1` record as `FORMAT=2`.
`scripts/migrate-windows-adapter.ps1` does the same when it converts state.
- Ownership rules from ADR 0004 and ADR 0009 are unchanged: path and profile
values remain adapter-native and creator-owned.

`scripts/lib/install-state-schema.json` lists the `FORMAT=2` field order and
the defaults a `FORMAT=1` record omits. The verifier and shared fixtures cover
both formats in all four adapters.

## Consequences

Existing installs that relied on the implicit fallback lose `~/.ssh` inside the
Box on their next rebuild until they start an agent or opt in. The release notes
document this as a behavior change.

An older adapter cannot read `FORMAT=2` state and fails closed, so going back to
an older release needs a fresh install identity or a reviewed manual edit of the
state file. This matches the ADR 0007 rule that readers reject formats they do
not recognize.
45 changes: 41 additions & 4 deletions docs/releases/v1.3.0.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,43 @@
# Squarebox v1.3.0 migration guide

## SSH directory mount is now opt-in

**Behavior change on your next rebuild.** Squarebox no longer mounts your host
`~/.ssh` directory, which usually holds private keys, into the Box by default.
AI assistants can run unattended in the Box, so they should not be able to read
those keys unless you choose that.

- **Bash/POSIX and Git Bash with an SSH agent:** nothing changes. The agent
socket is still forwarded, with read-only `~/.ssh/config` and
`~/.ssh/known_hosts`.
- **Bash/POSIX and Git Bash without an agent:** previously `~/.ssh` was mounted
read-only as a fallback. Now nothing is mounted, and install prints a note.
- **Native PowerShell:** previously `%USERPROFILE%\.ssh` was always mounted
read-only when present. Now it is mounted only on opt-in.

To keep the previous behavior, opt in once on your next rebuild. The choice is
recorded in the Install identity and reused by later rebuilds:

```bash
SQUAREBOX_MOUNT_SSH=1 sqrbx-rebuild
```

```powershell
.\install.ps1 -MountSsh # or: $env:SQUAREBOX_MOUNT_SSH = '1'; sqrbx-rebuild
```

Set `SQUAREBOX_MOUNT_SSH=0` (or `-MountSsh:$false`) on a later rebuild to turn
it back off. Starting an SSH agent before rebuilding is the safer alternative,
because key files then stay on the host.

The Install identity now uses `FORMAT=2`, which adds the `MOUNT_SSH` field.
v1.3.0 reads existing `FORMAT=1` state as opted out and rewrites it as
`FORMAT=2` on the next successful rebuild. Releases before v1.3.0 cannot read
`FORMAT=2` state; see ADR 0010.

## Windows lifecycle adapter migration

Git Bash and native PowerShell retain strict adapter-native `FORMAT=1` state.
Git Bash and native PowerShell retain strict adapter-native Install identity state.
Normal rebuild and uninstall commands do not reinterpret foreign profile paths.

From PowerShell 7, convert an existing identity explicitly:
Expand All @@ -17,9 +52,11 @@ validates data-only state, verifies live runtime ownership, moves shell
integration transactionally, and retains the same Box, image, Workspace, and
Managed home. After success, use only the target adapter for lifecycle work.

Native Windows OpenSSH-agent forwarding remains experimental. PowerShell keeps
the read-only SSH-directory fallback; Git Bash can forward an already available
Unix-compatible socket. Migration does not install a named-pipe relay.
Native Windows OpenSSH-agent forwarding remains experimental. PowerShell mounts
the SSH directory read-only only on opt-in (see above); Git Bash can forward an
already available Unix-compatible socket. Migration does not install a
named-pipe relay. It preserves a recorded `MOUNT_SSH` choice and converts
`FORMAT=1` state to `FORMAT=2` with the opt-in off.

## Box PID limit applies on recreation

Expand Down
Loading
Loading