Skip to content

fix: allow native SSH-signed commits in sandbox - #2383

Merged
jonathanKingston merged 2 commits into
mainfrom
copse/something-went-wrong-in-another-copse-thre-d07699
Sep 7, 2026
Merged

fix: allow native SSH-signed commits in sandbox#2383
jonathanKingston merged 2 commits into
mainfrom
copse/something-went-wrong-in-another-copse-thre-d07699

Conversation

@jonathanKingston

Copy link
Copy Markdown
Collaborator

Summary

  • add an explicit, off-by-default macOS permission for Copse's native git_commit subprocess to reach the single Unix socket named by SSH_AUTH_SOCK
  • preserve the existing filesystem and internet-deny sandbox, and replace path-configured SSH signing keys with an inline public identity so the private key and .ssh directory remain unreadable
  • pin a one-line sandbox-runtime patch so the documented per-spawn allowUnixSockets override reaches the macOS seatbelt profile
  • explain the Git-hook/agent capability boundary in Settings and the shell-permissions documentation

Closes copse-dev/agent-pane#2320.

The dependency patch can be removed after anthropics/sandbox-runtime#420 ships in a release.

Security boundary

The grant is macOS-only, requires explicit user consent, validates that SSH_AUTH_SOCK is an absolute normalized socket, and applies only to the native commit spawn. Network domains remain denied. Git hooks inherit the commit process and can ask ssh-agent to use any loaded key, so the UI recommends ssh-add -c.

Validation

  • pnpm test -- git-commit-signing — 12 passed
  • pnpm test — 8,555 passed
  • pnpm run typecheck
  • pnpm run lint
  • pnpm run format:check
  • pnpm run check:dead-code
  • pnpm run check:oracle
  • pnpm run check:e2e-syntax
  • pnpm run demo:site:check
  • pnpm run build
  • pnpm install --frozen-lockfile --ignore-scripts
  • focused Electron e2e: 2 passed
  • git diff --check

Visual evidence

Commit signing permission

Co-Authored-By: Copse noreply@copse.dev
Copse-Models: acp:codex-acp#gpt-5.6-sol, acp:claude-acp#opus[1m]

@jonathanKingston
jonathanKingston force-pushed the copse/something-went-wrong-in-another-copse-thre-d07699 branch 2 times, most recently from 603195a to 0263e66 Compare September 5, 2026 22:48
github-actions Bot added a commit that referenced this pull request Sep 5, 2026
github-actions Bot added a commit that referenced this pull request Sep 5, 2026
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

🖥️ PR preview

@jonathanKingston

Copy link
Copy Markdown
Collaborator Author

I fixed the sole CI blocker in the branch history: gitleaks matched the intentionally fake private-key marker in the test fixture. Because the scan covers full history, I amended the single PR commit rather than adding a follow-up, then rebased onto current main and force-pushed 0263e6676.

Validation after the rebase: both focused signing test files pass, 12/12, under the pinned Node 24.20.0 runtime; git diff --check is clean; the literal secret signature is absent; and the focused Settings screenshot is readable with the default-off state and risk copy visible. CI is rerunning now.

@copse-release-bot

copse-release-bot Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Reference screenshots

CI run 34123700910 published no changed screenshot candidates for fbab1598081a.
Any review PR for an older source SHA has been closed.

@jonathanKingston

Copy link
Copy Markdown
Collaborator Author

Final rerun result on 0263e6676: the aggregate CI gate is green. Precheck (including gitleaks, typecheck, lint, formatting, dead code, API compatibility, and oracle), full check, build, sidecar/bridge, and all eight Electron E2E shards passed. The generated screenshot-candidate PR #2390 contains only unrelated baseline churn and should not be merged; the focused signing screenshot already on this source branch is the relevant visual evidence.

@jonathanKingston
jonathanKingston force-pushed the copse/something-went-wrong-in-another-copse-thre-d07699 branch from 0263e66 to b4cf0af Compare September 6, 2026 09:19
github-actions Bot added a commit that referenced this pull request Sep 6, 2026
Add an explicit macOS permission for the native git_commit subprocess to reach the configured SSH agent socket while preserving the existing filesystem and network sandbox. Supply path-configured signing keys as inline public identities, and patch sandbox-runtime so per-spawn Unix socket allowances reach the seatbelt profile.

Co-Authored-By: Copse <noreply@copse.dev>
Copse-Models: acp:codex-acp#gpt-5.6-sol
@jonathanKingston
jonathanKingston force-pushed the copse/something-went-wrong-in-another-copse-thre-d07699 branch from b4cf0af to 700a4c4 Compare September 6, 2026 20:17
github-actions Bot added a commit that referenced this pull request Sep 6, 2026
Screenshot candidates rendered for parent PR
#2383 at
`700a4c4e20e1fcb1f8042ea6e1deae7e88903469` by
[CI run
34057539633](https://github.com/copse-dev/agent-pane/actions/runs/34057539633).

Review GitHub's image diffs, then merge this PR (or enable auto-merge)
to apply the
accepted references to
`copse/something-went-wrong-in-another-copse-thre-d07699`. This branch
contains
only PNG candidates from the immutable
`reference-screenshot-candidates-34057539633` artifact and never targets
`main`.

If the parent branch has advanced beyond the source SHA above, do not
merge this PR;
the successful CI run for the new head will replace it.

Co-authored-by: jonathanKingston <338988+jonathanKingston@users.noreply.github.com>
@jonathanKingston
jonathanKingston enabled auto-merge (squash) September 7, 2026 12:46
@jonathanKingston
jonathanKingston merged commit 89e398c into main Sep 7, 2026
17 checks passed
@jonathanKingston
jonathanKingston deleted the copse/something-went-wrong-in-another-copse-thre-d07699 branch September 7, 2026 12:56
github-actions Bot added a commit that referenced this pull request Sep 7, 2026
jonathanKingston added a commit that referenced this pull request Sep 7, 2026
…#2551)

Closes #2474 (P2, `area:ui`).

## The bug

Cmd/Ctrl+W deletes the active thread. Nothing checked whether a dialog
was on screen, so **closing Settings with the keystroke that closes
things everywhere else destroyed a conversation instead.** There is no
undo.

```ts
if (meta && e.key === 'w') {
  e.preventDefault()
  void confirmDeleteThread()   // ← no idea Settings is open
}
```

## The fix

The guard reads the DOM — *is any `<dialog>` open* — rather than asking
a list of `isXOpen()` predicates.

That shape is deliberate. The renderer has **seventeen** dialogs, and
the one hand-maintained list of them sits three lines above this in the
Cmd/Ctrl+F handler, naming **four**: settings, the command palette, file
search, keyboard shortcuts. That list is exactly the failure the issue
anticipates when it says *"or any other dialog for that matter"*, so the
check is built so a new dialog is covered the moment it exists, with
nobody having to remember to register it.

**A behaviour change worth noticing:** the find bar now uses the same
check, so it also defers to the thirteen dialogs the list missed — an
approval, an SSH passphrase, a confirm. Each is a question it should not
open underneath, but it is a change beyond the reported bug, so push
back if you'd rather I left the four-item list alone.

`show()` counts as well as `showModal()`: the approval prompt appears
**inline over the chat** rather than modally, and it is still something
the user is answering.

### `preventDefault()` stays outside the guard

It is what keeps the keystroke from also reaching macOS's File ▸ Close
accelerator (`app-menu-file-items.ts` registers `{ role: 'close' }`
there). Moving it inside would hand Cmd+W back to the menu exactly when
a dialog is up. So the keystroke is still swallowed; only the delete is
skipped.

Doing nothing is the right answer rather than closing the dialog for
them — Esc already does that, in the handler immediately below, and
every dialog honours it.

## Left alone deliberately

The shortcut is matched inline as `meta && e.key === 'w'` rather than
through a `matchXShortcut` helper like its neighbours in
`keyboard-shortcuts.ts`. One consequence: with Caps Lock on, `e.key` is
`'W'` and the shortcut silently does nothing.

Routing it through a matcher would be tidier and testable in the same
place as its siblings — but it would also **widen** which keystrokes
delete a thread, and that is the wrong direction to move a destructive
shortcut without being asked. Happy to do it if you want it.

## Testing

`main.ts` binds its shortcuts inside a boot function with no seam to
call, so the wiring is pinned at the source level — the file's existing
test does the same for the layout boot — while the guard itself is
unit-tested against real `<dialog>` elements in happy-dom.

The wiring assertions test the **property**, not the text: *every*
`confirmDeleteThread()` call site is guarded, so a second unguarded one
added later fails the suite rather than slipping past an assertion that
only looked at the first.

| Check | Result |
|---|---|
| `node scripts/run-tests.mts` (whole repo) | **8641 tests, 8631 pass,
10 skipped, 0 fail** |
| `tsc --noEmit` (node + web) | clean |
| `pnpm run lint` / `oxfmt --check .` | clean |
| `check:dead-code` (840 modules) / `check:oracle` (243 specs, 15
invariants) | clean |
| `check:e2e-syntax` (269 files) / `demo:site:check` | clean |
| all three wiring assertions with the guard removed | fail, as designed
|

9 new tests: 5 on `isAnyDialogOpen` (closed dialog, modal, non-modal,
several at once, and one inserted as raw HTML that nothing registered),
3 on the Cmd+W / Cmd+F wiring, and the existing suite unchanged.

Two local-environment notes, neither affecting CI: reaching a full run
needs an uncommitted shim over node-pty's native loader (it cannot be
rebuilt in this sandbox — the agent proxy denies `iojs.org` and
`www.electronjs.org`), reverted before committing; and this container's
`node_modules` predates #2383, so
`patches/@anthropic-ai__sandbox-runtime@0.0.74.patch` was not applied
and `git-commit-signing.test.ts` failed until I applied its one-line
hunk by hand. That failure reproduces identically on `52c839a` with my
changes stashed, so it is a stale install here, not a base-branch break.

Refs #2474

---
🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01Hpk1gEma9LhMUuvr2TwUj6

---
_Generated by [Claude
Code](https://claude.ai/code/session_01Hpk1gEma9LhMUuvr2TwUj6)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jonathan Kingston <KingstonMailBox@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Agent commits land unsigned: passphrase-protected SSH key + sandbox denies ssh-agent

1 participant