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
5 changes: 1 addition & 4 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,14 @@ updates:
commit-message:
prefix: "deps"

# pnpm, which this ecosystem reads. ssh2 is held at the version patches/ is made for: a bump of
# it fails test/ssh2.security.test.ts until the patch is made again for the new version.
# pnpm, which this ecosystem reads.
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
day: monday
cooldown:
default-days: 7
ignore:
- dependency-name: ssh2
groups:
js:
patterns: ["*"]
Expand Down
9 changes: 5 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ path.
| `src/logger.ts` | The output channel. |
| `src/spin/authority.ts` | `spin+<workspace>` parsed. |
| `src/spin/resolver.ts` | The VS Code resolver: this editor's commit, progress, the local port, a refusal shown with its action. |
| `src/spin/open.ts` | The flow, with no VS Code in it so `test/e2e` drives it: `spin code`, `spin ssh-session`, ssh2 over `spin tunnel`, the server started. |
| `src/spin/open.ts` | The flow, with no VS Code in it so `test/e2e` drives it: the OpenSSH checked, `spin code`, `spin ssh-session`, ssh over `spin tunnel`, the server started. |
| `src/spin/cli.ts` | The spin CLI, the only client of an installation. |
| `src/spin/contract.ts` | The CLI's JSON answers. |
| `src/spin/keys.ts` | The workspace's certificate as an ssh2 key; the host key pinned to the launch claim's, as ssh-session answers it. |
| `src/spin/connection.ts` | The ssh2 connection: exec, a channel, the loopback port to the server. |
| `src/spin/openssh.ts` | The ssh on the PATH held to OpenSSH 9.9 or newer: the one key exchange a workspace accepts, `mlkem768x25519-sha256`. |
| `src/spin/keys.ts` | The host key pinned to the launch claim's, as ssh-session answers it. |
| `src/spin/connection.ts` | The system `ssh`: a ControlMaster over `spin tunnel`, exec through it, and the loopback port to the server (`ssh -W`). |
| `src/spin/serverStart.ts` | The server from the extension disk, never downloaded, and its answer read. |
| `src/spin/refusal.ts` | What a person is shown. |
| `src/typings/` | VS Code's proposed `resolvers` API, as Microsoft wrote it. |
Expand All @@ -47,7 +48,7 @@ path.
## Build & verify

```bash
go tool task install # pnpm through sfw; ssh2 patched as it installs
go tool task install # pnpm through sfw
go tool task lint # biome
go tool task typecheck # tsc --noEmit
go tool task test # vitest
Expand Down
22 changes: 8 additions & 14 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ green.

## Developing

Needs Go (the tools are pinned in `go.mod` and `versions.yaml`), Node 22 and pnpm.
Needs Go (the tools are pinned in `go.mod` and `versions.yaml`), Node 22, pnpm, and OpenSSH 9.9
or newer - `ssh`, `sshd` and `ssh-keygen`.

```sh
go tool task install # dependencies, through Socket Firewall; ssh2 is patched as it installs
go tool task install # dependencies, through Socket Firewall
go tool task lint # Biome
go tool task typecheck # tsc over src/ and test/
go tool task test # unit tests (Vitest), with vscode replaced by test/mocks/vscode.ts
Expand All @@ -17,10 +18,11 @@ go tool task ci # what CI runs
go tool task test:e2e SPIN=<spin checkout> VSCODE_COMMIT=<commit> # the Spinbox resolver, below
```

The unit tests need OpenSSH's `sshd` and `ssh-keygen`: `test/spin/keys.sshd.test.ts` runs a
real sshd that, like a workspace's, admits certificates only, and `test/spin/resolve.test.ts`
opens `spin+<workspace>` from the extension's activation to a request its server answers, with
a stand-in CLI and workspace.
The unit tests run OpenSSH: `test/sshd.ts` starts a real sshd set up as a workspace's - it admits
certificates only and accepts the key exchange `mlkem768x25519-sha256` alone, which takes
OpenSSH 9.9 - `test/spin/connection.sshd.test.ts` connects to it with this machine's `ssh` as the
extension does, and `test/spin/resolve.test.ts` opens `spin+<workspace>` from the extension's
activation to a request its server answers, with a stand-in CLI and that sshd as the workspace.

## The Spinbox resolver against a real installation

Expand All @@ -34,11 +36,3 @@ other. `test/e2e/cli.e2e.test.ts` needs only the CLI (`SPIN_E2E_CLI`): the refus
with no installation.

F5 in VS Code runs the extension in an Extension Development Host (`.vscode/launch.json`).

## ssh2

ssh2 1.17.0 lacks the ECDSA signature fix jeanp413's ssh2 fork carried, so pnpm patches it as it
installs (`patches/ssh2@1.17.0.patch`, `patchedDependencies` in `pnpm-workspace.yaml`), and
`test/ssh2.security.test.ts` holds the installed tree to the fix and to strict key exchange
(Terrapin). A bump of ssh2 installs unpatched, which that test refuses: make the patch again with
`pnpm patch ssh2@<version>` and `pnpm patch-commit`, or delete it when ssh2 has the fix.
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,44 @@ workspace uses.

## How a workspace opens

- `ssh -V` says this machine's OpenSSH is 9.9 or newer (below);
- `spin code <workspace> --no-open --vscode-commit <this editor's commit> --start` has the
workspace running with the VS Code server for this commit on the extension disk the
installation publishes;
- `spin ssh-session -o json` opens a session, checking the workspace's launch claim, and answers
the host key it names, the certificate the workspace signed for this machine's key and the
session's name; the connection is carried by `spin tunnel <session>`, accepts that host key
only, and logs in with that certificate;
session's name; this machine's `ssh` connects, carried by `spin tunnel <session>` as its
ProxyCommand, accepts that host key only, and logs in with that certificate - one connection
(a ControlMaster) that everything after rides on;
- the server is started from the extension disk, and **never downloaded**: a workspace
without it is refused with what to do;
- VS Code reaches the server through a port on this machine's loopback;
- VS Code reaches the server through a port on this machine's loopback, each of its
connections an `ssh -W` through that one;
- whatever refuses - a key the installation has not enrolled (`Spin-Refused:
key-not-enrolled`), a profile not signed in, an installation that does not answer - is shown
in VS Code with the CLI's words and the one thing to do.

The CLI is the setting `remote.spin.cliPath`, or the one the Spinbox extension carries, or `spin`
on the PATH. `Spinbox: Show Connection Log` shows what each open did.

### OpenSSH 9.9 or newer, on Linux or macOS

A workspace's sshd accepts one key exchange, the post-quantum `mlkem768x25519-sha256`, which
OpenSSH added in 9.9, and refuses an older client with `no matching key exchange method found`.
The `ssh` on the PATH is asked its version first, and an older one is refused before anything
else runs, in VS Code:

```
Spinbox could not open my-workspace: ssh is OpenSSH 9.6 (OpenSSH_9.6p1 Ubuntu-3ubuntu13.20); a
workspace needs OpenSSH 9.9 or newer. Its sshd accepts only the post-quantum key exchange
mlkem768x25519-sha256, which OpenSSH added in 9.9, and refuses older clients. Update OpenSSH (on
macOS, 'brew install openssh') and open the workspace again.
```

An `ssh -V` it cannot read is let through, with a line in the log. The connection reads none of
your `~/.ssh/config`. Windows is refused: there is no spin CLI for it, and OpenSSH for Windows
has no ControlMaster.

## Install

The extension is `spin-stack.spinbox-remote`. It resolves a remote authority through VS Code's
Expand Down
2 changes: 1 addition & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ vars:

tasks:
install:
desc: Install the dependencies from the lockfile, through Socket Firewall (sfw). ssh2 is patched as pnpm installs it (patches/).
desc: Install the dependencies from the lockfile, through Socket Firewall (sfw).
cmds:
- sfw pnpm install --frozen-lockfile

Expand Down
6 changes: 2 additions & 4 deletions esbuild.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
// Bundles the extension into lib/extension.js. vscode is VS Code's own, never bundled. ssh2's
// native crypto binding and cpu-features are optional: ssh2 requires each inside a try and falls
// back to Node's crypto, so leaving them external ships a bundle with no native code.
// Bundles the extension into lib/extension.js. vscode is VS Code's own, never bundled.
import { build } from "esbuild"

await build({
Expand All @@ -10,7 +8,7 @@ await build({
platform: "node",
format: "cjs",
target: "node22",
external: ["vscode", "cpu-features", "*.node"],
external: ["vscode"],
sourcemap: false,
minify: true,
logLevel: "warning",
Expand Down
5 changes: 0 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,9 @@
"test:e2e": "vitest run --config vitest.e2e.config.mts",
"package": "vsce package --no-dependencies --out spinbox-remote.vsix"
},
"dependencies": {
"ssh2": "1.17.0"
},
"devDependencies": {
"@biomejs/biome": "2.6.3",
"@types/node": "22.20.5",
"@types/ssh2": "0.5.52",
"@types/ssh2-streams": "0.1.13",
"@types/vscode": "1.146.0",
"@vscode/vsce": "4.1.0",
"esbuild": "0.28.2",
Expand Down
93 changes: 0 additions & 93 deletions patches/ssh2@1.17.0.patch

This file was deleted.

Loading
Loading