feat(ssh): package standalone POSIX helper runtimes

This commit is contained in:
Yichen Jiang committed 2026-09-23 19:16:59 +08:00
1 parent 34a0a8e81d
commit d9342fcb5d
77 files changed
+2524 -309

No files matched your search

@@ -0,0 +1,127 @@
name: Build SSH helper
on:
workflow_call:
inputs:
targets:
description: Comma-separated native Node 24 targets; empty builds all four.
type: string
default: ''
release:
description: Retain a complete release build when another run starts.
type: boolean
default: false
workflow_dispatch:
inputs:
targets:
description: Linux/macOS x64/arm64 pkg targets; empty builds all four.
type: string
default: ''
permissions:
contents: read
concurrency:
group: ssh-helper-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ !inputs.release }}
env:
DSH_TELEMETRY_DISABLED: '1'
jobs:
plan:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
matrix: ${{ steps.targets.outputs.matrix }}
steps:
- name: Validate native targets
id: targets
env:
REQUESTED_TARGETS: ${{ inputs.targets }}
run: |
node --input-type=module <<'JS'
import { appendFileSync } from 'node:fs';
const runners = {
'node24-linux-x64': 'ubuntu-24.04',
'node24-linux-arm64': 'ubuntu-24.04-arm',
'node24-macos-arm64': 'macos-latest',
'node24-macos-x64': 'macos-15-intel',
};
const raw = process.env.REQUESTED_TARGETS;
const targets = raw ? raw.split(',').map(value => value.trim()) : Object.keys(runners);
if (new Set(targets).size !== targets.length || targets.some(target => !Object.hasOwn(runners, target))) throw new Error('Invalid or duplicate SSH helper target');
appendFileSync(process.env.GITHUB_OUTPUT, `matrix=${JSON.stringify(targets.map(target => ({target, runner: runners[target]})))}\n`);
JS
build:
needs: plan
name: ${{ matrix.target }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 40
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(needs.plan.outputs.matrix) }}
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: pnpm/action-setup@v4
with:
dest: ${{ runner.temp }}/setup-pnpm-${{ github.run_id }}-${{ github.run_attempt }}
- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
- uses: actions/cache@v4
with:
path: ~/.pkg-cache
key: ssh-pkg-${{ matrix.target }}-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: ssh-pkg-${{ matrix.target }}-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Prepare Linux compiler and sandbox
if: runner.os == 'Linux'
run: |
sudo apt-get update -q
sudo apt-get install -yq musl-tools bubblewrap
if sysctl kernel.apparmor_restrict_unprivileged_userns >/dev/null 2>&1; then
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
fi
bwrap --ro-bind / / --dev /dev --unshare-pid --proc /proc --die-with-parent -- true
- name: Build compatible Linux PTY addon
if: runner.os == 'Linux'
run: bash scripts/prepare-linux-node-pty.sh
- name: Build native release archive
env:
HELPER_TARGET: ${{ matrix.target }}
run: pnpm exec tsx scripts/build-exe-for-ssh-helper.ts --target="$HELPER_TARGET"
- name: Verify relocated executable and confinement
env:
HELPER_TARGET: ${{ matrix.target }}
run: |
archives=(dist-exe/ssh-helper/*.tar.gz)
test "${#archives[@]}" -eq 1
pnpm exec tsx scripts/verify-ssh-helper-artifact.ts --archive="${archives[0]}" --sandbox=required --report="dist-exe/ssh-helper/$HELPER_TARGET.json"
- name: Verify the packaged Landlock backend
if: runner.os == 'Linux'
env:
HELPER_TARGET: ${{ matrix.target }}
run: |
archives=(dist-exe/ssh-helper/*.tar.gz)
pnpm exec tsx scripts/verify-ssh-helper-artifact.ts --archive="${archives[0]}" --sandbox=required --backend=landlock-run --report="dist-exe/ssh-helper/$HELPER_TARGET.landlock.json"
- name: Verify OpenSSH on glibc 2.28 without Node
if: runner.os == 'Linux'
env:
HELPER_TARGET: ${{ matrix.target }}
run: |
archives=(dist-exe/ssh-helper/*.tar.gz)
test "${#archives[@]}" -eq 1
pnpm exec tsx scripts/verify-ssh-helper-ssh.ts --archive="${archives[0]}" --report="dist-exe/ssh-helper/$HELPER_TARGET.ssh.json"
- name: Upload verified archive and evidence
uses: actions/upload-artifact@v7
with:
name: ssh-helper-${{ matrix.target }}
path: dist-exe/ssh-helper/*
if-no-files-found: error
+8
View File
@@ -13,6 +13,7 @@ on:
options:
- larger-runner-benchmark
- consolidated-runner-benchmark
- ssh-helper
# New master pushes and manual runs replace obsolete checks in this workflow/ref.
# Standby drills share cancellation; use completed runs as readiness evidence.
@@ -30,6 +31,13 @@ env:
DSH_TELEMETRY_DISABLED: '1'
jobs:
ssh-helper:
if: (github.event_name == 'push' && github.ref == 'refs/heads/master') || (github.event_name == 'workflow_dispatch' && inputs.suite == 'ssh-helper')
name: ssh helper / native artifacts
uses: ./.github/workflows/build-exe-for-ssh-helper.yml
with:
targets: ${{ github.event_name == 'workflow_dispatch' && 'node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-macos-x64' || 'node24-linux-arm64,node24-macos-arm64,node24-macos-x64' }}
# These native runtime carriers are post-merge checks; release keeps all targets.
python-runtime:
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
+8 -1
View File
@@ -494,6 +494,13 @@ jobs:
secrets:
DEEPSEEK_API_KEY_EXTERNAL: ${{ secrets.DEEPSEEK_API_KEY_EXTERNAL }}
ssh-helper:
if: github.event_name == 'pull_request'
name: ssh helper / Linux x64
uses: ./.github/workflows/build-exe-for-ssh-helper.yml
with:
targets: node24-linux-x64
# Native builds and process tests block pull requests independently of coverage.
# Observational checks reuse the successful build and remain non-blocking.
# Dependabot PRs are excluded from the self-hosted pool and stay queued for
@@ -714,7 +721,7 @@ jobs:
&& github.event.pull_request.user.login != 'dependabot[bot]'
&& fromJSON('["self-hosted", "linux", "x64", "vm-backup"]')
|| 'ubuntu-latest' }}
needs: [node-24, node-24-coverage, node-24-bench, node-24-consumers, node-compat, python-sdk, python-runtime, windows-build, windows-native-tests]
needs: [node-24, node-24-coverage, node-24-bench, node-24-consumers, node-compat, python-sdk, python-runtime, ssh-helper, windows-build, windows-native-tests]
if: ${{ !cancelled() && github.event_name == 'pull_request' }}
steps:
- name: Fail if any needed job did not succeed
+76
View File
@@ -0,0 +1,76 @@
name: Publish SSH helper
# Select an existing dsh-v<version> tag when dispatching. This never publishes npm packages.
on:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: publish-ssh-helper-${{ github.ref }}
cancel-in-progress: false
jobs:
validate:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.tag.outputs.version }}
commit: ${{ steps.tag.outputs.commit }}
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Verify selected release tag
id: tag
run: |
node --input-type=module <<'JS'
import { readFileSync, appendFileSync } from 'node:fs';
import { execFileSync } from 'node:child_process';
const {version} = JSON.parse(readFileSync('package.json', 'utf8'));
if (process.env.GITHUB_REF_TYPE !== 'tag' || process.env.GITHUB_REF_NAME !== `dsh-v${version}`) throw new Error('Dispatch from the matching dsh-v<version> tag');
const commit = execFileSync('git', ['rev-parse', 'HEAD'], {encoding:'utf8'}).trim();
appendFileSync(process.env.GITHUB_OUTPUT, `version=${version}\ncommit=${commit}\n`);
JS
build:
needs: validate
uses: ./.github/workflows/build-exe-for-ssh-helper.yml
with:
release: true
publish:
needs: [validate, build]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: pnpm/action-setup@v4
with:
dest: ${{ runner.temp }}/setup-pnpm-${{ github.run_id }}-${{ github.run_attempt }}
- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- uses: actions/download-artifact@v8
with:
pattern: ssh-helper-*
path: dist-exe/ssh-helper
merge-multiple: true
- name: Require complete evidence from the selected source commit
env:
RELEASE_VERSION: ${{ needs.validate.outputs.version }}
SOURCE_COMMIT: ${{ needs.validate.outputs.commit }}
run: pnpm exec tsx scripts/ssh-helper/release.ts --directory=dist-exe/ssh-helper --version="$RELEASE_VERSION" --commit="$SOURCE_COMMIT"
- name: Attach the verified archives to the GitHub release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_TAG: ${{ github.ref_name }}
run: |
if ! gh release view "$RELEASE_TAG" >/dev/null 2>&1; then
gh release create "$RELEASE_TAG" --verify-tag --draft --title "$RELEASE_TAG" --notes 'SSH helper executables for Linux and macOS.'
fi
gh release upload "$RELEASE_TAG" dist-exe/ssh-helper/*.tar.gz dist-exe/ssh-helper/SHA256SUMS
+1 -1
View File
@@ -30,7 +30,7 @@ it.skipIf(process.platform !== 'darwin' || !electronInstalled).each(['workspace-
await mkdir(cwd)
vi.stubEnv('ELECTRON_RUN_AS_NODE', '1')
vi.stubEnv('DSH_TEST_RUNTIME_SECRET', 'must-not-inherit')
const runtime = await mountRuntime(ctx, { nodeExecutable: electron }, { mode, workspaceRoot: cwd })
const runtime = await mountRuntime(ctx, { launch: { kind: 'node-script', executable: electron } }, { mode, workspaceRoot: cwd })
const result = await runtime.run(runtime.resolve({
// Bound startup failure before the outer test deadline, leaving time for managed cleanup.
timeoutMs: 30_000,
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/config-catalog.md
config-catalog.md: f86f02fbec949c0212c3c038f47d1fdf50d239fe
config-catalog.zh.md: d56d5bdef41e76ce7a41e3d582a1a8afde18766b
config-catalog.md: 33bddf183cd14f7aca92b5a21ae5a9141f6a40b7
config-catalog.zh.md: a9e8f61b4dfba631e661be3d556161de579ccb26
+35 -14
View File
@@ -2194,7 +2194,9 @@ Requires: `fs` · `subprocess` · `sandbox` · `sandboxPolicy`
```ts config-catalog
/** Deployment-varying runtime bounds and launch choices. */
export interface Config extends LaunchConfig {
export interface Config {
/** Worker invocation in the subprocess world; omitted selects the local carrier. */
launch?: LaunchConfig
/** Default elapsed deadline, including nested tool and approval waits. */
timeoutMs?: number
/** Maximum numeric elapsed budget accepted by resolve. */
@@ -2211,16 +2213,23 @@ export interface Config extends LaunchConfig {
graceMs?: number
}
/** Deployment-owned Node executable and optional preinstalled built bootstrap. */
export interface LaunchConfig {
/** Executable in the subprocess world; defaults to the current Node executable. */
nodeExecutable?: string
/** Absolute preinstalled built bootstrap in the execution world. */
/** Deployment-owned worker invocation in the subprocess execution world. */
export type LaunchConfig = {
/** Start a separately installed Node executable and JavaScript bootstrap. */
kind: 'node-script'
/** Executable name or path resolved by the subprocess provider. */
executable: string
/** Absolute preinstalled bootstrap; omitted maps this package's bootstrap into the execution world. */
bootstrapPath?: string
} | {
/** Start the private PTC worker embedded in a packaged executable. */
kind: 'embedded'
/** Packaged executable name or path resolved by the subprocess provider. */
executable: string
}
```
Source: [`packages/ptc-runtime/ptc-runtime-node/src/index.ts:26`](../packages/ptc-runtime/ptc-runtime-node/src/index.ts)
Source: [`packages/ptc-runtime/ptc-runtime-node/src/index.ts:27`](../packages/ptc-runtime/ptc-runtime-node/src/index.ts)
<a id="deepseek-aidsh-pwsh-local"></a>
@@ -2799,18 +2808,14 @@ Source: [`packages/spill/spill-policy/src/index.ts:25`](../packages/spill/spill-
export interface Config {
/** OpenSSH host alias, including its existing user, key and known-host configuration. */
host: string
/** Absolute remote Node executable. */
node: string
/** Explicit script or self-contained executable invocation. */
launch: HelperLaunch
/** Absolute path to the installed, bundled helper entry. */
helper: string
/** SHA-256 of that bundled helper; mismatches refuse the connection. */
helperHash: string
/** Absolute remote default workspace. */
workspace: string
/** Optional preinstalled built PTC entry, paired with its expected digest. */
bootstrapPath?: string
/** SHA-256 of bootstrapPath; both fields must be supplied together. */
bootstrapHash?: string
/** Connection and administrative-request deadline, at most 2,147,483,647 milliseconds. */
requestTimeoutMs?: number
/** Maximum JSON payload bytes per helper request or response. */
@@ -2820,9 +2825,24 @@ export interface Config {
/** Remote helper lease; loss of heartbeats starts remote managed cleanup. */
leaseMs?: number
}
/** Installed helper invocation; script deployments may also install a PTC bootstrap. */
export type HelperLaunch = {
/** Run the installed script with a separately installed Node executable. */
kind: 'node-script'
/** Absolute remote Node executable. */
node: string
/** Absolute remote PTC bootstrap; requires bootstrapHash. */
bootstrapPath?: string
/** Lowercase SHA-256 of bootstrapPath; requires that path. */
bootstrapHash?: string
} | {
/** Run the helper executable with its embedded Node and PTC worker. */
kind: 'executable'
}
```
Source: [`packages/ssh/ssh/src/index.ts:17`](../packages/ssh/ssh/src/index.ts)
Source: [`packages/ssh/ssh/src/index.ts:33`](../packages/ssh/ssh/src/index.ts)
<a id="deepseek-aidsh-storage-domain"></a>
@@ -4248,6 +4268,7 @@ Imported as libraries by other packages; a `cordis.yml` cannot load them.
- `@deepseek-ai/dsh-session-snapshot` ([`packages/test-support/session-snapshot/src/index.ts`](../packages/test-support/session-snapshot/src/index.ts))
- `@deepseek-ai/dsh-session-telemetry` ([`packages/session/session-telemetry/src/index.ts`](../packages/session/session-telemetry/src/index.ts))
- `@deepseek-ai/dsh-session-title-llm` ([`packages/session/session-title-llm/src/index.ts`](../packages/session/session-title-llm/src/index.ts))
- `@deepseek-ai/dsh-ssh-helper-runtime` ([`packages/ssh/ssh-helper-runtime/src/index.ts`](../packages/ssh/ssh-helper-runtime/src/index.ts))
- `@deepseek-ai/dsh-subagent-in-process-driver` ([`packages/subagent/subagent-in-process-driver/src/index.ts`](../packages/subagent/subagent-in-process-driver/src/index.ts))
- `@deepseek-ai/dsh-timeout` ([`packages/util/timeout/src/index.ts`](../packages/util/timeout/src/index.ts))
- `@deepseek-ai/dsh-typert-generator` ([`packages/typert/generator/src/index.ts`](../packages/typert/generator/src/index.ts))
+35 -14
View File
@@ -2196,7 +2196,9 @@ export interface Config {
```ts config-catalog
/** Deployment-varying runtime bounds and launch choices. */
export interface Config extends LaunchConfig {
export interface Config {
/** Worker invocation in the subprocess world; omitted selects the local carrier. */
launch?: LaunchConfig
/** Default elapsed deadline, including nested tool and approval waits. */
timeoutMs?: number
/** Maximum numeric elapsed budget accepted by resolve. */
@@ -2213,16 +2215,23 @@ export interface Config extends LaunchConfig {
graceMs?: number
}
/** Deployment-owned Node executable and optional preinstalled built bootstrap. */
export interface LaunchConfig {
/** Executable in the subprocess world; defaults to the current Node executable. */
nodeExecutable?: string
/** Absolute preinstalled built bootstrap in the execution world. */
/** Deployment-owned worker invocation in the subprocess execution world. */
export type LaunchConfig = {
/** Start a separately installed Node executable and JavaScript bootstrap. */
kind: 'node-script'
/** Executable name or path resolved by the subprocess provider. */
executable: string
/** Absolute preinstalled bootstrap; omitted maps this package's bootstrap into the execution world. */
bootstrapPath?: string
} | {
/** Start the private PTC worker embedded in a packaged executable. */
kind: 'embedded'
/** Packaged executable name or path resolved by the subprocess provider. */
executable: string
}
```
来源: [`packages/ptc-runtime/ptc-runtime-node/src/index.ts:26`](../packages/ptc-runtime/ptc-runtime-node/src/index.ts)
来源: [`packages/ptc-runtime/ptc-runtime-node/src/index.ts:27`](../packages/ptc-runtime/ptc-runtime-node/src/index.ts)
<a id="deepseek-aidsh-pwsh-local"></a>
@@ -2801,18 +2810,14 @@ export interface Config {
export interface Config {
/** OpenSSH host alias, including its existing user, key and known-host configuration. */
host: string
/** Absolute remote Node executable. */
node: string
/** Explicit script or self-contained executable invocation. */
launch: HelperLaunch
/** Absolute path to the installed, bundled helper entry. */
helper: string
/** SHA-256 of that bundled helper; mismatches refuse the connection. */
helperHash: string
/** Absolute remote default workspace. */
workspace: string
/** Optional preinstalled built PTC entry, paired with its expected digest. */
bootstrapPath?: string
/** SHA-256 of bootstrapPath; both fields must be supplied together. */
bootstrapHash?: string
/** Connection and administrative-request deadline, at most 2,147,483,647 milliseconds. */
requestTimeoutMs?: number
/** Maximum JSON payload bytes per helper request or response. */
@@ -2822,9 +2827,24 @@ export interface Config {
/** Remote helper lease; loss of heartbeats starts remote managed cleanup. */
leaseMs?: number
}
/** Installed helper invocation; script deployments may also install a PTC bootstrap. */
export type HelperLaunch = {
/** Run the installed script with a separately installed Node executable. */
kind: 'node-script'
/** Absolute remote Node executable. */
node: string
/** Absolute remote PTC bootstrap; requires bootstrapHash. */
bootstrapPath?: string
/** Lowercase SHA-256 of bootstrapPath; requires that path. */
bootstrapHash?: string
} | {
/** Run the helper executable with its embedded Node and PTC worker. */
kind: 'executable'
}
```
来源: [`packages/ssh/ssh/src/index.ts:17`](../packages/ssh/ssh/src/index.ts)
来源: [`packages/ssh/ssh/src/index.ts:33`](../packages/ssh/ssh/src/index.ts)
<a id="deepseek-aidsh-storage-domain"></a>
@@ -4250,6 +4270,7 @@ export interface Config {
- `@deepseek-ai/dsh-session-snapshot`([`packages/test-support/session-snapshot/src/index.ts`](../packages/test-support/session-snapshot/src/index.ts))
- `@deepseek-ai/dsh-session-telemetry`([`packages/session/session-telemetry/src/index.ts`](../packages/session/session-telemetry/src/index.ts))
- `@deepseek-ai/dsh-session-title-llm`([`packages/session/session-title-llm/src/index.ts`](../packages/session/session-title-llm/src/index.ts))
- `@deepseek-ai/dsh-ssh-helper-runtime`([`packages/ssh/ssh-helper-runtime/src/index.ts`](../packages/ssh/ssh-helper-runtime/src/index.ts))
- `@deepseek-ai/dsh-subagent-in-process-driver`([`packages/subagent/subagent-in-process-driver/src/index.ts`](../packages/subagent/subagent-in-process-driver/src/index.ts))
- `@deepseek-ai/dsh-timeout`([`packages/util/timeout/src/index.ts`](../packages/util/timeout/src/index.ts))
- `@deepseek-ai/dsh-typert-generator`([`packages/typert/generator/src/index.ts`](../packages/typert/generator/src/index.ts))
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/module-graph.md
module-graph.md: bfe3e5faa7168b03848ac76d2dd2584d4aa31f5e
module-graph.zh.md: bfa0f83e14cbcf89599fa207ec919195128db49e
module-graph.md: 27c593699e5ca7c1d0a1da2610e7a08602c4b8b5
module-graph.zh.md: 82b18fa42d5d63fd131e8ca6c966c3dfabdd1c24
+4 -1
View File
@@ -376,6 +376,7 @@ flowchart TD
pkg_fs_ssh["fs-ssh"]
pkg_sandbox_ssh["sandbox-ssh"]
pkg_ssh["ssh"]
pkg_ssh_helper_runtime["ssh-helper-runtime"]
pkg_subprocess_ssh["subprocess-ssh"]
end
subgraph group_storage["packages/storage"]
@@ -957,6 +958,7 @@ flowchart TD
pkg_ssh --> pkg_session_projection
pkg_ssh --> pkg_subprocess
pkg_ssh --> pkg_subprocess_local
pkg_ssh --> pkg_util_values
pkg_tool_terminal --> pkg_agent
pkg_tool_terminal --> pkg_jobs
pkg_tool_terminal --> pkg_llm
@@ -1446,6 +1448,7 @@ flowchart TD
| [`session-format-v1-to-v2`](../packages/session/session-format-v1-to-v2) | `session` | — |
| [`session-format-v2-to-v3`](../packages/session/session-format-v2-to-v3) | `session` | — |
| [`session-format-v3-to-v4`](../packages/session/session-format-v3-to-v4) | `session` | — |
| [`ssh-helper-runtime`](../packages/ssh/ssh-helper-runtime) | `ssh` | — |
| [`storage`](../packages/storage/storage) | `storage` | — |
| [`win32-process`](../packages/subprocess/win32-process) | `subprocess` | — |
| [`llm-mock-server`](../packages/test-support/llm-mock-server) | `test-support` | — |
@@ -1598,7 +1601,7 @@ flowchart TD
| [`shell-env`](../packages/shell/shell-env) | `shell` | [`app-boot`](../packages/boot/app-boot), [`home-paths`](../packages/util/home-paths), [`shell`](../packages/shell/shell), [`tools`](../packages/core/tools) |
| [`tool-bash-persistent`](../packages/shell/tool-bash-persistent) | `shell` | [`agent`](../packages/core/agent), [`terminal`](../packages/terminal/terminal), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
| [`tool-pwsh-persistent`](../packages/shell/tool-pwsh-persistent) | `shell` | [`agent`](../packages/core/agent), [`terminal`](../packages/terminal/terminal), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
| [`ssh`](../packages/ssh/ssh) | `ssh` | [`brand`](../packages/util/brand), [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`fs-sandbox`](../packages/fs/fs-sandbox), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-local`](../packages/sandbox/sandbox-local), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session-projection`](../packages/session/session-projection), [`subprocess`](../packages/subprocess/subprocess), [`subprocess-local`](../packages/subprocess/subprocess-local) |
| [`ssh`](../packages/ssh/ssh) | `ssh` | [`brand`](../packages/util/brand), [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`fs-sandbox`](../packages/fs/fs-sandbox), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-local`](../packages/sandbox/sandbox-local), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session-projection`](../packages/session/session-projection), [`subprocess`](../packages/subprocess/subprocess), [`subprocess-local`](../packages/subprocess/subprocess-local), [`util-values`](../packages/util/values) |
| [`tool-terminal`](../packages/terminal/tool-terminal) | `terminal` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`system-prompt`](../packages/core/system-prompt), [`terminal`](../packages/terminal/terminal), [`tools`](../packages/core/tools) |
| [`llm-replay`](../packages/test-support/llm-replay) | `test-support` | [`compaction`](../packages/compaction/compaction), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
+4 -1
View File
@@ -378,6 +378,7 @@ flowchart TD
pkg_fs_ssh["fs-ssh"]
pkg_sandbox_ssh["sandbox-ssh"]
pkg_ssh["ssh"]
pkg_ssh_helper_runtime["ssh-helper-runtime"]
pkg_subprocess_ssh["subprocess-ssh"]
end
subgraph group_storage["packages/storage"]
@@ -959,6 +960,7 @@ flowchart TD
pkg_ssh --> pkg_session_projection
pkg_ssh --> pkg_subprocess
pkg_ssh --> pkg_subprocess_local
pkg_ssh --> pkg_util_values
pkg_tool_terminal --> pkg_agent
pkg_tool_terminal --> pkg_jobs
pkg_tool_terminal --> pkg_llm
@@ -1448,6 +1450,7 @@ flowchart TD
| [`session-format-v1-to-v2`](../packages/session/session-format-v1-to-v2) | `session` | — |
| [`session-format-v2-to-v3`](../packages/session/session-format-v2-to-v3) | `session` | — |
| [`session-format-v3-to-v4`](../packages/session/session-format-v3-to-v4) | `session` | — |
| [`ssh-helper-runtime`](../packages/ssh/ssh-helper-runtime) | `ssh` | — |
| [`storage`](../packages/storage/storage) | `storage` | — |
| [`win32-process`](../packages/subprocess/win32-process) | `subprocess` | — |
| [`llm-mock-server`](../packages/test-support/llm-mock-server) | `test-support` | — |
@@ -1600,7 +1603,7 @@ flowchart TD
| [`shell-env`](../packages/shell/shell-env) | `shell` | [`app-boot`](../packages/boot/app-boot), [`home-paths`](../packages/util/home-paths), [`shell`](../packages/shell/shell), [`tools`](../packages/core/tools) |
| [`tool-bash-persistent`](../packages/shell/tool-bash-persistent) | `shell` | [`agent`](../packages/core/agent), [`terminal`](../packages/terminal/terminal), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
| [`tool-pwsh-persistent`](../packages/shell/tool-pwsh-persistent) | `shell` | [`agent`](../packages/core/agent), [`terminal`](../packages/terminal/terminal), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
| [`ssh`](../packages/ssh/ssh) | `ssh` | [`brand`](../packages/util/brand), [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`fs-sandbox`](../packages/fs/fs-sandbox), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-local`](../packages/sandbox/sandbox-local), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session-projection`](../packages/session/session-projection), [`subprocess`](../packages/subprocess/subprocess), [`subprocess-local`](../packages/subprocess/subprocess-local) |
| [`ssh`](../packages/ssh/ssh) | `ssh` | [`brand`](../packages/util/brand), [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`fs-sandbox`](../packages/fs/fs-sandbox), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-local`](../packages/sandbox/sandbox-local), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session-projection`](../packages/session/session-projection), [`subprocess`](../packages/subprocess/subprocess), [`subprocess-local`](../packages/subprocess/subprocess-local), [`util-values`](../packages/util/values) |
| [`tool-terminal`](../packages/terminal/tool-terminal) | `terminal` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`system-prompt`](../packages/core/system-prompt), [`terminal`](../packages/terminal/terminal), [`tools`](../packages/core/tools) |
| [`llm-replay`](../packages/test-support/llm-replay) | `test-support` | [`compaction`](../packages/compaction/compaction), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/subsystems/ssh.md
ssh.md: 04480e6ec397490f2e61851c956b9d318c1ef54f
ssh.zh.md: 32d7c8b6c2936240b82b02de3a29adeda724171f
ssh.md: 440e42c26d8505bc2d961bc8f49b5a7798007c41
ssh.zh.md: 862d8402a163e0688789a02a8a3b235e23ed3794
+22 -11
View File
@@ -8,7 +8,7 @@ The [SSH provider family](../../packages/ssh/README.md) supplies one remote file
Filesystem identities, executable lookup, process cwd, sandbox workspace roots and language-server file URLs refer to the SSH host. Providers canonicalize paths where the files exist, preserving filesystem interpretation of `symlink/..`. The policy resolver carries absolute execution-world spelling without trying to resolve remote paths on the Harness host.
`processPath()` supplies a path usable by the paired subprocess provider. `processPathFromHostPath()` remains unavailable for SSH; installing a remote artifact does not make an arbitrary host path portable. [`NodePtcRuntime`](../../packages/ptc-runtime/ptc-runtime-node/README.md) therefore takes an explicitly installed, digest-verified remote bootstrap.
`processPath()` supplies a path usable by the paired subprocess provider. `processPathFromHostPath()` remains unavailable for SSH; installing a remote artifact does not make an arbitrary host path portable. [`NodePtcRuntime`](../../packages/ptc-runtime/ptc-runtime-node/README.md) therefore takes the verified remote launch configuration: an installed script bootstrap or the [helper executable’s embedded worker](../../packages/ssh/ssh-helper-runtime/README.md).
## Transport and trust
@@ -30,23 +30,36 @@ See the [decision record](../../.agents/notes/implemented/architecture/2026-09-1
## Connection API
```ts type-equiv
/** Installed helper invocation; script deployments may also install a PTC bootstrap. */
type HelperLaunch = {
/** Run the installed script with a separately installed Node executable. */
kind: 'node-script'
/** Absolute remote Node executable. */
node: string
/** Absolute remote PTC bootstrap; requires bootstrapHash. */
bootstrapPath?: string
/** Lowercase SHA-256 of bootstrapPath; requires that path. */
bootstrapHash?: string
} | {
/** Run the helper executable with its embedded Node and PTC worker. */
kind: 'executable'
}
```
```ts type-equiv
/** Deployment-owned SSH identity and installed helper; no model argument selects these values. */
interface Config {
/** OpenSSH host alias, including its existing user, key and known-host configuration. */
host: string
/** Absolute remote Node executable. */
node: string
/** Explicit script or self-contained executable invocation. */
launch: HelperLaunch
/** Absolute path to the installed, bundled helper entry. */
helper: string
/** SHA-256 of that bundled helper; mismatches refuse the connection. */
helperHash: string
/** Absolute remote default workspace. */
workspace: string
/** Optional preinstalled built PTC entry, paired with its expected digest. */
bootstrapPath?: string
/** SHA-256 of bootstrapPath; both fields must be supplied together. */
bootstrapHash?: string
/** Connection and administrative-request deadline, at most 2,147,483,647 milliseconds. */
requestTimeoutMs?: number
/** Maximum JSON payload bytes per helper request or response. */
@@ -67,10 +80,8 @@ declare class SshConnection extends Service {
constructor(ctx: Context, config: Config);
/** Hold plugin readiness until the remote identity and helper digest are verified. */
async [Service.init](): Promise<void>;
/** Verified remote Node executable for the paired PTC runtime. */
get nodeExecutable(): string;
/** Verified preinstalled PTC entry; unconfigured runtimes fail before program execution. */
get bootstrapPath(): string;
/** Verified remote PTC launch configuration; script deployments require a verified bootstrap. */
get ptcLaunch(): { kind: 'embedded'; executable: string } | { kind: 'node-script'; executable: string; bootstrapPath: string };
/**
* Send a helper operation; cancellation never replays an ambiguous mutation.
* @param method - the private helper operation.
+22 -11
View File
@@ -8,7 +8,7 @@
文件系统身份、可执行文件查找、进程 cwd、沙箱工作区根目录及语言服务器文件 URL 都指向 SSH 主机。提供方在文件实际存在的位置规范化路径,保留文件系统对 `symlink/..` 的解释。策略解析器保留执行环境中的绝对路径写法,不尝试在 Harness 主机上解析远端路径。
`processPath()` 提供配套子进程提供方可用的路径。SSH 的 `processPathFromHostPath()` 仍不可用;安装远端产物不意味着任意主机路径可移植。因此 [`NodePtcRuntime`](../../packages/ptc-runtime/ptc-runtime-node/README.zh.md) 使用显式安装并经过摘要验证的远端引导程序。
`processPath()` 提供配套子进程提供方可用的路径。SSH 的 `processPathFromHostPath()` 仍不可用;安装远端产物不意味着任意主机路径可移植。因此 [`NodePtcRuntime`](../../packages/ptc-runtime/ptc-runtime-node/README.zh.md) 使用验证后的远端启动配置:已安装的脚本引导程序,或 [helper 可执行文件内嵌的 worker](../../packages/ssh/ssh-helper-runtime/README.zh.md)。
## 传输与信任
@@ -30,23 +30,36 @@ headless 通过已挂载的文件系统提供方记录和检查 Session cwd。
## 连接 API
```ts type-equiv
/** Installed helper invocation; script deployments may also install a PTC bootstrap. */
type HelperLaunch = {
/** Run the installed script with a separately installed Node executable. */
kind: 'node-script'
/** Absolute remote Node executable. */
node: string
/** Absolute remote PTC bootstrap; requires bootstrapHash. */
bootstrapPath?: string
/** Lowercase SHA-256 of bootstrapPath; requires that path. */
bootstrapHash?: string
} | {
/** Run the helper executable with its embedded Node and PTC worker. */
kind: 'executable'
}
```
```ts type-equiv
/** Deployment-owned SSH identity and installed helper; no model argument selects these values. */
interface Config {
/** OpenSSH host alias, including its existing user, key and known-host configuration. */
host: string
/** Absolute remote Node executable. */
node: string
/** Explicit script or self-contained executable invocation. */
launch: HelperLaunch
/** Absolute path to the installed, bundled helper entry. */
helper: string
/** SHA-256 of that bundled helper; mismatches refuse the connection. */
helperHash: string
/** Absolute remote default workspace. */
workspace: string
/** Optional preinstalled built PTC entry, paired with its expected digest. */
bootstrapPath?: string
/** SHA-256 of bootstrapPath; both fields must be supplied together. */
bootstrapHash?: string
/** Connection and administrative-request deadline, at most 2,147,483,647 milliseconds. */
requestTimeoutMs?: number
/** Maximum JSON payload bytes per helper request or response. */
@@ -67,10 +80,8 @@ declare class SshConnection extends Service {
constructor(ctx: Context, config: Config);
/** Hold plugin readiness until the remote identity and helper digest are verified. */
async [Service.init](): Promise<void>;
/** Verified remote Node executable for the paired PTC runtime. */
get nodeExecutable(): string;
/** Verified preinstalled PTC entry; unconfigured runtimes fail before program execution. */
get bootstrapPath(): string;
/** Verified remote PTC launch configuration; script deployments require a verified bootstrap. */
get ptcLaunch(): { kind: 'embedded'; executable: string } | { kind: 'node-script'; executable: string; bootstrapPath: string };
/**
* Send a helper operation; cancellation never replays an ambiguous mutation.
* @param method - the private helper operation.
+1
View File
@@ -228,6 +228,7 @@
"@vitest/coverage-v8": "^4.1.8",
"@yao-pkg/pkg": "6.21.0",
"@yarnpkg/cli-dist": "4.17.1",
"zod": "^4.4.3",
"eslint-plugin-sonarjs": "^4.1.0",
"execa": "^10.0.0",
"extract-zip": "^2.0.1",
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/ptc-runtime/ptc-runtime-node/README.md
README.md: 5196d6982c3bf8edda3b8b825e41db83f6994141
README.zh.md: 16c9f8fc426b1f2d28eaa039ba259f08088664a8
README.md: 398d633ad5719c350abe9df09f5720eb49bf7798
README.zh.md: b4b9f24a840b77b84264c19cc99bdc1cd72bfa2c
@@ -52,8 +52,7 @@ Configure the provider row after its required services are available:
| `maxMessageBytes` | `134,217,728` | Limit for a control frame, outstanding argument bytes and queued control writes |
| `maxPendingCalls` | `128` | Maximum simultaneous host binding calls |
| `graceMs` | `3,000` | Managed termination and output-drain grace |
| `nodeExecutable` | Current Node executable | Executable resolved in the subprocess execution world |
| `bootstrapPath` | Package bootstrap | Optional absolute path to a preinstalled built bootstrap in that world |
| `launch` | Local carrier | Explicit `{ kind: "node-script", executable, bootstrapPath? }` or `{ kind: "embedded", executable }` in the subprocess world |
The [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-ptc-runtime-node) defines accepted config fields. `resolve(request)` supplies cwd, the numeric or null deadline choice and the execution policy; `run(spec)` accepts those resolved inputs and does not fill missing values.
@@ -91,7 +90,7 @@ Length-framed JSON travels separately from stdout/stderr. The host bounds frames
### Source and built bootstraps
Source execution loads an erasable-only bootstrap closure without relying on sibling built exports. Built execution uses the packaged `process.js` entry. An execution world that cannot map the host bootstrap requires a preinstalled compatible `bootstrapPath`; a host path is never assumed to name the same remote file.
Source execution loads an erasable-only bootstrap closure without relying on sibling built exports. Built script execution uses `process.js`; a remote script deployment supplies `launch.bootstrapPath`. Embedded carriers launch their private worker through `launch.executable`. `resolveLaunch()` selects the local default during construction; an explicit remote invocation never depends on whether the client itself is packaged. A host path is never assumed to name the same remote file.
### Source map
@@ -52,8 +52,7 @@ kind: "package-reference"
| `maxMessageBytes` | `134,217,728` | 控制帧、未完成参数字节和排队控制写入的上限 |
| `maxPendingCalls` | `128` | 同时进行的 Host 绑定调用数量上限 |
| `graceMs` | `3,000` | 受管终止与输出排空宽限时间 |
| `nodeExecutable` | 当前 Node 可执行文件 | 在子进程执行世界中解析的可执行文件 |
| `bootstrapPath` | 包内 bootstrap | 该执行世界中预先安装的构建后 bootstrap 的可选绝对路径 |
| `launch` | 本地载体 | 子进程环境中显式的 `{ kind: "node-script", executable, bootstrapPath? }` 或 `{ kind: "embedded", executable }` |
[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-ptc-runtime-node)定义可接受的配置字段。`resolve(request)` 补全 cwd、数值或 null 截止选择与执行策略;`run(spec)` 接受这些已解析输入,不补缺省值。
@@ -91,7 +90,7 @@ Host 擦除可擦除类型,在配置的执行世界中解析可执行文件与
### 源代码与构建后 bootstrap
源代码执行加载仅含可擦除语法的 bootstrap 依赖,不依赖同级包的构建后导出。构建后执行使用包内 `process.js` 入口。无法映射 Host bootstrap 的执行世界需要预先安装兼容的 `bootstrapPath`;不会假设 Host 路径对应同一个远程文件。
源代码执行加载仅含可擦除语法的 bootstrap 依赖,不依赖同级包的构建后导出。构建后脚本使用 `process.js`,远端脚本部署提供 `launch.bootstrapPath`。内嵌载体通过 `launch.executable` 启动私有 worker。`resolveLaunch()` 在构造时选择本地默认值;显式的远端调用不依赖客户端自身是否打包。不会假设 Host 路径对应同一个远程文件。
### 源码索引
@@ -14,8 +14,9 @@ import type {} from '@deepseek-ai/dsh-fs'
import { snapshotJsonValue } from '@deepseek-ai/dsh-util-values'
import { validateBindings } from './bindings.ts'
import { JsonChannel } from './channel.ts'
import { bootstrapArgs } from './launch.ts'
import { bootstrapArgs, resolveLaunch } from './launch.ts'
import type { LaunchConfig } from './launch.ts'
export type { LaunchConfig } from './launch.ts'
import { OutputLedger } from './output-ledger.ts'
import { drainOutput } from './output-stream.ts'
import { STARTUP_ENVIRONMENT_NAMES } from './environment.ts'
@@ -23,7 +24,9 @@ import { decodePtcJsonWire, encodePtcJsonWire } from './json-wire.ts'
import type { ProgramBootData } from './protocol.ts'
/** Deployment-varying runtime bounds and launch choices. */
export interface Config extends LaunchConfig {
export interface Config {
/** Worker invocation in the subprocess world; omitted selects the local carrier. */
launch?: LaunchConfig
/** Default elapsed deadline, including nested tool and approval waits. */
timeoutMs?: number
/** Maximum numeric elapsed budget accepted by resolve. */
@@ -40,7 +43,7 @@ export interface Config extends LaunchConfig {
graceMs?: number
}
type ResolvedConfig = Required<Omit<Config, 'bootstrapPath'>> & Pick<Config, 'bootstrapPath'>
type ResolvedConfig = Required<Config>
interface LiveRun { controller: AbortController; finished: Promise<void> }
const STRIP_PREFIX = 'async function __dsh_program__() {\n'
const STRIP_SUFFIX = '\n}'
@@ -59,8 +62,10 @@ export class NodePtcRuntime extends PtcRuntime {
maxMessageBytes: z.number().default(134_217_728),
maxPendingCalls: z.number().default(128),
graceMs: z.number().default(3_000),
nodeExecutable: z.string(),
bootstrapPath: z.string(),
launch: z.union([
z.object({ kind: z.const('node-script').required(), executable: z.string().required(), bootstrapPath: z.string() }),
z.object({ kind: z.const('embedded').required(), executable: z.string().required() }),
]),
})
readonly language = 'typescript'
readonly isolation = 'process'
@@ -73,7 +78,7 @@ export class NodePtcRuntime extends PtcRuntime {
constructor(ctx: Context, config: Config) {
super(ctx)
this.config = { ...config, nodeExecutable: config.nodeExecutable ?? process.execPath } as ResolvedConfig
this.config = { ...config, launch: resolveLaunch(config.launch) } as ResolvedConfig
for (const [key, value] of Object.entries(this.config)) {
if (typeof value === 'number' && (!Number.isFinite(value) || value <= 0)) throw new Error(`ptc-runtime-node: ${key} must be positive and finite`)
}
@@ -84,8 +89,6 @@ export class NodePtcRuntime extends PtcRuntime {
if (!Number.isSafeInteger(this.config.maxMessageBytes) || this.config.maxMessageBytes > 0xffff_ffff) throw new Error('ptc-runtime-node: maxMessageBytes must fit an unsigned 32-bit frame length')
if (!Number.isSafeInteger(this.config.maxPendingCalls)) throw new Error('ptc-runtime-node: maxPendingCalls must be an integer')
if (!Number.isSafeInteger(this.config.maxOldGenerationSizeMb)) throw new Error('ptc-runtime-node: maxOldGenerationSizeMb must be an integer')
if (this.config.nodeExecutable.length === 0) throw new Error('ptc-runtime-node: nodeExecutable must be non-empty')
if (this.config.bootstrapPath !== undefined && !isAbsolute(this.config.bootstrapPath)) throw new Error('ptc-runtime-node: bootstrapPath must be absolute')
ctx.effect(() => async () => {
this.disposed = true
const active = [...this.live]
@@ -214,13 +217,14 @@ export class NodePtcRuntime extends PtcRuntime {
})),
maxOutputBytes: this.config.maxOutputBytes,
}
const executable = await this.ctx.subprocess.resolveExecutable(this.config.nodeExecutable, undefined, signal)
const executable = await this.ctx.subprocess.resolveExecutable(this.config.launch.executable, undefined, signal)
// Abort callbacks can settle execution before or during an awaited operation.
// oxlint-disable-next-line typescript/no-unnecessary-condition
if (settled) return await result.promise
const packaged = 'pkg' in process && this.config.bootstrapPath === undefined
const packaged = this.config.launch.kind === 'embedded'
const heapFlag = `--max-old-space-size=${this.config.maxOldGenerationSizeMb}`
const argv = [executable, ...packaged ? [] : [heapFlag], ...bootstrapArgs(this.ctx.fs, this.config, this.config.maxMessageBytes)]
const argv = [executable, ...packaged ? [] : [heapFlag],
...bootstrapArgs(this.ctx.fs, this.config.launch, this.config.maxMessageBytes)]
confined = policy.mode === 'danger-full-access' ? undefined : await this.ctx.sandbox.confine(argv, { ...policy, mode: policy.mode }, signal)
// oxlint-disable-next-line typescript/no-unnecessary-condition -- Cancellation can settle during awaited confinement.
if (settled) return await result.promise
@@ -1,26 +1,56 @@
/** Select source or built bootstrap assets in the mounted execution world. */
import { dirname, resolve } from 'node:path'
import { dirname, isAbsolute, resolve } from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
import type { FileSystem } from '@deepseek-ai/dsh-fs'
import { assertNever } from '@deepseek-ai/dsh-util-values'
/** Deployment-owned Node executable and optional preinstalled built bootstrap. */
export interface LaunchConfig {
/** Executable in the subprocess world; defaults to the current Node executable. */
nodeExecutable?: string
/** Absolute preinstalled built bootstrap in the execution world. */
/** Deployment-owned worker invocation in the subprocess execution world. */
export type LaunchConfig = {
/** Start a separately installed Node executable and JavaScript bootstrap. */
kind: 'node-script'
/** Executable name or path resolved by the subprocess provider. */
executable: string
/** Absolute preinstalled bootstrap; omitted maps this package's bootstrap into the execution world. */
bootstrapPath?: string
} | {
/** Start the private PTC worker embedded in a packaged executable. */
kind: 'embedded'
/** Packaged executable name or path resolved by the subprocess provider. */
executable: string
}
/**
* Resolve the local carrier default or validate an explicit execution-world invocation.
* @param config - explicit remote or local worker configuration; omitted selects this carrier.
* @returns the worker invocation without guessing from the execution world's paths.
*/
export function resolveLaunch(config?: LaunchConfig): LaunchConfig {
const launch: LaunchConfig = config ?? { kind: 'pkg' in process ? 'embedded' : 'node-script', executable: process.execPath }
if (launch.executable.length === 0) throw new Error('ptc-runtime-node: launch.executable must be non-empty')
if (launch.kind === 'node-script' && launch.bootstrapPath !== undefined && !isAbsolute(launch.bootstrapPath)) {
throw new Error('ptc-runtime-node: launch.bootstrapPath must be absolute')
}
return launch
}
/**
* Select explicit arguments without inheriting host loader or inspector flags.
* @param fs - Filesystem mapping host bootstrap assets into the process world.
* @param config - Optional preinstalled built bootstrap.
* @param config - resolved worker invocation.
* @param maxMessageBytes - Validated frame and queued-write limit.
* @returns Arguments following the resolved Node executable.
*/
export function bootstrapArgs(fs: FileSystem, config: LaunchConfig, maxMessageBytes: number): string[] {
if (config.bootstrapPath !== undefined) return [config.bootstrapPath, String(maxMessageBytes)]
if ('pkg' in process) return [String(maxMessageBytes)]
export function bootstrapArgs(fs: Pick<FileSystem, 'processPathFromHostPath'>, config: LaunchConfig, maxMessageBytes: number): string[] {
switch (config.kind) {
case 'embedded': return [String(maxMessageBytes)]
case 'node-script': return scriptBootstrapArgs(fs, config.bootstrapPath, maxMessageBytes)
/* v8 ignore next -- Callers supply the resolved, closed LaunchConfig union. */
default: return assertNever(config)
}
}
function scriptBootstrapArgs(fs: Pick<FileSystem, 'processPathFromHostPath'>, bootstrapPath: string | undefined, maxMessageBytes: number): string[] {
if (bootstrapPath !== undefined) return [bootstrapPath, String(maxMessageBytes)]
const mapped = (path: string): string => {
const result = fs.processPathFromHostPath(path)
if (result === undefined) throw new Error(`PTC runtime bootstrap is unavailable in the subprocess execution world: ${path}`)
@@ -117,8 +117,8 @@ describe('Node runtime host failures', () => {
[{ maxMessageBytes: 0x1_0000_0000 }, 'maxMessageBytes'],
[{ maxPendingCalls: 1.5 }, 'maxPendingCalls'],
[{ maxOldGenerationSizeMb: 1.5 }, 'maxOldGenerationSizeMb'],
[{ nodeExecutable: '' }, 'nodeExecutable'],
[{ bootstrapPath: 'relative-bootstrap.js' }, 'bootstrapPath'],
[{ launch: { kind: 'node-script', executable: '' } }, 'launch.executable'],
[{ launch: { kind: 'node-script', executable: process.execPath, bootstrapPath: 'relative-bootstrap.js' } }, 'bootstrapPath'],
])('rejects deployment configuration %j', async (config, field) => {
await expect(mountRuntime(new Context(), config)).rejects.toThrow(field)
})
@@ -496,22 +496,15 @@ describe('Node runtime host failures', () => {
expect(env.DSH_TEST_RUNTIME_SECRET).toBeUndefined()
})
it('selects the private packaged bootstrap without leaking ambient environment', async () => {
const h = await setup()
it('selects the remote embedded bootstrap without leaking ambient environment', async () => {
const h = await setup({ launch: { kind: 'embedded', executable: process.execPath } })
h.onBoot(() => { h.emit({ type: 'done' }) })
const prior = Object.getOwnPropertyDescriptor(process, 'pkg')
try {
Object.defineProperty(process, 'pkg', { configurable: true, value: {} })
expect((await h.start()).error).toBeUndefined()
const spec = h.spawn.mock.calls[0]?.[0]
expect(spec?.env?.DSH_PTC_RUNTIME_NODE).toBe('1')
expect(Object.hasOwn(spec?.env ?? {}, 'PATH')).toBe(false)
expect(spec?.argv).toEqual([process.execPath, '134217728'])
expect(Object.fromEntries(Object.entries(spec?.env ?? {}).filter(([, value]) => value !== undefined)))
.toEqual({ DSH_PTC_RUNTIME_NODE: '1', NODE_OPTIONS: '--max-old-space-size=512' })
} finally {
if (prior === undefined) Reflect.deleteProperty(process, 'pkg')
else Object.defineProperty(process, 'pkg', prior)
}
expect((await h.start()).error).toBeUndefined()
const spec = h.spawn.mock.calls[0]?.[0]
expect(spec?.env?.DSH_PTC_RUNTIME_NODE).toBe('1')
expect(Object.hasOwn(spec?.env ?? {}, 'PATH')).toBe(false)
expect(spec?.argv).toEqual([process.execPath, '134217728'])
expect(Object.fromEntries(Object.entries(spec?.env ?? {}).filter(([, value]) => value !== undefined)))
.toEqual({ DSH_PTC_RUNTIME_NODE: '1', NODE_OPTIONS: '--max-old-space-size=512' })
})
})
@@ -1,25 +1,35 @@
import { expect, it } from 'vitest'
import type { FileSystem } from '@deepseek-ai/dsh-fs'
import { bootstrapArgs } from '../src/launch.ts'
import { bootstrapArgs, resolveLaunch } from '../src/launch.ts'
const unmapped = { processPathFromHostPath: () => undefined }
const noMapping = { processPathFromHostPath: () => { throw new Error('must not map a remote worker') } }
it('uses an explicit installed bootstrap without pretending it maps the host file', () => {
const fs = { processPathFromHostPath: () => { throw new Error('must not map') } } as unknown as FileSystem
expect(bootstrapArgs(fs, { bootstrapPath: '/remote/process.js' }, 2048)).toEqual(['/remote/process.js', '2048'])
expect(bootstrapArgs(noMapping, { kind: 'node-script', executable: '/remote/node', bootstrapPath: '/remote/process.js' }, 2048))
.toEqual(['/remote/process.js', '2048'])
})
it('fails when the execution world cannot read the source bootstrap', () => {
const fs = { processPathFromHostPath: () => undefined } as unknown as FileSystem
expect(() => bootstrapArgs(fs, {}, 2048)).toThrow('unavailable in the subprocess execution world')
expect(() => bootstrapArgs(unmapped, { kind: 'node-script', executable: process.execPath }, 2048))
.toThrow('unavailable in the subprocess execution world')
})
it('selects the private packaged bootstrap through the existing executable', () => {
it('uses a remote embedded worker when the client runs ordinary Node', () => {
const launch = resolveLaunch({ kind: 'embedded', executable: '/remote/dsh-ssh-helper' })
expect(launch).toEqual({ kind: 'embedded', executable: '/remote/dsh-ssh-helper' })
expect(bootstrapArgs(noMapping, launch, 2048)).toEqual(['2048'])
})
it('resolves the local carrier once while preserving an explicit remote script choice', () => {
const prior = Object.getOwnPropertyDescriptor(process, 'pkg')
try {
Object.defineProperty(process, 'pkg', { configurable: true, value: {} })
const fs = { processPathFromHostPath: () => { throw new Error('pkg does not expose a host bootstrap file') } } as unknown as FileSystem
expect(bootstrapArgs(fs, {}, 2048)).toEqual(['2048'])
expect(resolveLaunch()).toEqual({ kind: 'embedded', executable: process.execPath })
const remote = resolveLaunch({ kind: 'node-script', executable: '/remote/node', bootstrapPath: '/remote/process.js' })
expect(bootstrapArgs(noMapping, remote, 2048)).toEqual(['/remote/process.js', '2048'])
} finally {
if (prior === undefined) Reflect.deleteProperty(process, 'pkg')
else Object.defineProperty(process, 'pkg', prior)
}
expect(resolveLaunch()).toEqual({ kind: 'node-script', executable: process.execPath })
})
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/ssh/README.md
README.md: 905c17a8e59cd95ded0fc8b299eac83f8530004e
README.zh.md: 9833c8c087070a3ce62699a0e4c88f998491f79d
README.md: ff7db269993f75d6137ebfdb86edbfd2f3b51223
README.zh.md: 0a8944680ea26acdb7923bb68e63e238328852f7
+1
View File
@@ -23,6 +23,7 @@ This family runs files, ordinary processes, terminals and sandbox enforcement on
| Package | Responsibility | Service |
|---|---|---|
| [`ssh`](ssh/README.md) | Connection, helper identity and transport lifecycle | `ctx.ssh` |
| [`ssh-helper-runtime`](ssh-helper-runtime/README.md) | Private Node-free executable distribution | — |
| [`fs-ssh`](fs-ssh/README.md) | Remote file identity, reads and guarded atomic mutations | `ctx.fs` |
| [`subprocess-ssh`](subprocess-ssh/README.md) | Executable lookup, processes, control streams and terminals | `ctx.subprocess` |
| [`sandbox-ssh`](sandbox-ssh/README.md) | Remote file-effect confinement and enforcement facts | `ctx.sandbox` |
+1
View File
@@ -23,6 +23,7 @@ kind: "package-group"
| 包 | 职责 | 服务 |
|---|---|---|
| [`ssh`](ssh/README.zh.md) | 连接、辅助程序身份及传输生命周期 | `ctx.ssh` |
| [`ssh-helper-runtime`](ssh-helper-runtime/README.zh.md) | 无须安装 Node 的私有可执行文件发行包 | — |
| [`fs-ssh`](fs-ssh/README.zh.md) | 远端文件身份、读取及带保护的原子修改 | `ctx.fs` |
| [`subprocess-ssh`](subprocess-ssh/README.zh.md) | 可执行文件查找、进程、控制流及终端 | `ctx.subprocess` |
| [`sandbox-ssh`](sandbox-ssh/README.zh.md) | 远端文件效果限制及执行信息 | `ctx.sandbox` |
@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/ssh/ssh-helper-runtime/README.md
README.md: 4039fd6bf4a8312459d282d06e2f25c2b8a49334
README.zh.md: 13058ba86d8b227e0b78a9663fc12b12fae562d7
+107
View File
@@ -0,0 +1,107 @@
---
description: "Build and verify SSH helper executables with an embedded Node runtime for Linux and macOS."
kind: "package-library"
---
# @deepseek-ai/dsh-ssh-helper-runtime
English | [中文](README.zh.md)
## Summary
Run the SSH helper on a remote machine without installing Node or npm packages. Each archive includes the executable, its native resources and integrity metadata. The private carrier supports managed processes, terminals, sandboxing and an embedded PTC worker through the existing SSH providers. Project commands still use the remote machine’s own development tools.
## Table of Contents
- [Use this package](#use-this-package)
- [Understand the implementation](#understand-the-implementation)
- [Further Exploration](#further-exploration)
- [Model Experience](#model-experience)
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
- [Dev Note](#dev-note)
-----
<a id="use-this-package"></a>
## Use this package
### Build and installation
This private workspace produces release archives; it is not a profile plugin or an npm CLI. From a checkout with dependencies installed, build the current native platform:
```sh
pnpm exec tsx scripts/build-exe-for-ssh-helper.ts
```
The builder requires Node 24, a C/C++ compiler and the development headers from that Node installation. Linux additionally needs `musl-gcc`; its node-pty addon must meet the glibc baseline. The [native workflow](../../../.github/workflows/build-exe-for-ssh-helper.yml) prepares the compatible addon before packaging. `--target` selects one native target, `--skip-build` reuses prepared JS/native outputs, and `--dry-run` validates the target and dependency declarations without building.
| Target | Native archive | Checked deployment baseline |
|---|---|---|
| `node24-linux-x64` | `linux-x64` | glibc 2.28 |
| `node24-linux-arm64` | `linux-arm64` | glibc 2.28 |
| `node24-macos-x64` | `macos-x64` | macOS 14 deployment target |
| `node24-macos-arm64` | `macos-arm64` | macOS 14 deployment target |
Outputs live under `dist-exe/ssh-helper/`. Each `dsh-ssh-helper-<version>-<platform>-<arch>.tar.gz` has a companion `.sha256` file. The archive contains `dsh-ssh-helper`, `native/system/`, licenses and `manifest.json`; macOS also includes `dsh-ssh-helper-spawn-helper`. The manifest records the source commit, whether the source tree was dirty, embedded Node version, protocol and every payload file’s digest and permissions.
Verify the archive checksum, then extract the whole directory into a versioned runtime location outside the project and replaced temporary mounts. Keep the native resources beside the executable. The installation can be read-only; the helper user needs a writable temporary/cache location. Configure [`dsh-ssh`](../ssh/README.md) with `launch: { kind: "executable" }`, the absolute `helper` path and the executable’s SHA-256 from the manifest. The client obtains the embedded PTC invocation through `ctx.ssh.ptcLaunch`.
### Verification and publication
The [artifact verifier](../../../scripts/verify-ssh-helper-artifact.ts) accepts `--archive` and an optional `--report` path. It validates and relocates the archive, makes installation files read-only, and runs two concurrent Loader compositions sharing a fresh native cache. Those compositions exercise files, processes, PTY, native flock and PTC through the SSH providers. macOS also runs a worker under a policy denying the checkout and host Node. `--sandbox=required` requires real confinement; Linux’s additional `--backend=landlock-run` check makes the bwrap probe unavailable and requires the packaged Landlock runner.
The [OpenSSH verifier](../../../scripts/verify-ssh-helper-ssh.ts) accepts a Linux `--archive`. It creates a temporary glibc 2.28 SSH server with no Node installation, mounts only the release and a disposable public key, and exercises the production connection. Its portability lane makes no kernel-confinement claim; the native host lane owns that check. Test identities, host keys, ports and workspaces are private to the run.
PR CI requires Linux x64; master CI covers Linux arm64 and both macOS architectures. The manually dispatched native workflow defaults to all four targets. The [publication workflow](../../../.github/workflows/publish-ssh-helper.yml) accepts the matching `dsh-v<version>` tag, requires complete same-commit evidence from clean builds, and attaches the tested archives and `SHA256SUMS` to that GitHub release. A missing release is created as a draft. GitHub Actions owns native platform and publication execution; deployment-target inspection alone does not establish execution on the oldest macOS version.
-----
<a id="understand-the-implementation"></a>
## Understand the implementation
<details>
<summary>Implementation internals — click to expand</summary>
The carrier embeds its closed dependency set with the repository’s patched `pkg --sea` pipeline. Its entry dispatches the SSH helper, managed subprocess runner or PTC worker. Shared [deployment helpers](../../../scripts/executable-packaging.ts) also serve the Python SDK builder. Production deploy restores development installation state before invoking build tools, so pnpm’s automatic dependency check cannot prune them.
The operating system must execute the Landlock and PTY launchers from real files. A process-local resolution hook points the system package at its co-shipped native directory; node-pty uses its existing executable-relative spawn-helper convention. Binary inspection covers the embedded native modules before packaging and the external resources before publication.
</details>
-----
<a id="further-exploration"></a>
## Further Exploration
- [SSH connection](../ssh/README.md) — authentication, invocation and lifecycle.
- [PTC Node runtime](../../ptc-runtime/ptc-runtime-node/README.md) — explicit worker launch and execution limits.
- [SSH subsystem](../../../docs/subsystems/ssh.md) — remote execution coordinates.
-----
<a id="model-experience"></a>
## Model Experience
None, as this private process carrier registers no model-facing tools or prompt content; its consumers own operation results.
#### KV Cache effect
The carrier adds no request-prefix content.
## Known Limitations and Deferred Work
<a id="known-limitations-and-deferred-work"></a>
- Windows and musl-based Linux are not build targets. Sandbox availability still depends on the target kernel and system tools.
- Automatic download, deployment, updates and connection UI are separate from archive production.
- The executable accepts private worker invocations, not arbitrary Node CLI arguments. Project `node` commands and nested JavaScript subprocesses need their own Node installation.
<a id="dev-note"></a>
### Dev Note
<details>
<summary>Working context for maintainers — click to expand</summary>
No invariant companion is published. This process entry has no independently observed Cordis state; archive verification and the owning providers enforce its deployment and execution obligations.
</details>
@@ -0,0 +1,107 @@
---
description: "构建并验证内嵌 Node 运行时的 Linux 与 macOS SSH helper 可执行文件。"
kind: "package-library"
---
# @deepseek-ai/dsh-ssh-helper-runtime
[English](README.md) | 中文
## 概述
远端机器无须安装 Node 或 npm 包即可运行 SSH helper。每个发行包都包含可执行文件、原生资源和完整性元数据。这个私有载体通过既有 SSH 提供方支持托管进程、终端、沙箱和内嵌 PTC worker。项目命令仍使用远端机器自身的开发工具。
## 目录
- [使用本包](#use-this-package)
- [理解实现](#understand-the-implementation)
- [进一步探索](#further-exploration)
- [模型体验](#model-experience)
- [已知限制与延后工作](#known-limitations-and-deferred-work)
- [开发备注](#dev-note)
-----
<a id="use-this-package"></a>
## 使用本包
### 构建与安装
这个私有工作区生成发行压缩包,不是配置插件或 npm CLI。在已安装依赖的源码目录中构建当前原生平台:
```sh
pnpm exec tsx scripts/build-exe-for-ssh-helper.ts
```
构建器需要 Node 24、C/C++ 编译器和该 Node 安装附带的开发头文件。Linux 还需要 `musl-gcc`,其 node-pty 扩展必须满足 glibc 基线。[原生构建工作流](../../../.github/workflows/build-exe-for-ssh-helper.yml) 在打包前准备兼容扩展。`--target` 选择一个原生目标,`--skip-build` 复用已准备的 JS/原生输出,`--dry-run` 校验目标与依赖声明而不构建。
| 目标 | 原生发行包 | 检查的部署基线 |
|---|---|---|
| `node24-linux-x64` | `linux-x64` | glibc 2.28 |
| `node24-linux-arm64` | `linux-arm64` | glibc 2.28 |
| `node24-macos-x64` | `macos-x64` | macOS 14 部署目标 |
| `node24-macos-arm64` | `macos-arm64` | macOS 14 部署目标 |
产物位于 `dist-exe/ssh-helper/`。每个 `dsh-ssh-helper-<version>-<platform>-<arch>.tar.gz` 都有配套的 `.sha256` 文件。压缩包包含 `dsh-ssh-helper`、`native/system/`、许可证和 `manifest.json`,macOS 还包含 `dsh-ssh-helper-spawn-helper`。清单记录源码提交、源码树是否存在未提交修改、内嵌 Node 版本、协议,以及每个产物文件的摘要和权限。
验证压缩包摘要后,将整个目录解压到项目及被替换的临时挂载之外的版本化运行时位置。原生资源须与可执行文件保持相对位置。安装目录可以只读,但 helper 用户需要可写的临时/缓存位置。为 [`dsh-ssh`](../ssh/README.zh.md) 配置 `launch: { kind: "executable" }`、绝对 `helper` 路径,以及清单中可执行文件的 SHA-256。客户端通过 `ctx.ssh.ptcLaunch` 获取内嵌 PTC 启动方式。
### 验证与发布
[产物验证器](../../../scripts/verify-ssh-helper-artifact.ts) 接受 `--archive` 和可选的 `--report` 路径。它校验并移动解压后的发行包,将安装文件设为只读,再运行两个共享全新原生缓存的并发 Loader 组合。这些组合通过 SSH 提供方验证文件、进程、PTY、原生 flock 和 PTC。macOS 还在拒绝访问源码目录和宿主 Node 的策略下运行 worker。`--sandbox=required` 要求真实隔离;Linux 额外的 `--backend=landlock-run` 检查使 bwrap 探测不可用,并要求使用包内 Landlock 启动器。
[OpenSSH 验证器](../../../scripts/verify-ssh-helper-ssh.ts) 接受 Linux `--archive`。它创建未安装 Node 的临时 glibc 2.28 SSH 服务器,仅挂载发行包和一次性公钥,并验证生产连接。这个可移植性测试不声明内核隔离能力,该检查由原生主机测试负责。测试身份、主机密钥、端口和工作区均由本次运行独占。
PR CI 要求 Linux x64 通过,master CI 覆盖 Linux arm64 和两种 macOS 架构。手动触发的原生工作流默认构建全部四个目标。[发布工作流](../../../.github/workflows/publish-ssh-helper.yml) 接受匹配的 `dsh-v<version>` 标签,要求干净源码构建提供同一提交的完整验证证据,并将已测试的压缩包及 `SHA256SUMS` 附加到对应 GitHub Release。Release 不存在时创建草稿。原生平台与发布执行由 GitHub Actions 负责;仅检查部署目标不能证明已在最低 macOS 版本上运行。
-----
<a id="understand-the-implementation"></a>
## 理解实现
<details>
<summary>实现细节 — 点击展开</summary>
载体通过仓库带补丁的 `pkg --sea` 流程内嵌完整依赖集合。入口分派 SSH helper、托管子进程启动器或 PTC worker。共享的[部署函数](../../../scripts/executable-packaging.ts) 也用于 Python SDK 构建器。生产部署会在调用构建工具前恢复开发依赖安装状态,避免 pnpm 的自动依赖检查将它们删除。
操作系统必须从真实文件执行 Landlock 和 PTY 启动器。进程内的解析钩子将 system 包指向随包分发的原生目录;node-pty 使用其既有的相对可执行文件定位 spawn-helper 的约定。二进制检查在打包前覆盖内嵌原生模块,并在发布前覆盖外部资源。
</details>
-----
<a id="further-exploration"></a>
## 进一步探索
- [SSH 连接](../ssh/README.zh.md) — 鉴权、启动与生命周期。
- [PTC Node 运行时](../../ptc-runtime/ptc-runtime-node/README.zh.md) — 显式 worker 启动与执行限制。
- [SSH 子系统](../../../docs/subsystems/ssh.zh.md) — 远端执行坐标。
-----
<a id="model-experience"></a>
## 模型体验
None,因为这个私有进程载体不注册面向模型的工具或提示内容,操作结果由其消费方负责。
#### KV Cache 影响
载体不添加请求前缀内容。
## 已知限制与延后工作
<a id="known-limitations-and-deferred-work"></a>
- 构建目标不包含 Windows 和基于 musl 的 Linux。沙箱可用性仍取决于目标内核和系统工具。
- 自动下载、部署、更新和连接 UI 独立于发行包生产。
- 可执行文件接受私有 worker 调用,不接受任意 Node CLI 参数。项目中的 `node` 命令和嵌套 JavaScript 子进程需要单独安装 Node。
<a id="dev-note"></a>
### 开发备注
<details>
<summary>维护者工作上下文 — 点击展开</summary>
不发布 invariant 配套入口。这个进程入口没有独立观察的 Cordis 状态;产物校验与所属提供方负责其部署和执行义务。
</details>
@@ -0,0 +1,64 @@
{
"name": "@deepseek-ai/dsh-ssh-helper-runtime",
"description": "Private executable carrier for the POSIX SSH helper and managed workers",
"version": "0.1.7-alpha.1",
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./package.json": "./package.json"
},
"files": [
"lib/index.js",
"lib/types/**/*.d.ts"
],
"license": "MIT",
"peerDependencies": {
"@deepseek-ai/cordis": "workspace:^"
},
"devDependencies": {
"@deepseek-ai/cordis": "workspace:^",
"@deepseek-ai/dsh-app-boot": "workspace:^",
"@deepseek-ai/dsh-fs-ssh": "workspace:^",
"@deepseek-ai/dsh-subprocess-ssh": "workspace:^",
"@deepseek-ai/dsh-sandbox-ssh": "workspace:^"
},
"dependencies": {
"@deepseek-ai/cordis": "workspace:^",
"@deepseek-ai/cosmokit": "workspace:^",
"@deepseek-ai/dsh-agent": "workspace:^",
"@deepseek-ai/dsh-brand": "workspace:^",
"@deepseek-ai/dsh-fs": "workspace:^",
"@deepseek-ai/dsh-fs-local": "workspace:^",
"@deepseek-ai/dsh-fs-sandbox": "workspace:^",
"@deepseek-ai/dsh-http-proxy": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-lazy-require": "workspace:^",
"@deepseek-ai/dsh-llm": "workspace:^",
"@deepseek-ai/dsh-ptc-runtime": "workspace:^",
"@deepseek-ai/dsh-ptc-runtime-node": "workspace:^",
"@deepseek-ai/dsh-sandbox": "workspace:^",
"@deepseek-ai/dsh-sandbox-local": "workspace:^",
"@deepseek-ai/dsh-sandbox-policy": "workspace:^",
"@deepseek-ai/dsh-sandbox-windows-acl": "workspace:^",
"@deepseek-ai/dsh-scope": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",
"@deepseek-ai/dsh-session-projection": "workspace:^",
"@deepseek-ai/dsh-ssh": "workspace:^",
"@deepseek-ai/dsh-subprocess": "workspace:^",
"@deepseek-ai/dsh-subprocess-local": "workspace:^",
"@deepseek-ai/dsh-system-prompt": "workspace:^",
"@deepseek-ai/dsh-timeout": "workspace:^",
"@deepseek-ai/dsh-typert-protocol": "workspace:^",
"@deepseek-ai/dsh-util-crypto": "workspace:^",
"@deepseek-ai/dsh-util-values": "workspace:^",
"@deepseek-ai/dsh-win32-process": "workspace:^",
"@deepseek-ai/node-addon-system": "workspace:^",
"@deepseek-ai/schemastery": "workspace:^"
}
}
@@ -0,0 +1,39 @@
/** Private SEA dispatcher for the SSH helper and its managed process workers. */
/* v8 ignore file -- the executable artifact suite runs this entry through pkg's SEA loader. */
import { registerHooks } from 'node:module'
import { statSync } from 'node:fs'
import { dirname, join } from 'node:path'
import { isSea } from 'node:sea'
import { pathToFileURL } from 'node:url'
/**
* Dispatch the co-shipped helper or worker once from the private SEA bootstrap.
* @returns after the selected process entry has completed its cleanup.
* @throws when invoked outside SEA or when native resources cannot be loaded.
*/
export async function runSshHelperRuntime(): Promise<void> {
if (!isSea()) throw new Error('SSH helper runtime must run from its packaged executable')
// The operating system executes Landlock outside the SEA virtual filesystem.
const nativeManifest = join(dirname(process.execPath), 'native', 'system', 'package.json')
statSync(nativeManifest)
const platformPackage = `@deepseek-ai/node-addon-system-${process.platform}-${process.arch}/package.json`
registerHooks({
resolve(specifier, context, nextResolve) {
if (specifier === platformPackage) return { url: pathToFileURL(nativeManifest).href, shortCircuit: true }
return nextResolve(specifier, context)
},
})
const selection = process.env.DSH_SUBPROCESS_RUNNER
Reflect.deleteProperty(process.env, 'DSH_SUBPROCESS_RUNNER')
if (process.env.DSH_PTC_RUNTIME_NODE === '1') {
Reflect.deleteProperty(process.env, 'DSH_PTC_RUNTIME_NODE')
await import('@deepseek-ai/dsh-ptc-runtime-node/process')
} else if (selection !== undefined) {
const { runSelectedSubprocessRunner } = await import('@deepseek-ai/dsh-subprocess-local/runner')
await runSelectedSubprocessRunner(selection)
} else {
await import('@deepseek-ai/dsh-ssh/helper')
}
}
@@ -0,0 +1,96 @@
/** Test-only transport to a real helper executable, retaining authenticated local stream sockets. */
import { spawn } from 'node:child_process'
import { createConnection, type Socket } from 'node:net'
import { once } from 'node:events'
import assert from 'node:assert/strict'
import { Context, Service } from '@deepseek-ai/cordis'
import { z } from 'zod'
import { SshRpcPeer, SSH_PROTOCOL_VERSION } from '../../../ssh/src/protocol.ts'
import { helloSchema, type SshStreamEndpoint } from '../../../ssh/src/schemas.ts'
import { authenticateStream } from '../../../ssh/src/stream-security.ts'
interface Config { executable: string; helperHash: string; workspace: string; home: string; path: string }
/** Loader-mounted fixture; only its administrative transport differs from SshConnection. */
export default class DirectHelper extends Service {
readonly ready: Promise<z.infer<typeof helloSchema>>
private readonly rpc: SshRpcPeer
private readonly child: ReturnType<typeof spawn>
private readonly exited: Promise<void>
private readonly sockets = new Set<Socket>()
private closing: Promise<void> | undefined
private diagnostic = ''
private readonly deadline: NodeJS.Timeout
constructor(ctx: Context, private readonly config: Config) {
super(ctx, 'ssh')
const child = spawn(config.executable, [], {
cwd: config.workspace, stdio: ['pipe', 'pipe', 'pipe'],
env: { HOME: config.home, PATH: config.path, TMPDIR: config.home, NODE_OPTIONS: '--disable-sigusr1', DSH_ARTIFACT_SECRET: 'must-not-reach-program' },
})
this.child = child
this.exited = new Promise<void>((resolve, reject) => {
child.once('error', reject)
child.once('close', (code, signal) => {
if (code === 0 && signal === null) resolve()
else reject(new Error(`Artifact helper exited (${code ?? signal}): ${this.diagnostic}`))
})
})
void this.exited.catch(() => {})
child.stderr.on('data', (chunk: Buffer) => { this.diagnostic = (this.diagnostic + chunk.toString()).slice(-8192) })
this.rpc = new SshRpcPeer(child.stdout, child.stdin, 64 * 1024 * 1024, 128)
child.once('error', (error) => { this.rpc.close(error) })
this.deadline = setTimeout(() => { child.kill('SIGKILL') }, 90_000)
this.ready = this.rpc.request('hello', { protocol: SSH_PROTOCOL_VERSION, workspace: config.workspace, leaseMs: 60_000 }, helloSchema, AbortSignal.timeout(20_000))
.then((hello) => {
assert.equal(hello.hash, config.helperHash)
assert.equal(hello.executable, config.executable)
return hello
})
void this.ready.catch(() => {})
ctx.effect(() => () => this.dispose())
}
async [Service.init](): Promise<void> { await this.ready }
get ptcLaunch(): { kind: 'embedded'; executable: string } { return { kind: 'embedded', executable: this.config.executable } }
async request<T>(method: string, params: unknown, schema: z.ZodType<T>, signal?: AbortSignal): Promise<T> {
return this.rpc.request(method, params, schema, signal ?? AbortSignal.timeout(30_000))
}
async connectStream(endpoint: SshStreamEndpoint): Promise<Socket> {
const socket = createConnection({ path: endpoint.path, allowHalfOpen: true })
this.own(socket)
await once(socket, 'connect')
const secure = await authenticateStream(socket, endpoint.capability, 10_000)
this.own(secure)
return secure
}
private own(socket: Socket): void {
this.sockets.add(socket)
socket.on('error', () => { socket.destroy() })
socket.once('close', () => { this.sockets.delete(socket) })
}
dispose(): Promise<void> { this.closing ??= this.close(); return this.closing }
private async close(): Promise<void> {
try {
await this.ready
await this.rpc.request('close', {}, z.null(), AbortSignal.timeout(10_000))
this.child.stdin?.end()
} finally {
this.rpc.close()
const sockets = [...this.sockets].reverse().map(async (socket) => {
const closed = socket.closed ? Promise.resolve() : once(socket, 'close').catch(() => {})
socket.destroy()
await closed
})
this.child.stdin?.destroy()
try { await this.exited; await Promise.all(sockets) }
finally { clearTimeout(this.deadline) }
}
}
}
+142
View File
@@ -0,0 +1,142 @@
/** Test-only Loader consumer exercising files, processes, terminals and PTC through the SSH providers. */
import assert from 'node:assert/strict'
import { writeFile } from 'node:fs/promises'
import { basename } from 'node:path'
import { Context } from '@deepseek-ai/cordis'
import { boot } from '@deepseek-ai/dsh-app-boot'
import type { SubprocessHandle } from '@deepseek-ai/dsh-subprocess'
import type {} from '@deepseek-ai/dsh-ssh'
import type {} from '@deepseek-ai/dsh-fs-ssh'
import type {} from '@deepseek-ai/dsh-subprocess-ssh'
import type {} from '@deepseek-ai/dsh-sandbox-ssh'
import type {} from '@deepseek-ai/dsh-ptc-runtime-node'
const [configPath, reportPath, sandbox] = process.argv.slice(2)
if (configPath === undefined || reportPath === undefined || !['required', 'unavailable', 'portable'].includes(sandbox ?? '')) throw new Error('Artifact driver requires config, report, and expected sandbox availability')
const ctx = await boot('ssh-helper-artifact', configPath)
const checks: string[] = []
const cleanupErrors: string[] = []
const diagnostics = new Context()
diagnostics.logger = ctx.logger
diagnostics.logger.exporter({ levels: { default: 2 }, export: ({ type, args }) => {
if (type === 'error') cleanupErrors.push(args.map(value => String(value)).join(' '))
} })
async function completed(handle: SubprocessHandle): Promise<string> {
const outcome = await handle.done
assert.equal(await handle.waitForExit(), true)
assert.deepEqual(outcome, { exitCode: 0, signal: null })
return handle.collected.stdout?.readFrom(0).text ?? ''
}
try {
const hello = await ctx.ssh.ready
const root = hello.workspace
assert.equal(hello.kind, 'executable')
assert.equal(ctx.ssh.ptcLaunch.kind, 'embedded')
checks.push('executable-handshake')
const target = await ctx.fs.resolve(`${root}/sample.txt`)
await ctx.fs.writeText(target, 'first\n你好\n')
assert.equal(await ctx.fs.readText(target), 'first\n你好\n')
assert.equal(Buffer.from(await ctx.fs.readByteRange(target, { offset: 0, length: 5 })).toString(), 'first')
assert.equal(Buffer.from(await ctx.fs.readBytes(target, undefined, 1024)).toString(), 'first\n你好\n')
await ctx.fs.editText(target, { oldString: 'first', newString: 'second', replaceAll: false })
assert.equal(await ctx.fs.readText(target), 'second\n你好\n')
let streamed = ''
for await (const part of await ctx.fs.streamText(target)) streamed += part
assert.equal(streamed, 'second\n你好\n')
assert((await ctx.fs.listDir(await ctx.fs.resolve(root))).some(entry => entry.name === 'sample.txt'))
await assert.rejects(ctx.fs.writeText(target, 'denied', undefined, undefined, { mode: 'read-only', workspaceRoot: root }))
checks.push('guarded-files-and-streams')
const options = { cwd: root, graceMs: 1000, stdio: { stdin: 'ignore', stdout: { maxBytes: 65_536 }, stderr: { maxBytes: 65_536 } } } as const
const ordinary = ctx.subprocess.spawn({ ...options, argv: ['/bin/sh', '-c', 'printf "ordinary"; printf "diagnostic" >&2'] })
assert.equal(await completed(ordinary), 'ordinary')
assert.equal(ordinary.collected.stderr?.readFrom(0).text, 'diagnostic')
checks.push('managed-process-output')
if (process.env.DSH_VERIFY_NO_NODE === '1') {
assert.equal(await completed(ctx.subprocess.spawn({ ...options, argv: ['/bin/sh', '-c', 'set -e; if command -v node; then exit 90; fi; test ! -e /usr/bin/node; test ! -e /usr/local/bin/node; printf no-node'] })), 'no-node')
checks.push('target-has-no-node')
}
const sleeping = ctx.subprocess.spawn({ ...options, stdio: { ...options.stdio, stdout: 'pipe' }, argv: ['/bin/sh', '-c', 'printf ready; exec /bin/sleep 60'] })
await new Promise<void>((resolve, reject) => {
const stdout = sleeping.stdout!
let text = ''
const cleanup = (): void => { clearTimeout(timer); stdout.off('data', read); stdout.off('error', failed); stdout.off('end', ended) }
const failed = (error: Error): void => { cleanup(); reject(error) }
const ended = (): void => { failed(new Error('Sleeping process exited before readiness')) }
const read = (bytes: Buffer): void => { text += bytes.toString(); if (text.includes('ready')) { cleanup(); resolve() } }
const timer = setTimeout(() => { failed(new Error('Sleeping process did not become ready')) }, 10_000)
stdout.on('data', read); stdout.once('error', failed); stdout.once('end', ended)
})
sleeping.terminate()
await sleeping.done
assert.equal(await sleeping.waitForExit(), true)
sleeping.stdout?.destroy()
checks.push('process-cancellation')
const terminal = await ctx.subprocess.spawnTerminal({ argv: ['/bin/sh'], cwd: root, env: {}, terminalType: 'xterm', cols: 80, rows: 24, graceMs: 1000 })
let terminalText = ''
const terminalRead = (async () => { for await (const bytes of terminal.output) terminalText += Buffer.from(bytes).toString() })()
try {
await terminal.resize(100, 35)
await terminal.write("stty size; printf 'pty-result:%s\\n' 42; exit\n")
assert.equal((await terminal.done).exitCode, 0)
await terminalRead
assert(terminalText.includes('pty-result:42'))
assert(terminalText.includes('35 100'))
} finally { await terminal.terminate(); await terminalRead }
checks.push('native-pty')
const runtime = ctx.ptcRuntime
const bindings = [{ global: 'tools', functions: { echo: async (input: unknown) => String(input) } }]
const policy = { mode: 'danger-full-access', workspaceRoot: root } as const
const result = await runtime.run(runtime.resolve({
program: 'const fs=await import("node:fs/promises"); await fs.writeFile("ptc.txt","embedded"); console.log("worker-output"); return {value:await tools.echo("bridge"),env:Object.keys(process.env),executable:process.execPath,cwd:process.cwd()};',
bindings, cwd: root, sandboxPolicy: policy,
}))
assert.equal(result.error, undefined, JSON.stringify(result))
assert.deepEqual(result.value, { value: 'bridge', env: [], executable: hello.executable, cwd: root })
assert(result.logs.join('').includes('worker-output'))
assert.equal(await ctx.fs.readText(await ctx.fs.resolve(`${root}/ptc.txt`)), 'embedded')
const locked = await runtime.run(runtime.resolve({
program: 'const fs=await import("node:fs"); const {tryLockExclusive}=await import("@deepseek-ai/node-addon-system/flock"); const fd=fs.openSync("native-lock","w"); try { await tryLockExclusive(fd); return "locked" } finally { fs.closeSync(fd) }',
bindings: [], cwd: root, sandboxPolicy: policy,
}))
assert.equal(locked.error, undefined, JSON.stringify(locked))
assert.equal(locked.value, 'locked')
checks.push('native-flock')
const timed = await runtime.run(runtime.resolve({ program: 'while(true){}', bindings: [], cwd: root, sandboxPolicy: policy, timeoutMs: 1500 }))
assert.equal(timed.error?.kind, 'timeout')
checks.push('embedded-ptc-and-deadline')
if (sandbox === 'required') {
const runner = process.env.DSH_EXPECT_SANDBOX_RUNNER
if (runner !== undefined) {
const invocation = await ctx.sandbox.confine(['/bin/sh', '-c', 'true'], { mode: 'read-only', workspaceRoot: root })
assert.equal(basename(invocation.argv[0]!), runner)
}
const confined = await runtime.run(runtime.resolve({
program: 'return await (await import("node:fs/promises")).readFile("ptc.txt","utf8")', bindings: [], cwd: root,
sandboxPolicy: { mode: 'read-only', workspaceRoot: root },
}))
assert.equal(confined.error, undefined, JSON.stringify(confined))
assert.equal(confined.value, 'embedded')
const denied = await runtime.run(runtime.resolve({
program: 'await(await import("node:fs/promises")).writeFile("forbidden.txt","bad")', bindings: [], cwd: root,
sandboxPolicy: { mode: 'read-only', workspaceRoot: root },
}))
assert.equal(denied.sandbox?.denied, true, JSON.stringify(denied))
checks.push('sandbox-enforcement')
} else if (sandbox === 'unavailable') {
await assert.rejects(ctx.sandbox.confine(['/bin/sh', '-c', 'true'], { mode: 'read-only', workspaceRoot: root }), /sandbox|runner/i)
checks.push('sandbox-unavailable-refusal')
}
await writeFile(reportPath, `${JSON.stringify({ checks, platform: hello.platform, nodeVersion: hello.nodeVersion, sandboxLane: sandbox }, null, 2)}\n`)
} finally {
try { await ctx.fiber.dispose() }
finally { await diagnostics.fiber.dispose() }
assert.deepEqual(cleanupErrors, [], 'Loader or provider cleanup reported errors')
}
@@ -0,0 +1,8 @@
# glibc 2.28 target without Node or a checkout; only the release payload is mounted at runtime.
FROM almalinux:8.10
RUN dnf install -y openssh-server procps-ng shadow-utils passwd && dnf clean all \
&& useradd --create-home dsh && passwd -d dsh \
&& rm -f /etc/ssh/ssh_host_*key*
COPY sshd_config /etc/ssh/sshd_config
COPY start.sh /start.sh
ENTRYPOINT ["/bin/sh", "/start.sh"]
@@ -0,0 +1,13 @@
Port 22
ListenAddress 0.0.0.0
HostKey /etc/ssh/ssh_host_ed25519_key
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
UsePAM no
AuthenticationMethods publickey
AllowUsers dsh
AllowTcpForwarding yes
AllowStreamLocalForwarding yes
PidFile /run/sshd.pid
LogLevel VERBOSE
@@ -0,0 +1,7 @@
#!/bin/sh
set -eu
ssh-keygen -q -t ed25519 -N '' -f /etc/ssh/ssh_host_ed25519_key
install -d -m 700 -o dsh -g dsh /home/dsh/.ssh
install -m 600 -o dsh -g dsh /fixture-key.pub /home/dsh/.ssh/authorized_keys
install -d -m 700 -o dsh -g dsh /home/dsh/workspace
exec /usr/sbin/sshd -D -e -f /etc/ssh/sshd_config
@@ -0,0 +1,24 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib/types"
},
"include": [
"src"
],
"references": [
{
"path": "../../../vendor/cordis"
},
{
"path": "../ssh"
},
{
"path": "../../subprocess/subprocess-local"
},
{
"path": "../../ptc-runtime/ptc-runtime-node"
}
]
}
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/ssh/ssh/README.md
README.md: c978538491c3ce8045663b9e675d2141e541cacc
README.zh.md: 20c91b199aebdcf308d3e3f6c03f9188315e994a
README.md: 3df08097a354c01bd7f2ad3cf928f7c4a79d05f9
README.zh.md: acec72885c5fa01cb9de1db881b0a7c72d9b1fa5
+6 -5
View File
@@ -31,20 +31,21 @@ Compose this service with [`fs-ssh`](../fs-ssh/README.md), [`subprocess-ssh`](..
Both endpoints require Linux or macOS. The local `ssh` command must support connection multiplexing and Unix-socket forwarding; the server must permit that forwarding. Configure the alias, credentials and known-host entry before startup: the service enables `BatchMode`, requires strict host-key checking, disables agent forwarding and adds no interactive authentication flow.
Install the built helper and its matching runtime dependencies on the remote host. Keep Node, helper, bootstrap and their dependencies outside the workspace and writable temporary roots. They must also remain outside a backend’s replaced temporary tree, such as bwrap’s private `/tmp`; the workspace may still be under `/tmp`. Digest verification detects an unexpected installed artifact after helper startup; it does not make writable deployment files safe to execute or authenticate a malicious SSH host.
Install an [SSH helper executable archive](../ssh-helper-runtime/README.md), or the built helper script with its matching Node dependencies. Keep the runtime outside the workspace and writable temporary roots. They must also remain outside a backend’s replaced temporary tree, such as bwrap’s private `/tmp`; the workspace may still be under `/tmp`. Digest verification detects an unexpected installed artifact after helper startup; it does not make writable deployment files safe to execute or authenticate a malicious SSH host.
| Field | Default | Meaning |
|---|---|---|
| `host` | required | Existing OpenSSH host alias |
| `node`, `helper`, `workspace` | required | Absolute remote Node executable, bundled helper entry and default workspace |
| `launch` | required | `{ kind: "executable" }`, or `{ kind: "node-script", node: "/absolute/node" }` |
| `helper`, `workspace` | required | Absolute remote helper entry and default workspace |
| `helperHash` | required | Lowercase SHA-256 of the installed helper entry |
| `bootstrapPath`, `bootstrapHash` | omitted | Paired remote PTC entry and its lowercase SHA-256 |
| `launch.bootstrapPath`, `launch.bootstrapHash` | omitted | Paired remote PTC script and digest; only for `node-script` |
| `requestTimeoutMs` | `30000` | Connection and administrative-request deadline, from 1 through 2,147,483,647 ms |
| `maxFrameBytes` | `67108864` | Per-message JSON payload ceiling, at most 64 MiB |
| `maxPending` | `128` | Ordinary outstanding requests; heartbeat and bounded cleanup requests have reserved capacity |
| `leaseMs` | `30000` | Helper heartbeat lease, from 3000 to 600000 ms |
For PTC, configure both bootstrap fields and pass the verified `ctx.ssh.nodeExecutable` and `ctx.ssh.bootstrapPath` to [`NodePtcRuntime`](../../ptc-runtime/ptc-runtime-node/README.md). Basic filesystem and Bash use may omit the pair. The `bootstrapPath` getter refuses an unconfigured PTC deployment.
Pass the verified `ctx.ssh.ptcLaunch` as the `launch` configuration of [`NodePtcRuntime`](../../ptc-runtime/ptc-runtime-node/README.md). Executable deployments include their worker. Script deployments require both bootstrap fields for PTC; filesystem and Bash use may omit them. The getter refuses an unconfigured script bootstrap. The client rejects mismatched protocol versions, runtime kinds and helper digests before readiness.
-----
@@ -64,7 +65,7 @@ Failed startup and process results release their reservations after native quies
For terminals opting into shell activity observation, root exit retains the reservation and its remaining work. Activity RPC continues to reach the provider; explicit termination awaits quiescence before releasing endpoints and recording the completed result. Helper connection disposal and lease expiry retain their existing termination authority.
The helper starts with `--disable-sigusr1`, so a same-user process signal cannot open its Node debugger.
The connection disables the helper’s SIGUSR1 debugger activation: the script invocation uses `--disable-sigusr1`, and the executable invocation supplies that flag through `NODE_OPTIONS`.
</details>
+6 -5
View File
@@ -31,20 +31,21 @@ kind: "package-reference"
两端均需运行 Linux 或 macOS。本地 `ssh` 命令必须支持连接复用与 Unix 套接字转发,服务器也必须允许该转发。启动前配置主机别名、凭据与已知主机记录:本服务启用 `BatchMode`、要求严格检查主机密钥、禁用认证代理转发,且不提供交互认证流程。
在远端主机安装已构建的辅助程序及其匹配的运行依赖。Node、辅助程序、引导程序及其依赖必须位于工作区和可写临时目录之外,也必须位于后端会替换的临时目录树之外,例如 bwrap 的私有 `/tmp`;工作区仍可位于 `/tmp` 下。摘要校验在辅助程序启动后发现非预期的已安装产物;它不能保证可写部署文件的执行安全,也不能认证恶意 SSH 主机。
安装 [SSH helper 可执行文件发行包](../ssh-helper-runtime/README.zh.md),或已构建的 helper 脚本及其匹配的 Node 依赖。运行时必须位于工作区和可写临时目录之外,也必须位于后端会替换的临时目录树之外,例如 bwrap 的私有 `/tmp`;工作区仍可位于 `/tmp` 下。摘要校验在辅助程序启动后发现非预期的已安装产物;它不能保证可写部署文件的执行安全,也不能认证恶意 SSH 主机。
| 字段 | 默认值 | 含义 |
|---|---|---|
| `host` | 必填 | 已有的 OpenSSH 主机别名 |
| `node`、`helper`、`workspace` | 必填 | 远端 Node 可执行文件、辅助程序打包入口和默认工作区的绝对路径 |
| `launch` | 必填 | `{ kind: "executable" }`,或 `{ kind: "node-script", node: "/absolute/node" }` |
| `helper`、`workspace` | 必填 | 远端 helper 入口和默认工作区的绝对路径 |
| `helperHash` | 必填 | 已安装辅助程序入口的小写 SHA-256 |
| `bootstrapPath`、`bootstrapHash` | 省略 | 成对提供的远端 PTC 入口及其小写 SHA-256 |
| `launch.bootstrapPath`、`launch.bootstrapHash` | 省略 | 成对提供的远端 PTC 脚本及摘要;仅用于 `node-script` |
| `requestTimeoutMs` | `30000` | 连接与管理请求的截止时限,范围为 1 至 2,147,483,647 毫秒 |
| `maxFrameBytes` | `67108864` | 每条 JSON 消息的负载上限,最大为 64 MiB |
| `maxPending` | `128` | 普通未完成请求的数量上限;心跳与有界清理请求使用预留容量 |
| `leaseMs` | `30000` | 辅助进程心跳租期,范围为 3000 至 600000 毫秒 |
使用 PTC 时,配置两个引导字段,并将验证后的 `ctx.ssh.nodeExecutable` 与 `ctx.ssh.bootstrapPath` 传给 [`NodePtcRuntime`](../../ptc-runtime/ptc-runtime-node/README.zh.md)。仅使用文件系统和 Bash 时可以省略这对字段。未配置 PTC 部署时,`bootstrapPath` getter 会拒绝访问。
将验证后的 `ctx.ssh.ptcLaunch` 作为 [`NodePtcRuntime`](../../ptc-runtime/ptc-runtime-node/README.zh.md) 的 `launch` 配置。可执行文件部署包含 worker;脚本部署使用 PTC 时需要两个引导字段,仅使用文件系统和 Bash 时可以省略。脚本引导未配置时,getter 会拒绝访问。客户端在就绪前拒绝不匹配的协议版本、运行形式和 helper 摘要。
-----
@@ -64,7 +65,7 @@ OpenSSH 主连接承载私有管理 RPC。每条程序流使用独立转发的 U
终端启用 shell 活动观察时,根进程退出会保留 reservation 及其剩余工作。活动 RPC 继续访问 provider;明确终止时先等待进程范围完全停稳,再释放端点并记录完成结果。helper 连接卸载和租约到期仍保留原有的终止权限。
辅助程序以 `--disable-sigusr1` 启动,因此同用户进程发送的信号无法开启其 Node 调试器。
连接会禁用 helper 的 SIGUSR1 调试器激活:脚本调用使用 `--disable-sigusr1`,可执行文件调用通过 `NODE_OPTIONS` 提供该标志。
</details>
+4 -2
View File
@@ -55,7 +55,8 @@
"@deepseek-ai/dsh-sandbox-local": "workspace:^",
"@deepseek-ai/dsh-sandbox-policy": "workspace:^",
"@deepseek-ai/dsh-session-projection": "workspace:^",
"@deepseek-ai/dsh-brand": "workspace:^"
"@deepseek-ai/dsh-brand": "workspace:^",
"@deepseek-ai/dsh-util-values": "workspace:^"
},
"dependencies": {
"@deepseek-ai/schemastery": "workspace:^",
@@ -78,6 +79,7 @@
"@deepseek-ai/dsh-sandbox-ssh": "workspace:^",
"@deepseek-ai/dsh-lsp": "workspace:^",
"@deepseek-ai/dsh-lsp-stdio": "workspace:^",
"@deepseek-ai/dsh-brand": "workspace:^"
"@deepseek-ai/dsh-brand": "workspace:^",
"@deepseek-ai/dsh-util-values": "workspace:^"
}
}
+7 -1
View File
@@ -1,6 +1,7 @@
/** Private OpenSSH process entry; the helper module owns request and cleanup behavior. */
/* v8 ignore file -- launched through plain Node in SSH acceptance; helper behavior is exercised through its explicit streams. */
import { fileURLToPath } from 'node:url'
import { isSea } from 'node:sea'
import { runSshHelper } from './helper.ts'
const controller = new AbortController()
@@ -8,7 +9,12 @@ const stop = (): void => { controller.abort(new Error('SSH helper process termin
for (const signal of ['SIGTERM', 'SIGHUP', 'SIGINT'] as const) process.once(signal, stop)
try {
if (process.argv.length !== 2) throw new Error('SSH helper accepts no command arguments')
await runSshHelper({ input: process.stdin, output: process.stdout, entryPath: fileURLToPath(import.meta.url), signal: controller.signal })
const packaged = isSea()
await runSshHelper({
input: process.stdin, output: process.stdout,
entryPath: packaged ? process.execPath : fileURLToPath(import.meta.url),
kind: packaged ? 'executable' : 'node-script', signal: controller.signal,
})
} catch (error) {
process.stderr.write(`dsh-ssh-sandbox: ${error instanceof Error ? error.message : String(error)}\n`)
process.exitCode = 127
+4 -1
View File
@@ -42,6 +42,8 @@ export interface HelperTransport {
output: Writable
/** Installed entry whose digest is compared by the client. */
entryPath: string
/** Whether the installed entry includes its own Node and worker runtime. */
kind: 'node-script' | 'executable'
/** Process termination joins the same cleanup as transport loss. */
signal: AbortSignal
}
@@ -97,13 +99,14 @@ export async function runSshHelper(transport: HelperTransport): Promise<void> {
protocol: z.literal(SSH_PROTOCOL_VERSION), workspace: remotePath, leaseMs: z.number().int().min(3000).max(600_000),
bootstrapPath: remotePath.optional(),
}).strict().parse(raw)
if (transport.kind === 'executable' && input.bootstrapPath !== undefined) throw new Error('Executable SSH helper uses its embedded PTC bootstrap')
workspace = ctx.fs.processPath(await ctx.fs.resolve(input.workspace, { signal }))
leaseMs = input.leaseMs
initialized = true
touchLease()
return {
protocol: SSH_PROTOCOL_VERSION, hash: createHash('sha256').update(readFileSync(transport.entryPath)).digest('hex'),
platform: process.platform, nodeVersion: process.version, node: process.execPath, root, workspace,
kind: transport.kind, platform: process.platform, nodeVersion: process.version, executable: process.execPath, root, workspace,
...(input.bootstrapPath === undefined ? {} : { bootstrapHash: createHash('sha256').update(readFileSync(input.bootstrapPath)).digest('hex') }),
}
}
+62 -25
View File
@@ -7,28 +7,40 @@ import { createConnection, type Socket } from 'node:net'
import { Context, Service } from '@deepseek-ai/cordis'
import schema from '@deepseek-ai/schemastery'
import { z } from 'zod'
import { assertNever } from '@deepseek-ai/dsh-util-values'
import { SshRpcPeer, SSH_PROTOCOL_VERSION } from './protocol.ts'
import { helloSchema, type SshStreamEndpoint } from './schemas.ts'
import { authenticateStream } from './stream-security.ts'
type Hello = z.infer<typeof helloSchema>
/** Installed helper invocation; script deployments may also install a PTC bootstrap. */
export type HelperLaunch = {
/** Run the installed script with a separately installed Node executable. */
kind: 'node-script'
/** Absolute remote Node executable. */
node: string
/** Absolute remote PTC bootstrap; requires bootstrapHash. */
bootstrapPath?: string
/** Lowercase SHA-256 of bootstrapPath; requires that path. */
bootstrapHash?: string
} | {
/** Run the helper executable with its embedded Node and PTC worker. */
kind: 'executable'
}
/** Deployment-owned SSH identity and installed helper; no model argument selects these values. */
export interface Config {
/** OpenSSH host alias, including its existing user, key and known-host configuration. */
host: string
/** Absolute remote Node executable. */
node: string
/** Explicit script or self-contained executable invocation. */
launch: HelperLaunch
/** Absolute path to the installed, bundled helper entry. */
helper: string
/** SHA-256 of that bundled helper; mismatches refuse the connection. */
helperHash: string
/** Absolute remote default workspace. */
workspace: string
/** Optional preinstalled built PTC entry, paired with its expected digest. */
bootstrapPath?: string
/** SHA-256 of bootstrapPath; both fields must be supplied together. */
bootstrapHash?: string
/** Connection and administrative-request deadline, at most 2,147,483,647 milliseconds. */
requestTimeoutMs?: number
/** Maximum JSON payload bytes per helper request or response. */
@@ -46,9 +58,12 @@ declare module '@deepseek-ai/cordis' {
/** One non-reconnecting SSH session; loss invalidates all active operations. */
export class SshConnection extends Service {
static Config: schema<Config> = schema.object({
host: schema.string().required(), node: schema.string().required(), helper: schema.string().required(),
host: schema.string().required(), helper: schema.string().required(),
launch: schema.union([
schema.object({ kind: schema.const('node-script').required(), node: schema.string().required(), bootstrapPath: schema.string(), bootstrapHash: schema.string() }),
schema.object({ kind: schema.const('executable').required() }),
]).required(),
helperHash: schema.string().required(), workspace: schema.string().required(),
bootstrapPath: schema.string(), bootstrapHash: schema.string(),
requestTimeoutMs: schema.number().default(30_000), maxFrameBytes: schema.number().default(64 * 1024 * 1024),
maxPending: schema.number().default(128), leaseMs: schema.number().default(30_000),
})
@@ -67,7 +82,7 @@ export class SshConnection extends Service {
private failure: Error | undefined
private sockets = new Set<Socket>()
private nextSocket = 0
private readonly config: Required<Omit<Config, 'bootstrapPath' | 'bootstrapHash'>> & Pick<Config, 'bootstrapPath' | 'bootstrapHash'>
private readonly config: Required<Config>
private remote: Hello | undefined
constructor(ctx: Context, config: Config) {
@@ -75,13 +90,17 @@ export class SshConnection extends Service {
if (process.platform !== 'linux' && process.platform !== 'darwin') throw new Error('SSH runtime requires a POSIX client')
this.config = z.object({
host: z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_.@-]*$/),
node: z.string().startsWith('/'), helper: z.string().startsWith('/'), helperHash: z.string().regex(/^[0-9a-f]{64}$/),
launch: z.discriminatedUnion('kind', [
z.object({ kind: z.literal('node-script'), node: z.string().startsWith('/'),
bootstrapPath: z.string().startsWith('/').optional(), bootstrapHash: z.string().regex(/^[0-9a-f]{64}$/).optional(),
}).strict().refine(value => (value.bootstrapPath === undefined) === (value.bootstrapHash === undefined), 'bootstrapPath and bootstrapHash must be paired'),
z.object({ kind: z.literal('executable') }).strict(),
]),
helper: z.string().startsWith('/'), helperHash: z.string().regex(/^[0-9a-f]{64}$/),
workspace: z.string().startsWith('/'), requestTimeoutMs: z.number().int().positive().max(2_147_483_647),
bootstrapPath: z.string().startsWith('/').optional(), bootstrapHash: z.string().regex(/^[0-9a-f]{64}$/).optional(),
maxFrameBytes: z.number().int().positive().max(64 * 1024 * 1024), maxPending: z.number().int().positive().max(128),
leaseMs: z.number().int().min(3000).max(600_000),
}).refine(value => (value.bootstrapPath === undefined) === (value.bootstrapHash === undefined), 'bootstrapPath and bootstrapHash must be paired')
.parse(config) as typeof this.config
}).strict().parse(config) as Required<Config>
this.ready = this.start()
// Startup uses Node I/O, local validation, and Error-valued RPC failures.
void this.ready.catch((error: unknown) => { this.fail(error as Error) })
@@ -91,16 +110,18 @@ export class SshConnection extends Service {
/** Hold plugin readiness until the remote identity and helper digest are verified. */
async [Service.init](): Promise<void> { await this.ready }
/** Verified remote Node executable for the paired PTC runtime. */
get nodeExecutable(): string {
/** Verified remote PTC launch configuration; script deployments require a verified bootstrap. */
get ptcLaunch(): { kind: 'embedded'; executable: string } | { kind: 'node-script'; executable: string; bootstrapPath: string } {
if (this.remote === undefined) throw new Error('SSH helper is not ready')
return this.remote.node
}
/** Verified preinstalled PTC entry; unconfigured runtimes fail before program execution. */
get bootstrapPath(): string {
if (this.remote === undefined || this.config.bootstrapPath === undefined) throw new Error('SSH PTC requires a verified bootstrapPath and bootstrapHash')
return this.config.bootstrapPath
const launch = this.config.launch
switch (launch.kind) {
case 'executable': return { kind: 'embedded', executable: this.remote.executable }
case 'node-script':
if (launch.bootstrapPath === undefined) throw new Error('SSH PTC requires a verified bootstrapPath and bootstrapHash')
return { kind: 'node-script', executable: this.remote.executable, bootstrapPath: launch.bootstrapPath }
/* v8 ignore next -- Config parsing admits only these two launch kinds. */
default: return assertNever(launch)
}
}
/**
@@ -259,7 +280,22 @@ export class SshConnection extends Service {
this.directory = await mkdtemp('/tmp/dsh-ssh-')
if (this.closed) throw new Error('SSH connection closed before startup')
const quote = (value: string): string => `'${value.replaceAll("'", "'\\''")}'`
const command = [this.config.node, '--disable-sigusr1', this.config.helper].map(quote).join(' ')
const launch = this.config.launch
let command: string
let bootstrapPath: string | undefined
let bootstrapHash: string | undefined
switch (launch.kind) {
case 'node-script':
command = [launch.node, '--disable-sigusr1', this.config.helper].map(quote).join(' ')
bootstrapPath = launch.bootstrapPath
bootstrapHash = launch.bootstrapHash
break
case 'executable':
command = `NODE_OPTIONS='--disable-sigusr1' ${quote(this.config.helper)}`
break
/* v8 ignore next -- Config parsing admits only these two launch kinds. */
default: return assertNever(launch)
}
const child = spawn('ssh', [
'-T', '-M', '-S', this.controlPath(), '-o', 'ControlPersist=no', '-o', 'BatchMode=yes',
'-o', 'StrictHostKeyChecking=yes', '-o', 'ForwardAgent=no', '-o', 'ClearAllForwardings=yes',
@@ -275,10 +311,11 @@ export class SshConnection extends Service {
rpc.once('closed', (error) => { this.fail(error as Error) })
const hello = await rpc.request('hello', {
protocol: SSH_PROTOCOL_VERSION, workspace: this.config.workspace, leaseMs: this.config.leaseMs,
...(this.config.bootstrapPath === undefined ? {} : { bootstrapPath: this.config.bootstrapPath }),
...(bootstrapPath === undefined ? {} : { bootstrapPath }),
}, helloSchema, AbortSignal.timeout(this.config.requestTimeoutMs))
if (hello.hash !== this.config.helperHash) throw new Error('SSH helper digest differs from the configured artifact')
if (hello.bootstrapHash !== this.config.bootstrapHash) throw new Error('SSH PTC bootstrap digest differs from the configured artifact')
if (hello.kind !== launch.kind) throw new Error('SSH helper runtime differs from the configured launch kind')
if (hello.bootstrapHash !== bootstrapHash) throw new Error('SSH PTC bootstrap digest differs from the configured artifact')
this.remote = hello
let heartbeatPending: Promise<unknown> | undefined
this.heartbeat = setInterval(() => {
+1 -1
View File
@@ -7,7 +7,7 @@ import { z } from 'zod'
import type { Branded } from '@deepseek-ai/dsh-brand'
/** Wire version shared by the installed helper and client package. */
export const SSH_PROTOCOL_VERSION = 1
export const SSH_PROTOCOL_VERSION = 2
/** Maximum prepared or running process handles owned by one helper. */
export const SSH_MAX_PROCESS_HANDLES = 128
+7 -1
View File
@@ -1,6 +1,7 @@
/** Strict JSON validation for SSH helper requests and remote observations. */
import { z } from 'zod'
import type { Branded } from '@deepseek-ai/dsh-brand'
import { SSH_PROTOCOL_VERSION } from './protocol.ts'
/** Identity of one prepared or running process in its owning SSH helper. */
export type SshProcessId = Branded<'SshProcessId'>
@@ -54,7 +55,12 @@ export const spawnSchema = z.object({
}).strict().optional(),
}).strict().refine(value => (value.stdio === undefined) !== (value.terminal === undefined), 'select ordinary or terminal execution')
/** Connection handshake binds sockets and workspace to one helper process. */
export const helloSchema = z.object({ protocol: z.literal(1), hash: z.string().regex(/^[0-9a-f]{64}$/), platform: z.enum(['linux', 'darwin']), nodeVersion: z.string(), node: remotePath, root: remotePath, workspace: remotePath, bootstrapHash: z.string().regex(/^[0-9a-f]{64}$/).optional() }).strict()
export const helloSchema = z.object({
protocol: z.literal(SSH_PROTOCOL_VERSION), hash: z.string().regex(/^[0-9a-f]{64}$/),
kind: z.enum(['node-script', 'executable']), platform: z.enum(['linux', 'darwin']),
nodeVersion: z.string(), executable: remotePath, root: remotePath, workspace: remotePath,
bootstrapHash: z.string().regex(/^[0-9a-f]{64}$/).optional(),
}).strict()
/** A prepared process publishes its sockets before target code may execute. */
export const streamEndpointSchema = z.object({ path: remotePath, capability: z.string().regex(/^[0-9a-f]{64}$/) }).strict()
/** A stream capability reaches only the authenticated SSH client and its helper. */
@@ -68,7 +68,7 @@ async function setup(phase: 'connect' | 'authenticate', pauseControl?: 'forward'
if (method === 'heartbeat') { heartbeatCalls++; return holdHeartbeat ? heartbeat.promise : null }
if (method === 'ordinary') { ordinaryEntered.resolve(undefined); return ordinary.promise }
if (method !== 'hello') throw new Error(`unexpected helper operation: ${method}`)
return { protocol: 1, hash: 'a'.repeat(64), platform: 'linux', nodeVersion: 'v24.19.0', node: '/usr/bin/node', root: '/tmp/remote-helper', workspace: '/workspace' }
return { protocol: 2, hash: 'a'.repeat(64), platform: 'linux', nodeVersion: 'v24.19.0', kind: 'node-script', executable: '/usr/bin/node', root: '/tmp/remote-helper', workspace: '/workspace' }
})
const rawSocket = new PendingSocket()
const secureSocket = new PendingSocket()
@@ -110,7 +110,7 @@ async function setup(phase: 'connect' | 'authenticate', pauseControl?: 'forward'
const socket = phase === 'connect' ? rawSocket : secureSocket
const ctx = new Context()
const fiber = ctx.plugin(SshConnection, {
host: 'hermetic-test', node: '/usr/bin/node', helper: '/opt/dsh/helper.js', helperHash: 'a'.repeat(64), workspace: '/workspace',
host: 'hermetic-test', launch: { kind: 'node-script', node: '/usr/bin/node' }, helper: '/opt/dsh/helper.js', helperHash: 'a'.repeat(64), workspace: '/workspace',
requestTimeoutMs: 10_000, maxFrameBytes: 4096, maxPending: holdHeartbeat ? 1 : 8, leaseMs: 30_000,
})
onTestFinished(async () => {
+4 -4
View File
@@ -10,16 +10,16 @@ import { SshRpcPeer } from '../../src/protocol.ts'
import { helloSchema, type SshStreamEndpoint } from '../../src/schemas.ts'
import { authenticateStream } from '../../src/stream-security.ts'
export async function createHelperHarness(handshake = true, leaseMs = 30_000) {
export async function createHelperHarness(handshake = true, leaseMs = 30_000, kind: 'node-script' | 'executable' = 'node-script') {
const root = await realpath(await mkdtemp('/tmp/dsh-ssh-rpc-'))
const input = new PassThrough()
const output = new PassThrough()
const controller = new AbortController()
const serving = runSshHelper({ input, output, entryPath: fileURLToPath(new URL('../../src/helper-entry.ts', import.meta.url)), signal: controller.signal })
const serving = runSshHelper({ input, output, kind, entryPath: fileURLToPath(new URL('../../src/helper-entry.ts', import.meta.url)), signal: controller.signal })
void serving.catch(() => {})
const client = new SshRpcPeer(output, input, 64 * 1024 * 1024, 128)
const sockets = new Set<Socket>()
const hello = () => client.request('hello', { protocol: 1, workspace: root, leaseMs }, helloSchema)
const hello = () => client.request('hello', { protocol: 2, workspace: root, leaseMs }, helloSchema)
let closing: Promise<void> | undefined
const close = () => {
closing ??= (async () => {
@@ -44,7 +44,7 @@ export async function createHelperHarness(handshake = true, leaseMs = 30_000) {
let facts: z.infer<typeof helloSchema> | undefined
try { if (handshake) facts = await hello() } catch (error) { await close(); throw error }
const connection = {
ready: Promise.resolve(facts), nodeExecutable: process.execPath,
ready: Promise.resolve(facts),
request: <T>(method: string, params: unknown, schema: z.ZodType<T>, signal?: AbortSignal) =>
client.request(method, params, schema, signal),
connectStream, dispose: close,
@@ -22,11 +22,11 @@ describe.skipIf(process.platform === 'win32')('SSH helper wire and lifecycle bou
const controller = new AbortController()
const platform = vi.spyOn(process, 'platform', 'get').mockReturnValue('win32')
try {
await expect(runSshHelper({ input, output, entryPath, signal: controller.signal })).rejects.toThrow('POSIX host')
await expect(runSshHelper({ input, output, kind: 'node-script', entryPath, signal: controller.signal })).rejects.toThrow('POSIX host')
} finally { platform.mockRestore() }
controller.abort(new Error('startup cancelled'))
try {
await expect(runSshHelper({ input, output, entryPath, signal: controller.signal })).rejects.toThrow('startup cancelled')
await expect(runSshHelper({ input, output, kind: 'node-script', entryPath, signal: controller.signal })).rejects.toThrow('startup cancelled')
} finally { input.destroy(); output.destroy() }
})
@@ -34,7 +34,7 @@ describe.skipIf(process.platform === 'win32')('SSH helper wire and lifecycle bou
const input = new PassThrough()
const output = new PassThrough()
const controller = new AbortController()
const serving = runSshHelper({ input, output, entryPath, signal: controller.signal })
const serving = runSshHelper({ input, output, kind: 'node-script', entryPath, signal: controller.signal })
controller.abort()
try {
await serving
@@ -47,17 +47,26 @@ describe.skipIf(process.platform === 'win32')('SSH helper wire and lifecycle bou
const test = await createHelperHarness(false)
try {
for (const params of [
{ protocol: 2, workspace: test.root, leaseMs: 3000 },
{ protocol: 1, workspace: 'relative', leaseMs: 3000 },
{ protocol: 1, workspace: test.root, leaseMs: 2999 },
{ protocol: 1, workspace: test.root, leaseMs: 3000 },
{ protocol: 2, workspace: 'relative', leaseMs: 3000 },
{ protocol: 2, workspace: test.root, leaseMs: 2999 },
]) await expect(test.client.request('hello', params, helloSchema)).rejects.toThrow()
const bootstrapPath = `${test.root}/bootstrap.js`
await writeFile(bootstrapPath, 'export const identity = "test"\n')
const facts = await test.client.request('hello', { protocol: 1, workspace: test.root, leaseMs: 3000, bootstrapPath }, helloSchema)
const facts = await test.client.request('hello', { protocol: 2, workspace: test.root, leaseMs: 3000, bootstrapPath }, helloSchema)
expect(facts.bootstrapHash).toBe(createHash('sha256').update(await readFile(bootstrapPath)).digest('hex'))
} finally { await test.close() }
})
it('refuses an external bootstrap for an executable helper', async () => {
const test = await createHelperHarness(false, 30_000, 'executable')
try {
await expect(test.client.request('hello', { protocol: 2, workspace: test.root, leaseMs: 3000, bootstrapPath: '/external/process.js' }, helloSchema))
.rejects.toThrow('embedded PTC bootstrap')
expect((await test.hello()).kind).toBe('executable')
} finally { await test.close() }
})
it('expires the client lease and joins open text streams and reserved sockets', async () => {
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
let test: Awaited<ReturnType<typeof createHelperHarness>> | undefined
+11 -10
View File
@@ -27,6 +27,7 @@ const enabled = configPath !== undefined && process.platform !== 'win32'
async function setup() {
const config = JSON.parse(readFileSync(configPath as string, 'utf8')) as Config
if (config.launch.kind !== 'node-script') throw new Error('live.e2e requires a Node script deployment; executable acceptance is owned by ssh-helper-runtime')
const ctx = new Context()
const projection = ctx.plugin(SessionProjectionRegistry)
const policy = ctx.plugin(SandboxPolicyService, { mode: 'workspace-write', workspaceRoot: config.workspace })
@@ -55,7 +56,7 @@ async function processResult(handle: SubprocessHandle) {
async function removeOwned(test: Awaited<ReturnType<typeof setup>>) {
try {
const handle = test.ctx.subprocess.spawn({
argv: [test.hello.node, '-e', 'require("node:fs").rmSync(process.argv[1],{recursive:true,force:true})', test.root],
argv: [test.hello.executable, '-e', 'require("node:fs").rmSync(process.argv[1],{recursive:true,force:true})', test.root],
cwd: test.hello.workspace, stdio: { stdin: 'ignore', stdout: { maxBytes: 1024 }, stderr: { maxBytes: 1024 } }, graceMs: 500,
})
await processResult(handle)
@@ -67,13 +68,13 @@ describe.skipIf(!enabled)('POSIX SSH runtime acceptance', () => {
const test = await setup()
try {
const prepared = await test.ctx.ssh.request('process.prepare', {
argv: [test.hello.node, '-e', 'process.stdout.write("legitimate-target")'], cwd: test.root, graceMs: 500,
argv: [test.hello.executable, '-e', 'process.stdout.write("legitimate-target")'], cwd: test.root, graceMs: 500,
stdio: { stdin: 'ignore', stdout: 'pipe', stderr: 'pipe', control: 'pipe' },
}, preparedSchema)
const endpoint = prepared.streams.control!
const attack = "let connected=false,secure=false;const s=require('node:tls').connect({path:process.argv[1],ciphers:'PSK-AES256-GCM-SHA384',minVersion:'TLSv1.2',maxVersion:'TLSv1.2',pskCallback:()=>({identity:'dsh-stream',psk:Buffer.alloc(32)}),checkServerIdentity:()=>undefined});s.on('connect',()=>{connected=true});s.on('secureConnect',()=>{secure=true});s.on('error',()=>{});s.on('close',()=>process.stdout.write(JSON.stringify({connected,secure})));"
const attacker = test.ctx.subprocess.spawn({
argv: (await test.ctx.sandbox.confine([test.hello.node, '-e', attack, endpoint.path], {
argv: (await test.ctx.sandbox.confine([test.hello.executable, '-e', attack, endpoint.path], {
mode: 'read-only', workspaceRoot: test.root,
})).argv,
cwd: test.root, stdio: { stdin: 'ignore', stdout: { maxBytes: 4096 }, stderr: { maxBytes: 4096 } }, graceMs: 500,
@@ -124,7 +125,7 @@ describe.skipIf(!enabled)('POSIX SSH runtime acceptance', () => {
.rejects.toMatchObject({ code: 'FS_SANDBOX_DENIED' })
const createLink = 'const fs=require(\'node:fs\');const root=process.argv[1];fs.mkdirSync(root+\'/physical/child\',{recursive:true});fs.mkdirSync(root+\'/lexical\');fs.symlinkSync(root+\'/physical/child\',root+\'/lexical/link\');'
await processResult(ctx.subprocess.spawn({
argv: [hello.node, '-e', createLink, root], cwd: root,
argv: [hello.executable, '-e', createLink, root], cwd: root,
stdio: { stdin: 'ignore', stdout: { maxBytes: 1024 }, stderr: { maxBytes: 1024 } }, graceMs: 500,
}))
const resolved = await ctx.fs.resolve('file.txt', { cwd: `${root}/lexical/link/..` })
@@ -139,7 +140,7 @@ describe.skipIf(!enabled)('POSIX SSH runtime acceptance', () => {
const test = await setup()
try {
const code = 'const fs=require(\'node:fs\');const s=new(require(\'node:net\').Socket)({fd:7,readable:true,writable:true,allowHalfOpen:true});let a=[];s.on(\'data\',b=>a.push(b));s.on(\'end\',()=>{let denied;try{fs.writeFileSync(\'forbidden\',\'bad\')}catch(e){denied=e.code}process.stdout.write(JSON.stringify({pid:process.pid,denied,cgroup:process.platform===\'linux\'?fs.readFileSync(\'/proc/self/cgroup\',\'utf8\'):\'\'}));process.stderr.write(\'stderr\');s.end(Buffer.concat(a))});'
const argv = (await test.ctx.sandbox.confine([test.hello.node, '-e', code], { mode: 'read-only', workspaceRoot: test.root })).argv
const argv = (await test.ctx.sandbox.confine([test.hello.executable, '-e', code], { mode: 'read-only', workspaceRoot: test.root })).argv
const handle = test.ctx.subprocess.spawn({
argv, cwd: test.root,
stdio: { stdin: 'ignore', stdout: { maxBytes: 4096 }, stderr: { maxBytes: 4096 }, control: 'pipe' }, graceMs: 1000,
@@ -167,7 +168,7 @@ describe.skipIf(!enabled)('POSIX SSH runtime acceptance', () => {
try {
const code = 'const s=new(require(\'node:net\').Socket)({fd:7,readable:true,writable:true});const b=Buffer.alloc(65536,120);let n=0;function pump(){while(n<10000){n++;if(!process.stdout.write(b))return}}process.stdout.on(\'drain\',pump);s.on(\'data\',v=>s.write(v));pump();setInterval(()=>{},1000);'
handle = test.ctx.subprocess.spawn({
argv: (await test.ctx.sandbox.confine([test.hello.node, '-e', code], { mode: 'workspace-write', workspaceRoot: test.root })).argv,
argv: (await test.ctx.sandbox.confine([test.hello.executable, '-e', code], { mode: 'workspace-write', workspaceRoot: test.root })).argv,
cwd: test.root, stdio: { stdin: 'ignore', stdout: 'pipe', stderr: { maxBytes: 1024 }, control: 'pipe' }, graceMs: 500,
})
const reply = once(handle.control!, 'data')
@@ -215,7 +216,7 @@ describe.skipIf(!enabled)('POSIX SSH runtime acceptance', () => {
try {
const code = 'const fs=require(\'node:fs\');require(\'node:child_process\').spawn(process.execPath,[\'-e\',\'setInterval(()=>{},1000)\'],{detached:true,stdio:\'ignore\'}).unref();fs.appendFileSync(\'launches\',\'one\\n\');process.stdout.write(fs.readFileSync(\'/proc/self/cgroup\',\'utf8\'));setInterval(()=>{},1000);'
const handle = victim.ctx.subprocess.spawn({
argv: [victim.hello.node, '-e', code], cwd: victim.root,
argv: [victim.hello.executable, '-e', code], cwd: victim.root,
stdio: { stdin: 'ignore', stdout: 'pipe', stderr: { maxBytes: 4096 } }, graceMs: 500,
})
const [bytes] = await once(handle.stdout!, 'data') as [Buffer]
@@ -238,7 +239,7 @@ describe.skipIf(!enabled)('POSIX SSH runtime acceptance', () => {
await victimConnection.dispose()
try {
await processResult(observer.ctx.subprocess.spawn({
argv: [observer.hello.node, '-e', 'require("node:fs").rmSync(process.argv[1],{recursive:true,force:true})', victim.root],
argv: [observer.hello.executable, '-e', 'require("node:fs").rmSync(process.argv[1],{recursive:true,force:true})', victim.root],
cwd: observer.root, stdio: { stdin: 'ignore', stdout: { maxBytes: 1024 }, stderr: { maxBytes: 1024 } }, graceMs: 500,
}))
} finally { await removeOwned(observer) }
@@ -248,7 +249,7 @@ describe.skipIf(!enabled)('POSIX SSH runtime acceptance', () => {
it.skipIf(bootstrap === undefined)('runs PTC remotely with empty environment, bindings, denial and a bounded hot loop', async () => {
const test = await setup()
const fiber = test.ctx.plugin(NodePtcRuntime, {
nodeExecutable: test.ctx.ssh.nodeExecutable, bootstrapPath: test.ctx.ssh.bootstrapPath,
launch: test.ctx.ssh.ptcLaunch,
})
try {
await fiber
@@ -281,7 +282,7 @@ describe.skipIf(!enabled)('POSIX SSH runtime acceptance', () => {
await test.ctx.fs.writeText(await test.ctx.fs.resolve(`${test.root}/tsconfig.json`), '{"compilerOptions":{"strict":true}}')
await test.ctx.fs.writeText(await test.ctx.fs.resolve(`${test.root}/source.ts`), 'export const answer: number = 42\nexport const doubled = answer * 2\n')
stdio = test.ctx.plugin(LspStdio, { servers: { remoteTypescript: {
command: test.hello.node, args: [languageServer as string, '--stdio'], extensionToLanguage: { '.ts': 'typescript' },
command: test.hello.executable, args: [languageServer as string, '--stdio'], extensionToLanguage: { '.ts': 'typescript' },
} } })
await stdio
const request = { filePath: 'source.ts', workspaceRoot: test.root, position: { line: 1, character: 24 } }
+26 -16
View File
@@ -52,12 +52,12 @@ class Child extends EventEmitter {
}
const config: Config = {
host: 'test-alias', node: '/remote/node', helper: '/remote/helper.js', helperHash: 'a'.repeat(64), workspace: '/remote/workspace',
host: 'test-alias', launch: { kind: 'node-script', node: '/remote/node' }, helper: '/remote/helper.js', helperHash: 'a'.repeat(64), workspace: '/remote/workspace',
requestTimeoutMs: 1000, maxFrameBytes: 4096, maxPending: 8, leaseMs: 30_000,
}
const hello = {
protocol: 1, hash: 'a'.repeat(64), platform: 'linux', nodeVersion: 'v24.19.0',
node: '/canonical/node', root: '/tmp/remote-helper', workspace: '/canonical/workspace',
protocol: 2, hash: 'a'.repeat(64), platform: 'linux', nodeVersion: 'v24.19.0',
kind: 'node-script', executable: '/canonical/node', root: '/tmp/remote-helper', workspace: '/canonical/workspace',
}
function setup(options: {
@@ -133,11 +133,11 @@ function setup(options: {
describe.skipIf(process.platform === 'win32')('SSH connection startup', () => {
it.each([
{ host: '-option' }, { host: 'alias; command' }, { node: 'relative' }, { helperHash: 'bad' },
{ bootstrapPath: '/remote/bootstrap.js' }, { bootstrapHash: 'b'.repeat(64) },
{ host: '-option' }, { host: 'alias; command' }, { launch: { kind: 'node-script', node: 'relative' } }, { helperHash: 'bad' },
{ launch: { kind: 'node-script', node: '/remote/node', bootstrapPath: '/remote/bootstrap.js' } }, { launch: { kind: 'node-script', node: '/remote/node', bootstrapHash: 'b'.repeat(64) } },
{ requestTimeoutMs: 0 }, { requestTimeoutMs: 2_147_483_648 },
{ maxFrameBytes: 64 * 1024 * 1024 + 1 }, { maxPending: 129 }, { leaseMs: 2999 },
])('rejects invalid deployment configuration before SSH starts: %j', (invalid) => {
] satisfies Partial<Config>[])('rejects invalid deployment configuration before SSH starts: %j', (invalid) => {
expect(() => setup({ config: invalid })).toThrow()
expect(transport.spawn).not.toHaveBeenCalled()
})
@@ -150,37 +150,47 @@ describe.skipIf(process.platform === 'win32')('SSH connection startup', () => {
})
it('publishes only verified remote coordinates and quotes the configured executable paths', async () => {
const test = setup({ holdHello: true, config: { helper: "/remote/helper's file.js", bootstrapPath: '/remote/process.js', bootstrapHash: 'b'.repeat(64) }, hello: { bootstrapHash: 'b'.repeat(64) } })
expect(() => test.service.nodeExecutable).toThrow('not ready')
expect(() => test.service.bootstrapPath).toThrow('verified bootstrapPath')
const test = setup({ holdHello: true, config: { helper: "/remote/helper's file.js", launch: { kind: 'node-script', node: '/remote/node', bootstrapPath: '/remote/process.js', bootstrapHash: 'b'.repeat(64) } }, hello: { bootstrapHash: 'b'.repeat(64) } })
expect(() => test.service.ptcLaunch).toThrow('not ready')
await test.helloEntered
test.releaseHello()
await test.service[Service.init]()
expect(test.service.nodeExecutable).toBe('/canonical/node')
expect(test.service.bootstrapPath).toBe('/remote/process.js')
expect(test.service.ptcLaunch).toEqual({ kind: 'node-script', executable: '/canonical/node', bootstrapPath: '/remote/process.js' })
const argv = transport.spawn.mock.calls[0]?.[1] as string[]
expect(argv).toContain('StrictHostKeyChecking=yes')
expect(argv).toContain('ForwardAgent=no')
expect(argv.at(-1)).toBe("'/remote/node' '--disable-sigusr1' '/remote/helper'\\''s file.js'")
expect(test.calls[0]?.params).toEqual({ protocol: 1, workspace: '/remote/workspace', leaseMs: 30_000, bootstrapPath: '/remote/process.js' })
expect(test.calls[0]?.params).toEqual({ protocol: 2, workspace: '/remote/workspace', leaseMs: 30_000, bootstrapPath: '/remote/process.js' })
})
it('permits filesystem-only deployments and refuses an unconfigured PTC bootstrap getter', async () => {
const test = setup()
await test.service.ready
expect(test.service.nodeExecutable).toBe('/canonical/node')
expect(() => test.service.bootstrapPath).toThrow('verified bootstrapPath')
expect(() => test.service.ptcLaunch).toThrow('verified bootstrapPath')
expect(await test.service.request('echo', { value: 42 }, z.object({ value: z.number() }))).toEqual({ value: 42 })
})
it('launches an executable directly and publishes its embedded PTC worker', async () => {
const test = setup({
config: { launch: { kind: 'executable' }, helper: '/remote/helper with spaces' },
hello: { kind: 'executable', executable: '/canonical/dsh-ssh-helper' },
})
await test.service.ready
expect(test.service.ptcLaunch).toEqual({ kind: 'embedded', executable: '/canonical/dsh-ssh-helper' })
const argv = transport.spawn.mock.calls[0]?.[1] as string[]
expect(argv.at(-1)).toBe("NODE_OPTIONS='--disable-sigusr1' '/remote/helper with spaces'")
expect(test.calls[0]?.params).toEqual({ protocol: 2, workspace: '/remote/workspace', leaseMs: 30_000 })
})
it.each([
{ hello: { hash: 'c'.repeat(64) }, error: 'helper digest' },
{ hello: { bootstrapHash: 'c'.repeat(64) }, error: 'bootstrap digest' },
{ hello: { protocol: 2 }, error: 'Invalid' },
{ hello: { protocol: 1 }, error: 'Invalid' },
{ hello: { kind: 'executable' }, error: 'launch kind' },
])('refuses a mismatched helper identity before readiness: $error', async ({ hello, error }) => {
const test = setup({ hello })
await expect(test.service.ready).rejects.toThrow(error)
expect(() => test.service.nodeExecutable).toThrow('not ready')
expect(() => test.service.ptcLaunch).toThrow('not ready')
await expect(test.service.request('echo', {}, z.unknown())).rejects.toThrow()
})
+3
View File
@@ -43,6 +43,9 @@
},
{
"path": "../../util/brand"
},
{
"path": "../../util/values"
}
]
}
+115
View File
@@ -181,6 +181,9 @@ importers:
vitest:
specifier: ^4.1.8
version: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@22.20.0)(@vitest/coverage-v8@4.1.8)(happy-dom@20.11.6)(jsdom@29.1.1(@noble/hashes@2.3.0))(vite@8.0.16(@types/node@22.20.0)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0))
zod:
specifier: ^4.4.3
version: 4.4.3
apps/cli:
dependencies:
@@ -11535,6 +11538,118 @@ importers:
'@deepseek-ai/dsh-subprocess-ssh':
specifier: workspace:^
version: link:../subprocess-ssh
'@deepseek-ai/dsh-util-values':
specifier: workspace:^
version: link:../../util/values
packages/ssh/ssh-helper-runtime:
dependencies:
'@deepseek-ai/cordis':
specifier: workspace:^
version: link:../../../vendor/cordis
'@deepseek-ai/cosmokit':
specifier: link:../../../vendor/cosmokit
version: link:../../../vendor/cosmokit
'@deepseek-ai/dsh-agent':
specifier: workspace:^
version: link:../../core/agent
'@deepseek-ai/dsh-brand':
specifier: workspace:^
version: link:../../util/brand
'@deepseek-ai/dsh-fs':
specifier: workspace:^
version: link:../../fs/fs
'@deepseek-ai/dsh-fs-local':
specifier: workspace:^
version: link:../../fs/fs-local
'@deepseek-ai/dsh-fs-sandbox':
specifier: workspace:^
version: link:../../fs/fs-sandbox
'@deepseek-ai/dsh-http-proxy':
specifier: workspace:^
version: link:../../util/http-proxy
'@deepseek-ai/dsh-invariants':
specifier: workspace:^
version: link:../../runtime-diagnostics/invariants
'@deepseek-ai/dsh-lazy-require':
specifier: workspace:^
version: link:../../util/lazy-require
'@deepseek-ai/dsh-llm':
specifier: workspace:^
version: link:../../llm/llm
'@deepseek-ai/dsh-ptc-runtime':
specifier: workspace:^
version: link:../../ptc-runtime/ptc-runtime
'@deepseek-ai/dsh-ptc-runtime-node':
specifier: workspace:^
version: link:../../ptc-runtime/ptc-runtime-node
'@deepseek-ai/dsh-sandbox':
specifier: workspace:^
version: link:../../sandbox/sandbox
'@deepseek-ai/dsh-sandbox-local':
specifier: workspace:^
version: link:../../sandbox/sandbox-local
'@deepseek-ai/dsh-sandbox-policy':
specifier: workspace:^
version: link:../../sandbox/sandbox-policy
'@deepseek-ai/dsh-sandbox-windows-acl':
specifier: workspace:^
version: link:../../sandbox/sandbox-windows-acl
'@deepseek-ai/dsh-scope':
specifier: workspace:^
version: link:../../core/scope
'@deepseek-ai/dsh-session':
specifier: workspace:^
version: link:../../core/session
'@deepseek-ai/dsh-session-projection':
specifier: workspace:^
version: link:../../session/session-projection
'@deepseek-ai/dsh-ssh':
specifier: workspace:^
version: link:../ssh
'@deepseek-ai/dsh-subprocess':
specifier: workspace:^
version: link:../../subprocess/subprocess
'@deepseek-ai/dsh-subprocess-local':
specifier: workspace:^
version: link:../../subprocess/subprocess-local
'@deepseek-ai/dsh-system-prompt':
specifier: workspace:^
version: link:../../core/system-prompt
'@deepseek-ai/dsh-timeout':
specifier: workspace:^
version: link:../../util/timeout
'@deepseek-ai/dsh-typert-protocol':
specifier: workspace:^
version: link:../../typert/protocol
'@deepseek-ai/dsh-util-crypto':
specifier: workspace:^
version: link:../../util/crypto
'@deepseek-ai/dsh-util-values':
specifier: workspace:^
version: link:../../util/values
'@deepseek-ai/dsh-win32-process':
specifier: workspace:^
version: link:../../subprocess/win32-process
'@deepseek-ai/node-addon-system':
specifier: workspace:^
version: link:../../../native/system/packages/entry
'@deepseek-ai/schemastery':
specifier: link:../../../vendor/schemastery
version: link:../../../vendor/schemastery
devDependencies:
'@deepseek-ai/dsh-app-boot':
specifier: workspace:^
version: link:../../boot/app-boot
'@deepseek-ai/dsh-fs-ssh':
specifier: workspace:^
version: link:../fs-ssh
'@deepseek-ai/dsh-sandbox-ssh':
specifier: workspace:^
version: link:../sandbox-ssh
'@deepseek-ai/dsh-subprocess-ssh':
specifier: workspace:^
version: link:../subprocess-ssh
packages/ssh/subprocess-ssh:
dependencies:
@@ -2,7 +2,7 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { resolveLinuxNodePtyAddon, resolveWindowsNodePtyAddons } from './build-exe-for-python-sdk-native-pty.ts'
import { resolveLinuxNodePtyAddon, resolveWindowsNodePtyAddons } from './executable-native-pty.ts'
const roots: string[] = []
+12 -95
View File
@@ -7,12 +7,13 @@
*/
import { spawn } from 'node:child_process'
import { materializeStagedLinks, pnpmInvocation, restoreLegacyHoists } from './executable-packaging.ts'
import { existsSync, statSync } from 'node:fs'
import { chmod, copyFile, cp, lstat, mkdir, readFile, readdir, realpath, rm, writeFile } from 'node:fs/promises'
import { chmod, copyFile, cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { basename, dirname, extname, join, resolve, sep } from 'node:path'
import { basename, dirname, join, resolve, sep } from 'node:path'
import { parseArgs } from 'node:util'
import { resolveLinuxNodePtyAddon, resolveWindowsNodePtyAddons } from './build-exe-for-python-sdk-native-pty.ts'
import { resolveLinuxNodePtyAddon, resolveWindowsNodePtyAddons } from './executable-native-pty.ts'
import { copyOfficeSidecar, OFFICE_ASSET_IGNORES } from './build-exe-for-python-sdk-office.ts'
import { preparePrimaryRuntime, smokePrimaryRuntime, type PrimaryRuntimeTarget } from './primary-runtime/prepare.ts'
@@ -222,29 +223,6 @@ class BuildCli {
}
}
function pnpmInvocation(args: string[]): [command: string, args: string[]] {
const entrypoint = process.env.npm_execpath?.trim()
if (entrypoint !== undefined && entrypoint !== '') {
const extension = extname(entrypoint).toLowerCase()
if (extension === '.js' || extension === '.cjs' || extension === '.mjs') {
return [process.execPath, [entrypoint, ...args]]
}
if (extension !== '.cmd') return [entrypoint, args]
}
const home = process.env.PNPM_HOME?.trim()
if (home !== undefined && home !== '') {
const packageBin = resolve(home, '..', 'pnpm', 'bin')
for (const filename of ['pnpm.mjs', 'pnpm.cjs']) {
const candidate = resolve(packageBin, filename)
if (existsSync(candidate)) return [process.execPath, [candidate, ...args]]
}
}
if (process.platform === 'win32') {
throw new Error('build-exe-for-python-sdk: pnpm must expose a JavaScript entrypoint through npm_execpath or PNPM_HOME on Windows.')
}
return ['pnpm', args]
}
/**
* Render a command for logs and errors, quoting arguments with spaces.
* @param command - the executable.
@@ -289,7 +267,7 @@ class SingleExeBuild {
}
if (this.cli.dryRun) console.log(`build-exe-for-python-sdk: [dry-run] rm -rf ${this.staging}`)
else await rm(this.staging, { recursive: true, force: true })
await this.runPnpm('deploy', [
try { await this.runPnpm('deploy', [
'--filter',
DEPLOY_ROOT_PACKAGE,
'deploy',
@@ -301,7 +279,10 @@ class SingleExeBuild {
'--config.auto-install-peers=false',
'--config.link-workspace-packages=true',
this.staging,
])
]) } finally {
// Legacy deploy records production-only workspace state; restore the development installation before exec.
await this.runPnpm('restore development dependencies', ['install', '--offline', '--frozen-lockfile', '--prod=false', '--ignore-scripts'])
}
await this.restoreLegacyHoists()
await this.materializeStagedLinks()
if (this.cli.dryRun) {
@@ -322,38 +303,8 @@ class SingleExeBuild {
console.log('build-exe-for-python-sdk: [dry-run] restore direct dependencies omitted by legacy deploy')
return
}
const manifestPath = join(this.staging, 'package.json')
const manifest = JSON.parse(await readFile(manifestPath, 'utf8')) as {
dependencies?: Record<string, string>
}
const sourceNodeModules = resolve(root, DEPLOY_SOURCE_NODE_MODULES)
const restored: string[] = []
for (const dependency of Object.keys(manifest.dependencies ?? {}).sort()) {
const destination = join(this.staging, 'node_modules', dependency)
if (existsSync(destination)) continue
const source = join(sourceNodeModules, dependency)
if (!existsSync(source)) {
throw new Error(
`build-exe-for-python-sdk: deployed dependency ${dependency} is absent from both ${destination} and ${source}.`,
)
}
await mkdir(dirname(destination), { recursive: true })
const nestedNodeModules = join(source, 'node_modules')
await cp(source, destination, {
recursive: true,
dereference: true,
filter: path => path !== nestedNodeModules && !path.startsWith(nestedNodeModules + sep),
})
restored.push(dependency)
}
const stillMissing = Object.keys(manifest.dependencies ?? {})
.filter(dependency => !existsSync(join(this.staging, 'node_modules', dependency)))
if (stillMissing.length > 0) {
throw new Error(`build-exe-for-python-sdk: staged dependencies remain missing: ${stillMissing.join(', ')}.`)
}
if (restored.length > 0) {
console.log(`build-exe-for-python-sdk: restored legacy deploy hoists: ${restored.join(', ')}`)
}
const restored = await restoreLegacyHoists(this.staging, resolve(root, DEPLOY_SOURCE_NODE_MODULES))
if (restored.length > 0) console.log(`build-exe-for-python-sdk: restored legacy deploy hoists: ${restored.join(', ')}`)
}
/** Replace deploy-time package links with files and reject any remaining link. */
@@ -362,41 +313,7 @@ class SingleExeBuild {
console.log('build-exe-for-python-sdk: [dry-run] materialize staged package links')
return
}
const nodeModules = join(this.staging, 'node_modules')
let remaining = await this.findSymlink(nodeModules)
while (remaining !== undefined) {
const segments = remaining.slice(nodeModules.length + 1).split(sep)
const binIndex = segments.lastIndexOf('.bin')
if (binIndex >= 0) {
await rm(join(nodeModules, ...segments.slice(0, binIndex + 1)), { recursive: true, force: true })
remaining = await this.findSymlink(nodeModules)
continue
}
const destination = remaining
const source = await realpath(destination)
const nestedNodeModules = join(source, 'node_modules')
await rm(destination, { recursive: true, force: true })
await cp(source, destination, {
recursive: true,
dereference: true,
filter: path => path !== nestedNodeModules && !path.startsWith(nestedNodeModules + sep),
})
remaining = await this.findSymlink(nodeModules)
}
}
/** Return the first symbolic link below a directory, if one exists. */
private async findSymlink(directory: string): Promise<string | undefined> {
for (const entry of await readdir(directory, { withFileTypes: true })) {
const path = join(directory, entry.name)
const metadata = await lstat(path)
if (metadata.isSymbolicLink()) return path
if (metadata.isDirectory()) {
const nested = await this.findSymlink(path)
if (nested !== undefined) return nested
}
}
return undefined
await materializeStagedLinks(join(this.staging, 'node_modules'))
}
/** Add the executable entry and pkg assets to the staged manifest. */
+174
View File
@@ -0,0 +1,174 @@
/** Build a relocatable, native-host SSH helper archive with its private Node workers. */
import { spawn, execFileSync } from 'node:child_process'
import { existsSync, globSync } from 'node:fs'
import { chmod, copyFile, cp, lstat, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises'
import { basename, dirname, join, resolve } from 'node:path'
import { parseArgs } from 'node:util'
import { build } from 'tsdown'
import { create as createTar } from 'tar'
import { verifyRuntimeClosure } from './verify-runtime-closure.ts'
import { materializeStagedLinks, pnpmInvocation, restoreLegacyHoists } from './executable-packaging.ts'
import { resolveLinuxNodePtyAddon } from './executable-native-pty.ts'
import { artifactManifestSchema, fileDigest, payloadFiles, SSH_HELPER_TARGETS, verifyPayload, type SshHelperTarget } from './ssh-helper/artifact.ts'
import { probeHelper } from './ssh-helper/probe.ts'
import { verifyNativePayload } from './ssh-helper/native.ts'
const root = resolve(import.meta.dirname, '..')
const closureDirectory = 'packages/ssh/ssh-helper-runtime'
const closureName = '@deepseek-ai/dsh-ssh-helper-runtime'
const assets = ['package.json', 'lib/**/*.js', ...['js', 'cjs', 'mjs', 'json', 'node', 'so', 'so.*', 'dylib', 'wasm', 'sh', 'bash'].map(extension => `node_modules/**/*.${extension}`)]
interface Manifest { name: string; version: string; license?: string; dependencies?: Record<string, string> }
async function manifest(path: string): Promise<Manifest> { return JSON.parse(await readFile(path, 'utf8')) as Manifest }
async function run(command: string, args: string[]): Promise<void> {
console.log(`ssh-helper build: ${[command, ...args].join(' ')}`)
await new Promise<void>((resolve, reject) => {
const child = spawn(command, args, { cwd: root, stdio: 'inherit', env: { ...process.env, CI: 'true' } })
child.once('error', reject)
child.once('close', (code, signal) => {
if (code === 0 && signal === null) resolve()
else reject(new Error(`SSH helper build command failed (${code ?? signal}): ${command}`))
})
})
}
async function pnpm(...args: string[]): Promise<void> { const [command, argv] = pnpmInvocation(args); await run(command, argv) }
async function compileRuntime(dependencies: Readonly<Record<string, string>>): Promise<void> {
await pnpm('exec', 'tsx', 'native/system/scripts/build.ts')
await pnpm('exec', 'tsc', '-b', closureDirectory, 'native/system/packages/entry')
const names = new Set([closureName, ...Object.keys(dependencies)])
for (const path of globSync(['packages/*/*/package.json', 'vendor/*/package.json'], { cwd: root }).sort()) {
if (!names.has((await manifest(join(root, path))).name)) continue
const cwd = dirname(join(root, path))
const config = join(cwd, 'tsdown.config.ts')
if (existsSync(config)) await build({ cwd, config })
else await build({ cwd, config: false, entry: ['lib/types/{index,invariant,startup}.js'], outDir: 'lib', format: ['esm'], platform: 'node', target: 'es2024', fixedExtension: false, dts: false, clean: false })
}
}
async function prepareNative(staging: string, product: string, platform: string, arch: 'x64' | 'arm64'): Promise<void> {
const pty = join(staging, 'node_modules', 'node-pty')
await rm(join(pty, 'build'), { recursive: true, force: true })
for (const entry of await readdir(join(pty, 'prebuilds'))) {
if (entry !== `${platform}-${arch}`) await rm(join(pty, 'prebuilds', entry), { recursive: true, force: true })
}
if (platform === 'linux') {
const addon = resolveLinuxNodePtyAddon(join(root, 'packages/subprocess/subprocess-local/node_modules/node-pty'), arch)
await mkdir(join(pty, 'build/Release'), { recursive: true })
await copyFile(addon, join(pty, 'build/Release/pty.node'))
} else {
const destination = join(product, 'dsh-ssh-helper-spawn-helper')
await copyFile(join(pty, 'prebuilds', `darwin-${arch}`, 'spawn-helper'), destination)
await chmod(destination, 0o755)
}
const source = join(root, 'native/system/packages', `${platform}-${arch}`)
const destination = join(product, 'native/system')
await mkdir(destination, { recursive: true })
await copyFile(join(source, 'package.json'), join(destination, 'package.json'))
await cp(join(source, 'bin'), join(destination, 'bin'), { recursive: true })
// The Linux carrier targets glibc; no unused musl addon travels in this archive.
if (platform === 'linux') await rm(join(destination, 'bin/musl'), { recursive: true, force: true })
const binaries = platform === 'linux' ? ['bin/landlock-run', 'bin/glibc/system.node'] : ['bin/system.node']
for (const binary of binaries) {
if (!(await lstat(join(destination, binary))).isFile()) throw new Error(`Missing SSH native resource: ${binary}`)
}
}
async function copyLicenses(staging: string, product: string): Promise<void> {
const destination = join(product, 'LICENSES')
await mkdir(destination, { recursive: true })
const inventory: { name: string; version: string; license?: string }[] = []
for (const path of globSync(['node_modules/*/package.json', 'node_modules/@*/*/package.json'], { cwd: staging }).sort()) {
const directory = dirname(join(staging, path))
const data = await manifest(join(staging, path))
inventory.push({ name: data.name, version: data.version, ...(data.license === undefined ? {} : { license: data.license }) })
const licenses = (await readdir(directory)).filter(name => /^(?:licen[cs]e|copying|notice)(?:[.-]|$)/i.test(name))
for (const name of licenses) {
const source = join(directory, name)
if (!(await lstat(source)).isFile()) continue
const output = join(destination, data.name.replaceAll('/', '__'), name)
await mkdir(dirname(output), { recursive: true })
await copyFile(source, output)
}
}
await copyFile(join(root, 'LICENSE'), join(destination, 'deepseek-harness-MIT.txt'))
await copyFile(join(root, 'native/system/LICENSE'), join(destination, 'node-addon-system-BSD.txt'))
await writeFile(join(destination, 'packages.json'), `${JSON.stringify(inventory, null, 2)}\n`)
}
/**
* Build the selected native target and retain only its complete release archive.
* @param argv - command-line arguments, excluding the interpreter and script.
*/
export async function buildSshHelper(argv: string[]): Promise<void> {
const { values } = parseArgs({ args: argv, options: {
target: { type: 'string' }, output: { type: 'string' },
'skip-build': { type: 'boolean', default: false }, 'dry-run': { type: 'boolean', default: false },
} })
const host = `node24-${process.platform === 'darwin' ? 'macos' : process.platform}-${process.arch}`
const selected = values.target ?? host
if (!SSH_HELPER_TARGETS.some(target => target === selected)) throw new Error(`Unsupported SSH helper target: ${selected}`)
if (!values['dry-run'] && selected !== host) throw new Error(`SSH helper target ${selected} requires its native host; current host is ${host}`)
const target = selected as SshHelperTarget
const definition = await manifest(join(root, closureDirectory, 'package.json'))
const closure = await verifyRuntimeClosure(root, `${closureDirectory}/package.json`, false)
if (closure.failures.length > 0) throw new Error(`SSH helper runtime dependencies are incomplete:\n${closure.failures.join('\n')}`)
const output = resolve(root, values.output ?? 'dist-exe/ssh-helper')
if (values['dry-run']) {
console.log(`SSH helper ${target}: ${values['skip-build'] ? 'reuse built modules' : 'compile runtime'} -> deploy ${closureName} -> pkg --sea -> handshake -> ${output}`)
return
}
if (!values['skip-build']) await compileRuntime(definition.dependencies ?? {})
await mkdir(join(root, '.dsh-build'), { recursive: true })
const temporary = await mkdtemp(join(root, '.dsh-build/ssh-helper-'))
try {
const staging = join(temporary, 'staging')
const product = join(temporary, 'dsh-ssh-helper')
await mkdir(product)
try {
await pnpm('--filter', closureName, 'deploy', '--legacy', '--prod', '--config.allow-unused-patches=true', '--config.node-linker=hoisted', '--config.auto-install-peers=false', '--config.link-workspace-packages=true', staging)
} finally {
// Legacy deploy records production-only workspace state; pnpm's next exec otherwise prunes build tools.
await pnpm('install', '--offline', '--frozen-lockfile', '--prod=false', '--ignore-scripts')
}
await restoreLegacyHoists(staging, join(root, closureDirectory, 'node_modules'))
await materializeStagedLinks(join(staging, 'node_modules'))
await prepareNative(staging, product, process.platform, process.arch as 'x64' | 'arm64')
await verifyNativePayload(join(staging, 'node_modules'), target)
const stagedManifest = await manifest(join(staging, 'package.json'))
await writeFile(join(staging, 'runtime-bootstrap.mjs'), 'import { runSshHelperRuntime } from "./lib/index.js";\nawait runSshHelperRuntime();\n')
await writeFile(join(staging, 'package.json'), `${JSON.stringify({ ...stagedManifest,
bin: 'runtime-bootstrap.mjs', pkg: { assets, ignore: ['node_modules/@deepseek-ai/node-addon-system-*/**/*'] },
}, null, 2)}\n`)
const executable = join(product, 'dsh-ssh-helper')
await pnpm('exec', 'pkg', staging, '--sea', '--targets', target, '--output', executable)
await chmod(executable, 0o755)
await verifyNativePayload(product, target)
await copyLicenses(staging, product)
const hello = await probeHelper(executable, product)
if (hello.kind !== 'executable' || hello.executable !== executable) throw new Error('Packaged SSH helper did not report its physical executable')
const metadata = artifactManifestSchema.parse({
format: 1, version: definition.version, target,
commit: execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim(),
dirty: execFileSync('git', ['status', '--porcelain'], { cwd: root, encoding: 'utf8' }).trim() !== '',
protocol: hello.protocol, nodeVersion: hello.nodeVersion,
files: await Promise.all((await payloadFiles(product)).map(async path => ({
path, sha256: await fileDigest(join(product, path)), mode: (await lstat(join(product, path))).mode & 0o777,
}))),
})
await writeFile(join(product, 'manifest.json'), `${JSON.stringify(metadata, null, 2)}\n`)
await verifyPayload(product)
await mkdir(output, { recursive: true })
const archive = join(output, `dsh-ssh-helper-${definition.version}-${target.slice('node24-'.length)}.tar.gz`)
await createTar({ cwd: temporary, file: archive, gzip: true, portable: true }, ['dsh-ssh-helper'])
await writeFile(`${archive}.sha256`, `${await fileDigest(archive)} ${basename(archive)}\n`)
console.log(`SSH helper archive: ${archive}`)
} finally {
await rm(temporary, { recursive: true, force: true })
}
}
if (import.meta.main) await buildSshHelper(process.argv.slice(2))
+2
View File
@@ -324,6 +324,8 @@ export function checkExperimentalManifest(
}
function isReleaseMemberDirectory(dir: string): boolean {
// The SSH carrier is distributed as executable archives, never as an npm application.
if (dir === 'packages/ssh/ssh-helper-runtime') return false
return standardReleaseMemberDirectory.test(dir) || isPublicExperimentalPackageDirectory(dir)
}
+53 -1
View File
@@ -533,7 +533,7 @@ describe('CI workflow', () => {
})
.map(([name]) => name)
.sort()
expect(pushReachable).toEqual(['python-runtime', 'serial-linux-selfhosted', 'serial-windows', 'windows'])
expect(pushReachable).toEqual(['python-runtime', 'serial-linux-selfhosted', 'serial-windows', 'ssh-helper', 'windows'])
// Manual benchmarks retain their bounded fan-out.
for (const name of ['larger-runner-benchmark', 'consolidated-runner-benchmark']) {
@@ -607,6 +607,58 @@ describe('CI workflow', () => {
expect(aggregate.needs).toContain('python-runtime')
})
it('requires SSH helper acceptance before publication and PR aggregation', () => {
const ci = loadWorkflow('.github/workflows/ci.yml')
expect(workflowJob(ci, 'ssh-helper')).toMatchObject({
uses: './.github/workflows/build-exe-for-ssh-helper.yml', with: { targets: 'node24-linux-x64' },
})
expect(workflowJob(ci, 'all-checks-passed').needs).toContain('ssh-helper')
const master = workflowJob(loadWorkflow('.github/workflows/ci-master.yml'), 'ssh-helper')
expect(master.if).toContain("inputs.suite == 'ssh-helper'")
if (!isRecord(master.with)) throw new Error('SSH master caller must select native targets')
expect(evaluateRunsOn(master.with.targets, { github: { event_name: 'push' } })).toBe('node24-linux-arm64,node24-macos-arm64,node24-macos-x64')
expect(evaluateRunsOn(master.with.targets, { github: { event_name: 'workflow_dispatch' } })).toBe('node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-macos-x64')
const workflow = loadWorkflow('.github/workflows/build-exe-for-ssh-helper.yml')
const build = workflowJob(workflow, 'build')
if (!Array.isArray(build.steps)) throw new Error('SSH build must define its verification steps')
const steps = build.steps.filter(isRecord)
const artifact = steps.findIndex(step => String(step.run).includes('scripts/verify-ssh-helper-artifact.ts'))
const transport = steps.findIndex(step => String(step.run).includes('scripts/verify-ssh-helper-ssh.ts'))
const landlock = steps.findIndex(step => String(step.run).includes('--backend=landlock-run'))
const upload = steps.findIndex(step => String(step.uses).startsWith('actions/upload-artifact@'))
expect(artifact).toBeGreaterThan(-1)
expect(landlock).toBeGreaterThan(artifact)
expect(transport).toBeGreaterThan(artifact)
expect(upload).toBeGreaterThan(transport)
for (const index of [artifact, transport, landlock, upload]) expect(steps[index]).not.toHaveProperty('continue-on-error', true)
const publish = loadWorkflow('.github/workflows/publish-ssh-helper.yml')
expect(publish.permissions).toEqual({ contents: 'read' })
expect(workflowJob(publish, 'build')).toMatchObject({ needs: 'validate', with: { release: true } })
expect(workflowJob(publish, 'publish')).toMatchObject({ needs: ['validate', 'build'], permissions: { contents: 'write' } })
})
it('validates the SSH native matrix before allocating runners', () => {
const plan = workflowJob(loadWorkflow('.github/workflows/build-exe-for-ssh-helper.yml'), 'plan')
if (!Array.isArray(plan.steps)) throw new Error('SSH matrix plan has no steps')
const step: unknown = plan.steps.find(value => isRecord(value) && value.id === 'targets')
if (!isRecord(step) || typeof step.run !== 'string') throw new Error('SSH matrix plan has no script')
const source = step.run.split("<<'JS'\n")[1]?.split('\nJS')[0]?.replace(/^import .*;\n/gm, '')
if (source === undefined) throw new Error('SSH matrix plan must be an executable Node script')
const evaluate = (requested: string): unknown => {
let output = ''
runInNewContext(source, {
process: { env: { REQUESTED_TARGETS: requested, GITHUB_OUTPUT: 'output' } },
appendFileSync: (_path: string, text: string) => { output += text },
}, { timeout: 1000 })
return JSON.parse(output.slice('matrix='.length))
}
expect(evaluate('')).toHaveLength(4)
expect(evaluate('node24-linux-arm64')).toEqual([{ target: 'node24-linux-arm64', runner: 'ubuntu-24.04-arm' }])
for (const invalid of ['node24-linux-arm64,node24-linux-arm64', 'node24-win-x64', 'node24-linux-x64,', ' ']) {
expect(() => evaluate(invalid)).toThrow('Invalid or duplicate')
}
})
it('keeps every Vitest project process-isolated on native Windows', () => {
const config = readFileSync(resolve(root, 'vitest.config.ts'), 'utf8')
+1
View File
@@ -69,6 +69,7 @@ const PACKAGE_LIBRARIES: Readonly<Record<string, string>> = {
'packages/sandbox/sandbox-windows-acl': 'Windows ACL sandbox library consumed by sandbox-local.',
'packages/sdk/client': 'Client-process library; the spawned runtime owns plugin behavior.',
'packages/sdk/protocol': 'Wire-protocol library with type declarations only.',
'packages/ssh/ssh-helper-runtime': 'Private executable entry consumed by the packaging pipeline; no Cordis plugin export.',
'packages/session/session-format': 'Pure Session format planning, codec dispatch, and lossless JSON library.',
'packages/session/session-format-catalog': 'Generated build-static Session format inventory with no plugin registration.',
'packages/session/session-format-v0-to-v1': 'Pure released-v0 codec and adjacent migration library.',
@@ -1,4 +1,4 @@
/** Resolve the native node-pty input used by the Python SDK runtime builder. */
/** Resolve the native node-pty input used by the SDK and SSH executable builders. */
import { existsSync } from 'node:fs'
import { join } from 'node:path'
@@ -18,7 +18,7 @@ export function resolveLinuxNodePtyAddon(
const prebuilt = join(packageDirectory, 'prebuilds', `linux-${arch}`, 'pty.node')
if (existsSync(prebuilt)) return prebuilt
throw new Error(
`build-exe-for-python-sdk: node-pty addon is absent from both ${built} and ${prebuilt}.`,
`executable-native-pty: node-pty addon is absent from both ${built} and ${prebuilt}.`,
)
}
@@ -39,7 +39,7 @@ export function resolveWindowsNodePtyAddons(
]
const missing = addons.filter(path => !existsSync(path))
if (missing.length > 0) {
throw new Error(`build-exe-for-python-sdk: Windows node-pty addons are missing: ${missing.join(', ')}.`)
throw new Error(`executable-native-pty: Windows node-pty addons are missing: ${missing.join(', ')}.`)
}
return addons
}
+46
View File
@@ -0,0 +1,46 @@
import { lstat, mkdir, mkdtemp, readFile, rm, symlink, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { expect, it, onTestFinished } from 'vitest'
import { materializeStagedLinks, restoreLegacyHoists } from './executable-packaging.ts'
async function fixture() {
const root = await mkdtemp(join(tmpdir(), 'dsh-exe-deploy-'))
onTestFinished(() => rm(root, { recursive: true, force: true }))
const source = join(root, 'source')
const staging = join(root, 'staging')
await mkdir(source)
await mkdir(join(staging, 'node_modules'), { recursive: true })
return { root, source, staging, modules: join(staging, 'node_modules') }
}
it('restores omitted direct peers without copying their nested module instances', async () => {
const test = await fixture()
await writeFile(join(test.staging, 'package.json'), JSON.stringify({ dependencies: { peer: 'workspace:^' } }))
await mkdir(join(test.source, 'peer/node_modules/shadow'), { recursive: true })
await writeFile(join(test.source, 'peer/index.js'), 'export const identity = 1\n')
await writeFile(join(test.source, 'peer/node_modules/shadow/index.js'), 'shadow')
expect(await restoreLegacyHoists(test.staging, test.source)).toEqual(['peer'])
expect(await readFile(join(test.modules, 'peer/index.js'), 'utf8')).toBe('export const identity = 1\n')
await expect(lstat(join(test.modules, 'peer/node_modules'))).rejects.toThrow('ENOENT')
expect(await restoreLegacyHoists(test.staging, test.source)).toEqual([])
})
it('rejects a missing dependency instead of packaging an incomplete graph', async () => {
const test = await fixture()
await writeFile(join(test.staging, 'package.json'), JSON.stringify({ dependencies: { missing: 'workspace:^' } }))
await expect(restoreLegacyHoists(test.staging, test.source)).rejects.toThrow('absent from both')
})
it('materializes a package junction without deleting its source and drops linked command directories', async () => {
const test = await fixture()
await mkdir(join(test.source, 'peer'))
await writeFile(join(test.source, 'peer/index.js'), 'intact')
await symlink(join(test.source, 'peer'), join(test.modules, 'peer'), 'junction')
await symlink(join(test.source, 'peer'), join(test.modules, '.bin'), 'junction')
await materializeStagedLinks(test.modules)
expect((await lstat(join(test.modules, 'peer'))).isSymbolicLink()).toBe(false)
expect(await readFile(join(test.modules, 'peer/index.js'), 'utf8')).toBe('intact')
expect(await readFile(join(test.source, 'peer/index.js'), 'utf8')).toBe('intact')
await expect(lstat(join(test.modules, '.bin'))).rejects.toThrow('ENOENT')
})
+113
View File
@@ -0,0 +1,113 @@
/** Shared production-deploy preparation for the SDK and SSH executable carriers. */
import { existsSync } from 'node:fs'
import { cp, lstat, mkdir, readFile, readdir, realpath, rm, unlink } from 'node:fs/promises'
import { dirname, extname, join, resolve, sep } from 'node:path'
/**
* Resolve pnpm without invoking a command shell, including Windows shims.
* @param args - pnpm arguments.
* @returns the executable and its argument vector.
*/
export function pnpmInvocation(args: string[]): [command: string, args: string[]] {
const entrypoint = process.env.npm_execpath?.trim()
if (entrypoint !== undefined && entrypoint !== '') {
const extension = extname(entrypoint).toLowerCase()
if (extension === '.js' || extension === '.cjs' || extension === '.mjs') {
return [process.execPath, [entrypoint, ...args]]
}
if (extension !== '.cmd') return [entrypoint, args]
}
const home = process.env.PNPM_HOME?.trim()
if (home !== undefined && home !== '') {
const packageBin = resolve(home, '..', 'pnpm', 'bin')
for (const filename of ['pnpm.mjs', 'pnpm.cjs']) {
const candidate = resolve(packageBin, filename)
if (existsSync(candidate)) return [process.execPath, [candidate, ...args]]
}
}
if (process.platform === 'win32') {
throw new Error('executable-packaging: pnpm must expose a JavaScript entrypoint through npm_execpath or PNPM_HOME on Windows.')
}
return ['pnpm', args]
}
/**
* Restore direct dependencies omitted by pnpm's legacy hoister, excluding nested module trees.
* @param staging - deployed runtime root.
* @param sourceNodeModules - deploy source's installed dependencies.
* @returns names restored from the source installation.
*/
export async function restoreLegacyHoists(staging: string, sourceNodeModules: string): Promise<string[]> {
const manifestPath = join(staging, 'package.json')
const manifest = JSON.parse(await readFile(manifestPath, 'utf8')) as {
dependencies?: Record<string, string>
}
const restored: string[] = []
for (const dependency of Object.keys(manifest.dependencies ?? {}).sort()) {
const destination = join(staging, 'node_modules', dependency)
if (existsSync(destination)) continue
const source = join(sourceNodeModules, dependency)
if (!existsSync(source)) {
throw new Error(
`executable-packaging: deployed dependency ${dependency} is absent from both ${destination} and ${source}.`,
)
}
await mkdir(dirname(destination), { recursive: true })
const nestedNodeModules = join(source, 'node_modules')
await cp(source, destination, {
recursive: true,
dereference: true,
filter: path => path !== nestedNodeModules && !path.startsWith(nestedNodeModules + sep),
})
restored.push(dependency)
}
const stillMissing = Object.keys(manifest.dependencies ?? {})
.filter(dependency => !existsSync(join(staging, 'node_modules', dependency)))
if (stillMissing.length > 0) {
throw new Error(`executable-packaging: staged dependencies remain missing: ${stillMissing.join(', ')}.`)
}
return restored
}
/**
* Materialize deployed package links and remove command shims before embedding files.
* @param nodeModules - deployed dependency directory, owned by the current build.
*/
export async function materializeStagedLinks(nodeModules: string): Promise<void> {
let remaining = await findSymlink(nodeModules)
while (remaining !== undefined) {
const segments = remaining.slice(nodeModules.length + 1).split(sep)
const binIndex = segments.lastIndexOf('.bin')
if (binIndex >= 0) {
const directory = join(nodeModules, ...segments.slice(0, binIndex + 1))
if ((await lstat(directory)).isSymbolicLink()) await unlink(directory)
else await rm(directory, { recursive: true, force: true })
remaining = await findSymlink(nodeModules)
continue
}
const destination = remaining
const source = await realpath(destination)
const nestedNodeModules = join(source, 'node_modules')
await unlink(destination)
await cp(source, destination, {
recursive: true,
dereference: true,
filter: path => path !== nestedNodeModules && !path.startsWith(nestedNodeModules + sep),
})
remaining = await findSymlink(nodeModules)
}
}
/** Return the first symbolic link below a directory, if one exists. */
async function findSymlink(directory: string): Promise<string | undefined> {
for (const entry of await readdir(directory, { withFileTypes: true })) {
const path = join(directory, entry.name)
const metadata = await lstat(path)
if (metadata.isSymbolicLink()) return path
if (metadata.isDirectory()) {
const nested = await findSymlink(path)
if (nested !== undefined) return nested
}
}
return undefined
}
-5
View File
@@ -1534,11 +1534,6 @@
"aa5de2d4958ec64219565d53ec5a9d7f40f1f60037a2d52d1a9c3c65db034d73": 1,
"be027af1ef5cb3bfe41eeb19b54dbb176ec7c23ae367d709059839f9851833d5": 1
},
"packages/ptc-runtime/ptc-runtime-node/tests/launch.spec.ts": {
"57f598f462c77fae2b147b07c2e1228199c53dfd43652021af57712b4860732a": 1,
"9ddf7336f94d8ff4be8fe0ae7977c1846e6e92e7ea66180f8e2e6f8902b3c915": 1,
"e02136d8ab1f1fb84c6e772bbc863ceb3a0a3801f6eb158d5b9be074baa0885c": 1
},
"packages/ptc-runtime/ptc-runtime-node/tests/output-json.spec.ts": {
"8c4c486bbc27559714f4e0aaf376c72c49ac9157539b03c6c12853b8d3fc6e74": 1
},
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -euo pipefail
# Generate node-gyp's Makefile on the runner, then compile against glibc 2.28.
case "$(uname -m)" in
x86_64) image=quay.io/pypa/manylinux_2_28_x86_64 ;;
aarch64) image=quay.io/pypa/manylinux_2_28_aarch64 ;;
*) echo 'node-pty release preparation requires native Linux x64 or arm64' >&2; exit 1 ;;
esac
addon_dir="$(realpath packages/subprocess/subprocess-local/node_modules/node-pty)"
pnpm_setup_root="$(realpath "$(dirname "$(dirname "$PNPM_HOME")")")"
(cd "$addon_dir" && npm_config_build_from_source=true pnpm run install)
addon="$addon_dir/build/Release/pty.node"
test -f "$addon_dir/build/Makefile"
docker run --rm \
--user "$(id -u):$(id -g)" \
-v "$PWD:$PWD" \
-v "$HOME/.cache/node-gyp:$HOME/.cache/node-gyp:ro" \
-v "$pnpm_setup_root:$pnpm_setup_root:ro" \
-w "$addon_dir" "$image" \
bash -euxo pipefail -c 'rm -rf build/Release && make -C build -j2 BUILDTYPE=Release'
test -f "$addon"
readelf --version-info "$addon"
+117
View File
@@ -0,0 +1,117 @@
import { cp, lstat, mkdir, mkdtemp, readFile, rm, symlink, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { create as createTar } from 'tar'
import { expect, it, onTestFinished } from 'vitest'
import { artifactManifestSchema, fileDigest, payloadFiles, SSH_HELPER_TARGETS, verifyPayload } from './artifact.ts'
import { macosDeploymentVersions } from './native.ts'
import { extractHelper } from '../verify-ssh-helper-artifact.ts'
import { prepareSshRelease } from './release.ts'
async function fixture() {
const root = await mkdtemp(join(tmpdir(), 'dsh-helper-archive-'))
onTestFinished(() => rm(root, { recursive: true, force: true }))
const payload = join(root, 'dsh-ssh-helper')
await mkdir(join(payload, 'native/system'), { recursive: true })
await writeFile(join(payload, 'dsh-ssh-helper'), 'fixture executable')
await writeFile(join(payload, 'native/system/package.json'), '{}')
const metadata = {
format: 1, version: '1.0.0', commit: 'a'.repeat(40), dirty: false,
target: 'node24-linux-x64', protocol: 2, nodeVersion: 'v24.1.0',
files: await Promise.all((await payloadFiles(payload)).map(async path => ({
path, sha256: await fileDigest(join(payload, path)), mode: (await lstat(join(payload, path))).mode & 0o777,
}))),
}
const save = async () => { await writeFile(join(payload, 'manifest.json'), JSON.stringify(metadata)) }
await save()
return { root, payload, metadata, save }
}
it('validates the exact unpacked inventory and rejects altered bytes', async () => {
const test = await fixture()
expect(await verifyPayload(test.payload)).toEqual(test.metadata)
await writeFile(join(test.payload, 'dsh-ssh-helper'), 'changed executable')
await expect(verifyPayload(test.payload)).rejects.toThrow('digest differs')
})
it('rejects missing, undeclared and duplicate payload files', async () => {
const test = await fixture()
await writeFile(join(test.payload, 'undeclared'), '')
await expect(verifyPayload(test.payload)).rejects.toThrow('inventory')
await rm(join(test.payload, 'undeclared'))
test.metadata.files.push(test.metadata.files[0]!)
await test.save()
await expect(verifyPayload(test.payload)).rejects.toThrow('inventory')
test.metadata.files.pop()
await test.save()
await rm(join(test.payload, 'native/system/package.json'))
await expect(verifyPayload(test.payload)).rejects.toThrow('inventory')
})
it.each(['../outside', '/absolute', 'a/../b', 'a\\b', 'a//b'])('rejects unsafe manifest path %s', async (path) => {
const test = await fixture()
expect(() => artifactManifestSchema.parse({ ...test.metadata, files: [{ ...test.metadata.files[0], path }] })).toThrow('payload path')
})
it.skipIf(process.platform === 'win32')('rejects links before extracting an archive', async () => {
const test = await fixture()
await symlink('/etc/passwd', join(test.payload, 'outside'))
await expect(payloadFiles(test.payload)).rejects.toThrow('symbolic link')
const archive = join(test.root, 'linked.tar.gz')
await createTar({ cwd: test.root, file: archive, gzip: true }, ['dsh-ssh-helper'])
const extracted = join(test.root, 'unpack')
await mkdir(extracted)
await expect(extractHelper(archive, extracted)).rejects.toThrow('Invalid SSH helper archive entry')
})
it('extracts and verifies a portable archive in a new directory', async () => {
const test = await fixture()
const archive = join(test.root, 'helper.tar.gz')
await createTar({ cwd: test.root, file: archive, gzip: true, portable: true }, ['dsh-ssh-helper'])
const extracted = join(test.root, 'relocated')
await mkdir(extracted)
const directory = await extractHelper(archive, extracted)
expect(dirname(directory)).toBe(extracted)
expect(await verifyPayload(directory)).toEqual(test.metadata)
expect(await readFile(join(directory, 'dsh-ssh-helper'), 'utf8')).toBe('fixture executable')
})
it('reads minimum macOS versions without accepting a linked library version as the OS version', () => {
expect(macosDeploymentVersions('Load command 0\ncmd LC_VERSION_MIN_MACOSX\nversion 11.0\nLoad command 1\ncmd LC_LOAD_DYLIB\ncurrent version 1311.100.3\nLoad command 2\ncmd LC_BUILD_VERSION\nminos 14.0\nsdk 15.1\n'))
.toEqual(['11.0', '14.0'])
expect(macosDeploymentVersions('Load command 0\ncmd LC_LOAD_DYLIB\ncurrent version 14.0\n')).toEqual([])
})
it('publishes only a complete clean-source matrix with native and no-Node evidence', async () => {
const test = await fixture()
const archives = join(test.root, 'release')
await mkdir(archives)
const checks = ['executable-handshake', 'guarded-files-and-streams', 'managed-process-output', 'process-cancellation', 'native-pty', 'native-flock', 'embedded-ptc-and-deadline', 'sandbox-enforcement']
for (const target of SSH_HELPER_TARGETS) {
const parent = join(test.root, target)
await mkdir(parent)
const payload = join(parent, 'dsh-ssh-helper')
await cp(test.payload, payload, { recursive: true })
const manifest = { ...test.metadata, target }
await writeFile(join(payload, 'manifest.json'), JSON.stringify(manifest))
const archive = join(archives, `dsh-ssh-helper-1.0.0-${target.slice('node24-'.length)}.tar.gz`)
await createTar({ cwd: parent, file: archive, gzip: true, portable: true }, ['dsh-ssh-helper'])
await writeFile(`${archive}.sha256`, `${await fileDigest(archive)} archive\n`)
const runtime = { checks, nodeVersion: 'v24.1.0', platform: target.includes('linux') ? 'linux' : 'darwin', sandboxLane: 'required' }
await writeFile(join(archives, `${target}.json`), JSON.stringify({ manifest, runtime, restrictedWorker: true, readonlyInstall: true, concurrentColdStarts: 2 }))
if (target.includes('linux')) {
await writeFile(join(archives, `${target}.ssh.json`), JSON.stringify({ manifest, runtime: { ...runtime, checks: [...checks, 'target-has-no-node'], sandboxLane: 'portable' }, glibc: 'glibc 2.28' }))
await writeFile(join(archives, `${target}.landlock.json`), JSON.stringify({ manifest, runtime, expectedBackend: 'landlock-run' }))
}
}
expect(await prepareSshRelease(archives, '1.0.0', test.metadata.commit)).toHaveLength(5)
expect((await readFile(join(archives, 'SHA256SUMS'), 'utf8')).trim().split('\n')).toHaveLength(4)
await expect(prepareSshRelease(archives, '1.0.0', 'b'.repeat(40))).rejects.toThrow()
const report = join(archives, 'node24-linux-x64.ssh.json')
const evidence = JSON.parse(await readFile(report, 'utf8')) as { runtime: { checks: string[] } }
evidence.runtime.checks = evidence.runtime.checks.filter(check => check !== 'target-has-no-node')
await writeFile(report, JSON.stringify(evidence))
await expect(prepareSshRelease(archives, '1.0.0', test.metadata.commit)).rejects.toThrow('lacks target-has-no-node')
await rm(report)
await expect(prepareSshRelease(archives, '1.0.0', test.metadata.commit)).rejects.toThrow('ENOENT')
})
+88
View File
@@ -0,0 +1,88 @@
/** Versioned archive metadata and file integrity checks for SSH helper releases. */
import { createHash } from 'node:crypto'
import { lstat, readFile, readdir } from 'node:fs/promises'
import { join, relative, sep } from 'node:path'
import { z } from 'zod'
/** Native target identifiers accepted by the builder and release workflow. */
export const SSH_HELPER_TARGETS = ['node24-linux-x64', 'node24-linux-arm64', 'node24-macos-x64', 'node24-macos-arm64'] as const
/** One qualified OS and architecture for a bundled Node 24 helper. */
export type SshHelperTarget = typeof SSH_HELPER_TARGETS[number]
const payloadPath = z.string().min(1).refine(value =>
!value.startsWith('/') && !value.includes('\\') && !value.includes('\0')
&& value.split('/').every(part => part !== '' && part !== '.' && part !== '..'), 'invalid payload path')
/** Parser for metadata supplied beside an executable, including every runtime resource. */
export const artifactManifestSchema = z.object({
format: z.literal(1),
version: z.string().regex(/^\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?$/),
commit: z.string().regex(/^[a-f0-9]{40}$/),
dirty: z.boolean(),
target: z.enum(SSH_HELPER_TARGETS),
protocol: z.number().int().positive(),
nodeVersion: z.string().regex(/^v24\./),
files: z.array(z.object({
path: payloadPath, sha256: z.string().regex(/^[a-f0-9]{64}$/), mode: z.number().int().min(0).max(0o777),
}).strict()).min(1),
}).strict()
/** Results written by the isolated Loader consumer after its assertions succeed. */
export const runtimeReportSchema = z.object({
checks: z.array(z.string()), platform: z.enum(['linux', 'darwin']), nodeVersion: z.string(),
sandboxLane: z.enum(['required', 'unavailable', 'portable']),
}).strict()
/** Parsed release metadata; files excludes manifest.json to avoid a self-referential digest. */
export type ArtifactManifest = z.infer<typeof artifactManifestSchema>
/**
* Enumerate an owned payload without following links outside its directory.
* @param root - unpacked payload directory.
* @returns sorted POSIX relative file paths, excluding manifest.json.
*/
export async function payloadFiles(root: string): Promise<string[]> {
const paths: string[] = []
const visit = async (directory: string): Promise<void> => {
for (const name of await readdir(directory)) {
const path = join(directory, name)
const metadata = await lstat(path)
if (metadata.isSymbolicLink()) throw new Error(`SSH helper payload contains a symbolic link: ${path}`)
if (metadata.isDirectory()) await visit(path)
else if (metadata.isFile()) paths.push(relative(root, path).split(sep).join('/'))
else throw new Error(`SSH helper payload contains a non-regular file: ${path}`)
}
}
await visit(root)
return paths.filter(path => path !== 'manifest.json').sort()
}
/**
* Hash the exact file bytes used for installation or publication.
* @param path - regular file path.
* @returns lowercase SHA-256.
*/
export async function fileDigest(path: string): Promise<string> {
return createHash('sha256').update(await readFile(path)).digest('hex')
}
/**
* Verify all payload bytes and permissions before running an unpacked release.
* @param directory - unpacked payload root.
* @returns the validated metadata; missing, duplicate, extra, or altered files reject.
*/
export async function verifyPayload(directory: string): Promise<ArtifactManifest> {
const manifest = artifactManifestSchema.parse(JSON.parse(await readFile(join(directory, 'manifest.json'), 'utf8')))
const actual = await payloadFiles(directory)
const declared = manifest.files.map(file => file.path).sort()
if (new Set(declared).size !== declared.length || JSON.stringify(actual) !== JSON.stringify(declared)) {
throw new Error('SSH helper payload file inventory differs from manifest.json')
}
if (!declared.includes('dsh-ssh-helper') || !declared.includes('native/system/package.json')) throw new Error('SSH helper payload omits required runtime files')
for (const file of manifest.files) {
const path = join(directory, file.path)
if (await fileDigest(path) !== file.sha256) throw new Error(`SSH helper payload digest differs: ${file.path}`)
if (((await lstat(path)).mode & 0o777) !== file.mode) throw new Error(`SSH helper payload permissions differ: ${file.path}`)
}
return manifest
}
+74
View File
@@ -0,0 +1,74 @@
/** Inspect every native payload, including modules embedded in the executable. */
import { execFileSync } from 'node:child_process'
import { open } from 'node:fs/promises'
import { join } from 'node:path'
import { payloadFiles, type SshHelperTarget } from './artifact.ts'
function newer(version: string, ceiling: number[]): boolean {
const components = version.split('.').map(Number)
for (let index = 0; index < Math.max(components.length, ceiling.length); index += 1) {
const difference = (components[index] ?? 0) - (ceiling[index] ?? 0)
if (difference !== 0) return difference > 0
}
return false
}
/**
* Read deployment versions without confusing linked-library versions with OS requirements.
* @param output - otool load-command text, possibly containing multiple architecture slices.
* @returns every minimum macOS version declared by the binary.
*/
export function macosDeploymentVersions(output: string): string[] {
let command: string | undefined
const versions: string[] = []
for (const line of output.split('\n')) {
const text = line.trim()
if (/^Load command \d+$/.test(text)) command = undefined
else if (text.startsWith('cmd ')) command = text.slice(4)
else {
const match = /^(minos|version)\s+(\d+(?:\.\d+)+)$/.exec(text)
if (match !== null && ((command === 'LC_BUILD_VERSION' && match[1] === 'minos') || (command === 'LC_VERSION_MIN_MACOSX' && match[1] === 'version'))) versions.push(match[2] as string)
}
}
return versions
}
/**
* Inspect all ELF or Mach-O files for the requested architecture and deployment baseline.
* @param directory - symlink-free staging or payload directory.
* @param target - build target whose ABI must match every native file.
* @returns relative paths of inspected native files.
*/
export async function verifyNativePayload(directory: string, target: SshHelperTarget): Promise<string[]> {
const inspected: string[] = []
const linux = target.includes('-linux-')
const arch = target.endsWith('-arm64') ? 'arm64' : 'x64'
for (const path of await payloadFiles(directory)) {
const filename = join(directory, path)
const file = await open(filename, 'r')
const header = Buffer.alloc(4)
try { await file.read(header, 0, 4, 0) } finally { await file.close() }
const magic = header.readUInt32BE()
const elf = magic === 0x7f454c46
const macho = [0xcffaedfe, 0xcefaedfe, 0xfeedfacf, 0xfeedface, 0xcafebabe, 0xbebafeca, 0xcafebabf].includes(magic)
if (!elf && !macho) continue
if (linux !== elf) throw new Error(`Native file does not belong to ${target}: ${path}`)
if (elf) {
const elfHeader = execFileSync('readelf', ['--file-header', filename], { encoding: 'utf8' })
const expected = arch === 'arm64' ? /Machine:\s+AArch64/ : /Machine:\s+Advanced Micro Devices X86-64/
if (!expected.test(elfHeader)) throw new Error(`ELF architecture differs from ${target}: ${path}`)
const versions = execFileSync('readelf', ['--version-info', filename], { encoding: 'utf8' })
for (const match of versions.matchAll(/\bGLIBC_(\d+(?:\.\d+)+)/g)) {
if (newer(match[1] as string, [2, 28])) throw new Error(`${path} requires GLIBC_${match[1]}, exceeding 2.28`)
}
} else {
execFileSync('lipo', [filename, '-verify_arch', arch === 'arm64' ? 'arm64' : 'x86_64'], { stdio: 'pipe' })
const loads = execFileSync('otool', ['-l', filename], { encoding: 'utf8' })
const versions = macosDeploymentVersions(loads)
if (versions.length === 0) throw new Error(`Mach-O has no deployment version: ${path}`)
if (versions.some(version => newer(version, [14, 0]))) throw new Error(`${path} requires macOS newer than 14.0`)
}
inspected.push(path)
}
return inspected
}
+42
View File
@@ -0,0 +1,42 @@
/** Short handshake against an actual executable, with bounded process ownership. */
import { spawn } from 'node:child_process'
import { z } from 'zod'
import { SshRpcPeer, SSH_PROTOCOL_VERSION } from '../../packages/ssh/ssh/src/protocol.ts'
import { helloSchema } from '../../packages/ssh/ssh/src/schemas.ts'
/**
* Run a packaged helper and read its runtime version through the private wire protocol.
* @param executable - physical executable under test.
* @param workspace - existing directory supplied for the handshake.
* @returns the validated handshake after the helper has exited.
*/
export async function probeHelper(executable: string, workspace: string): Promise<z.infer<typeof helloSchema>> {
const child = spawn(executable, [], { cwd: workspace, env: { ...process.env, NODE_OPTIONS: '--disable-sigusr1' }, stdio: ['pipe', 'pipe', 'pipe'] })
let diagnostic = ''
child.stderr.on('data', (chunk: Buffer) => { diagnostic = (diagnostic + chunk.toString()).slice(-8192) })
const closed = new Promise<void>((resolve, reject) => {
child.once('error', reject)
child.once('close', (code, signal) => {
if (code === 0 && signal === null) resolve()
else reject(new Error(`SSH helper probe exited (${code ?? signal}): ${diagnostic}`))
})
})
void closed.catch(() => {})
const rpc = new SshRpcPeer(child.stdout, child.stdin, 64 * 1024 * 1024, 128)
child.once('error', (error) => { rpc.close(error) })
const force = setTimeout(() => { child.kill('SIGKILL') }, 30_000)
try {
const hello = await rpc.request('hello', { protocol: SSH_PROTOCOL_VERSION, workspace, leaseMs: 30_000 }, helloSchema, AbortSignal.timeout(20_000))
await rpc.request('close', {}, z.null(), AbortSignal.timeout(5000))
child.stdin.end()
await closed
return hello
} catch (error) {
child.kill('SIGTERM')
await closed.catch(() => {})
throw new Error(`SSH helper handshake failed: ${diagnostic}`, { cause: error })
} finally {
clearTimeout(force)
rpc.close()
}
}
+76
View File
@@ -0,0 +1,76 @@
/** Require four complete, same-commit executable reports before publishing release assets. */
import assert from 'node:assert/strict'
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { basename, join, resolve } from 'node:path'
import { parseArgs } from 'node:util'
import { z } from 'zod'
import { artifactManifestSchema, fileDigest, runtimeReportSchema, SSH_HELPER_TARGETS, verifyPayload, type ArtifactManifest } from './artifact.ts'
import { extractHelper } from '../verify-ssh-helper-artifact.ts'
const reportSchema = z.object({
manifest: artifactManifestSchema,
runtime: runtimeReportSchema,
restrictedWorker: z.boolean().optional(), expectedBackend: z.string().nullable().optional(), glibc: z.string().optional(),
readonlyInstall: z.boolean().optional(), concurrentColdStarts: z.number().optional(),
}).strict()
const coreChecks = ['executable-handshake', 'guarded-files-and-streams', 'managed-process-output', 'process-cancellation', 'native-pty', 'native-flock', 'embedded-ptc-and-deadline']
async function report(path: string, manifest: ArtifactManifest, checks: readonly string[]) {
const evidence = reportSchema.parse(JSON.parse(await readFile(path, 'utf8')))
assert.deepEqual(evidence.manifest, manifest, `Report metadata differs: ${path}`)
assert.equal(evidence.runtime.nodeVersion, manifest.nodeVersion)
assert.equal(evidence.runtime.platform, manifest.target.includes('-linux-') ? 'linux' : 'darwin')
for (const check of [...coreChecks, ...checks]) assert(evidence.runtime.checks.includes(check), `Report ${path} lacks ${check}`)
return evidence
}
/**
* Validate archives and their required native and SSH evidence, then write the release checksum list.
* @param directory - downloaded artifacts from one successful four-target workflow run.
* @param version - version selected by the release tag.
* @param commit - immutable source commit checked out by every build job.
* @returns the exact archive and checksum files permitted for upload.
*/
export async function prepareSshRelease(directory: string, version: string, commit: string): Promise<string[]> {
const temporary = await mkdtemp(join(tmpdir(), 'dsh-helper-release-'))
const archives: string[] = []
const sums: string[] = []
try {
for (const target of SSH_HELPER_TARGETS) {
const archive = join(directory, `dsh-ssh-helper-${version}-${target.slice('node24-'.length)}.tar.gz`)
const digest = await fileDigest(archive)
assert.equal(digest, (await readFile(`${archive}.sha256`, 'utf8')).split(/\s/)[0])
const destination = join(temporary, target)
await mkdir(destination)
const manifest = await verifyPayload(await extractHelper(archive, destination))
assert.equal(manifest.target, target)
assert.equal(manifest.version, version)
assert.equal(manifest.commit, commit)
assert.equal(manifest.dirty, false, 'Release artifacts must come from a clean source checkout')
const native = await report(join(directory, `${target}.json`), manifest, ['sandbox-enforcement'])
assert.equal(native.runtime.sandboxLane, 'required')
assert.equal(native.readonlyInstall, true)
assert.equal(native.concurrentColdStarts, 2)
if (target.includes('-macos-')) assert.equal(native.restrictedWorker, true)
else {
const ssh = await report(join(directory, `${target}.ssh.json`), manifest, ['target-has-no-node'])
assert.equal(ssh.glibc, 'glibc 2.28')
const landlock = await report(join(directory, `${target}.landlock.json`), manifest, ['sandbox-enforcement'])
assert.equal(landlock.expectedBackend, 'landlock-run')
assert.equal(landlock.runtime.sandboxLane, 'required')
}
archives.push(archive)
sums.push(`${digest} ${basename(archive)}`)
}
const checksums = join(directory, 'SHA256SUMS')
await writeFile(checksums, `${sums.join('\n')}\n`)
return [...archives, checksums]
} finally { await rm(temporary, { recursive: true, force: true }) }
}
if (import.meta.main) {
const { values } = parseArgs({ options: { directory: { type: 'string' }, version: { type: 'string' }, commit: { type: 'string' } } })
if (values.directory === undefined || values.version === undefined || values.commit === undefined) throw new Error('Release verification requires --directory, --version and --commit')
console.log((await prepareSshRelease(resolve(values.directory), values.version, values.commit)).join('\n'))
}
+67
View File
@@ -0,0 +1,67 @@
/** Run the embedded worker while macOS denies access to the checkout and host Node. */
import assert from 'node:assert/strict'
import { spawn } from 'node:child_process'
import { Duplex, type Readable, type Writable } from 'node:stream'
import { join } from 'node:path'
import { z } from 'zod'
import { JsonChannel } from '../../packages/ptc-runtime/ptc-runtime-node/src/channel.ts'
import { decodePtcJsonWire } from '../../packages/ptc-runtime/ptc-runtime-node/src/json-wire.ts'
import { SUBPROCESS_CONTROL_ENV, SUBPROCESS_CONTROL_FD } from '../../packages/subprocess/subprocess/src/control.ts'
/**
* Execute a PTC program with the host runtime and source files inaccessible.
* @param executable - relocated helper executable.
* @param workspace - isolated program working directory.
* @param home - empty helper home and native-resource cache.
* @param checkout - real source checkout path denied by Seatbelt.
* @param hostNode - real host Node path denied by Seatbelt.
*/
export async function probeRestrictedWorker(
executable: string, workspace: string, home: string, checkout: string, hostNode: string,
): Promise<void> {
const profile = `(version 1)(allow default)${[checkout, hostNode].map(path => `(deny file-read* (subpath ${JSON.stringify(path)}))(deny process-exec (subpath ${JSON.stringify(path)}))`).join('')}`
const child = spawn('/usr/bin/sandbox-exec', ['-p', profile, executable, '1048576'], {
cwd: workspace, env: { HOME: home, TMPDIR: home, PATH: '/usr/bin:/bin', DSH_PTC_RUNTIME_NODE: '1', [SUBPROCESS_CONTROL_ENV]: 'pipe', NODE_OPTIONS: '--disable-sigusr1' },
stdio: ['ignore', 'pipe', 'pipe', 'ignore', 'ignore', 'ignore', 'ignore', 'pipe'],
})
const completed = Promise.withResolvers<unknown>()
let diagnostic = ''
assert(child.stderr !== null && child.stdout !== null)
child.stderr.on('data', (chunk: Buffer) => { diagnostic = (diagnostic + chunk.toString()).slice(-8192) })
child.stdout.resume()
const exited = new Promise<void>((resolve, reject) => {
child.once('error', reject)
child.once('close', (code, signal) => {
if (code === 0 && signal === null) resolve()
else reject(new Error(`Restricted PTC worker exited (${code ?? signal}): ${diagnostic}`))
})
})
void exited.catch((error: unknown) => { completed.reject(error) })
const streams: ReadonlyArray<Readable | Writable | null | undefined> = child.stdio
const control = streams[SUBPROCESS_CONTROL_FD]
assert(control instanceof Duplex)
const channel = new JsonChannel(control, 1_048_576, (raw) => {
const message = z.object({ type: z.string(), value: z.unknown().optional(), error: z.unknown().optional() }).parse(raw)
if (message.type === 'ready') {
const code = `const fs=await import('node:fs/promises'); const denied=[]; for(const path of ${JSON.stringify([join(checkout, 'package.json'), hostNode])}) { try { await fs.readFile(path); denied.push('readable') } catch(error) { denied.push(error.code) } } return {executable:process.execPath,env:Object.keys(process.env),denied};`
void channel.send({ type: 'boot', data: { code, namespaces: [], maxOutputBytes: 65_536 } })
.catch((error: unknown) => { completed.reject(error) })
} else if (message.type === 'done') {
if (message.error !== undefined) completed.reject(new Error(JSON.stringify(message.error)))
else completed.resolve(decodePtcJsonWire(message.value))
channel.close()
} else completed.reject(new Error(`Unexpected restricted-worker message: ${message.type}`))
}, (error) => { completed.reject(error) })
const force = setTimeout(() => { child.kill('SIGKILL') }, 20_000)
try {
const result = z.object({ executable: z.string(), env: z.array(z.string()), denied: z.array(z.enum(['EPERM', 'EACCES'])) }).parse(await completed.promise)
assert.equal(result.executable, executable)
assert.deepEqual(result.env, [])
assert.equal(result.denied.length, 2)
await exited
} finally {
channel.close()
child.kill('SIGTERM')
try { await exited } finally { clearTimeout(force) }
}
}
+5
View File
@@ -2176,6 +2176,11 @@
"symbol": "PtcRunSandbox",
"source": "packages/ptc-runtime/ptc-runtime/src/types.ts"
},
{
"doc": "docs/subsystems/ssh.md",
"symbol": "HelperLaunch",
"source": "packages/ssh/ssh/src/index.ts"
},
{
"doc": "docs/subsystems/ssh.md",
"symbol": "Config",
@@ -52,6 +52,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/host/product-telemetry-otel': { kind: 'none', reason: 'The plugin exports explicit analytics records without contributing model context.' },
'packages/api/terminal-controller': { kind: 'none', reason: 'User-owned terminal processes and screen streams never enter model requests or Session events.' },
'packages/client/ui-sidebar-terminal': { kind: 'none', reason: 'The browser renders user terminal screens without exposing them to the model.' },
'packages/ssh/ssh-helper-runtime': { kind: 'none', reason: 'The private executable carrier registers no model-facing content; its providers and consumers own operation results.' },
'packages/ssh/ssh': { kind: 'none', reason: 'The connection owner transports private provider operations; consumers own all model-facing content.' },
'packages/ssh/fs-ssh': { kind: 'indirect', reason: 'The remote filesystem delegates model rendering to the existing filesystem consumers.' },
'packages/ssh/subprocess-ssh': { kind: 'indirect', reason: 'The remote process provider delegates model rendering to Bash, terminal, LSP and ptc-runtime consumers.' },
+10
View File
@@ -39,6 +39,16 @@ afterEach(() => {
})
describe('verifyRuntimeClosure', () => {
it('checks a private worker closure without requiring application presets', async () => {
const manifestPath = 'packages/ssh/ssh-helper-runtime/package.json'
const root = fixture({ [manifestPath]: { name: 'helper', dependencies: { '@scope/worker': 'workspace:^' } } })
workspace(root, '@scope/worker', { peerDependencies: { '@scope/peer': 'workspace:^' } })
workspace(root, '@scope/peer', {})
expect((await verifyRuntimeClosure(root, manifestPath, false)).failures).toEqual(['helper -> @scope/worker -> @scope/peer'])
writeFileSync(join(root, manifestPath), JSON.stringify({ name: 'helper', dependencies: { '@scope/worker': 'workspace:^', '@scope/peer': 'workspace:^' } }))
expect(await verifyRuntimeClosure(root, manifestPath, false)).toMatchObject({ failures: [], presetCount: 0 })
})
it('requires only plugins active for each published target', async () => {
const root = fixture({
'python/sdk-runtime/package.json': { name: 'runtime', dependencies: { '@scope/shared': 'workspace:^' } },
+6 -4
View File
@@ -42,18 +42,20 @@ export interface RuntimeClosureResult {
* Check that the runtime manifest contains every shipped-preset plugin and workspace peer.
* @param root repository root containing the runtime manifest and shipped presets.
* @param manifestPath runtime manifest path relative to {@link root}.
* @param includePresets whether this carrier includes the shipped application presets.
* @returns the discovered preset count, reachable workspace package count, and violations.
*/
export async function verifyRuntimeClosure(
root: string,
manifestPath = 'python/sdk-runtime/package.json',
includePresets = true,
): Promise<RuntimeClosureResult> {
const runtimeManifest = await loadManifest(resolve(root, manifestPath))
const runtimeName = runtimeManifest.name ?? manifestPath
const workspace = await loadWorkspacePackages(root)
const runtimeDependencies = runtimeManifest.dependencies ?? {}
const platforms = await loadJson<RuntimePlatformManifest>(resolve(root, 'python/sdk-runtime/platforms.json'))
const presetPaths = globSync(AGENT_PRESET_GLOB, { cwd: root }).sort()
const platforms = includePresets ? await loadJson<RuntimePlatformManifest>(resolve(root, 'python/sdk-runtime/platforms.json')) : {}
const presetPaths = includePresets ? globSync(AGENT_PRESET_GLOB, { cwd: root }).sort() : []
const targets = Object.keys(platforms).sort()
const parents = new Map<string, string | undefined>()
const queue: string[] = []
@@ -65,8 +67,8 @@ export async function verifyRuntimeClosure(
}
const failures: string[] = []
if (presetPaths.length === 0) failures.push(`no agent presets matched ${AGENT_PRESET_GLOB}`)
if (targets.length === 0) failures.push('python/sdk-runtime/platforms.json defines no runtime targets')
if (includePresets && presetPaths.length === 0) failures.push(`no agent presets matched ${AGENT_PRESET_GLOB}`)
if (includePresets && targets.length === 0) failures.push('python/sdk-runtime/platforms.json defines no runtime targets')
failures.push(...await missingPresetPlugins(root, runtimeDependencies, presetPaths, targets))
for (let index = 0; index < queue.length; index += 1) {
const packageName = queue[index]
+167
View File
@@ -0,0 +1,167 @@
/** Unpack and run a release archive through the actual Loader-mounted SSH consumers. */
import { spawn } from 'node:child_process'
import { chmod, mkdir, mkdtemp, readFile, readdir, realpath, rm, writeFile } from 'node:fs/promises'
import { homedir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import { parseArgs } from 'node:util'
import { extract, list } from 'tar'
import { fileDigest, runtimeReportSchema, verifyPayload, type ArtifactManifest } from './ssh-helper/artifact.ts'
import { probeRestrictedWorker } from './ssh-helper/worker-probe.ts'
import { verifyNativePayload } from './ssh-helper/native.ts'
const root = resolve(import.meta.dirname, '..')
const fixtures = join(root, 'packages/ssh/ssh-helper-runtime/tests/fixtures')
async function directoryPermissions(directory: string, mode: number): Promise<void> {
await chmod(directory, mode)
for (const entry of await readdir(directory, { withFileTypes: true })) {
if (entry.isDirectory()) await directoryPermissions(join(directory, entry.name), mode)
}
}
async function makeReadonly(directory: string, manifest: ArtifactManifest): Promise<void> {
for (const file of manifest.files) await chmod(join(directory, file.path), file.mode & ~0o222)
await chmod(join(directory, 'manifest.json'), 0o444)
await directoryPermissions(directory, 0o555)
}
/**
* Reject links and escaping paths before extracting a helper's release archive.
* @param archive - gzip tar file supplied by the build.
* @param directory - empty extraction directory owned by the caller.
* @returns the extracted payload root.
*/
export async function extractHelper(archive: string, directory: string): Promise<string> {
const entries: { path: string; type: string }[] = []
await list({ file: archive, onReadEntry: (entry) => { entries.push({ path: entry.path, type: entry.type }) } })
const seen = new Set<string>()
for (const entry of entries) {
const path = entry.path.replace(/\/$/, '')
if (!['File', 'Directory'].includes(entry.type) || path.split('/').some(part => part === '..' || part === '.' || part === '')
|| path.includes('\\') || !path.startsWith('dsh-ssh-helper') || (path !== 'dsh-ssh-helper' && !path.startsWith('dsh-ssh-helper/')) || seen.has(path)) {
throw new Error(`Invalid SSH helper archive entry: ${entry.path} (${entry.type})`)
}
seen.add(path)
}
if (!seen.has('dsh-ssh-helper/manifest.json')) throw new Error('SSH helper archive has no manifest.json')
await extract({ file: archive, cwd: directory, strict: true })
return join(directory, 'dsh-ssh-helper')
}
/**
* Run the isolated consumer process and await all output and process teardown.
* @param config - generated Loader configuration.
* @param report - output report path.
* @param sandbox - expected confinement availability on this lane.
* @param environment - isolated client environment, including any test-owned SSH wrapper.
*/
export async function runArtifactDriver(config: string, report: string, sandbox: string, environment: NodeJS.ProcessEnv): Promise<void> {
const child = spawn(process.execPath, ['--import', 'tsx/esm', join(fixtures, 'driver.ts'), config, report, sandbox], {
cwd: root, stdio: ['ignore', 'pipe', 'pipe'], env: environment,
})
let output = ''
for (const stream of [child.stdout, child.stderr]) stream.on('data', (chunk: Buffer) => {
output = (output + chunk.toString()).slice(-32_768)
})
const force = setTimeout(() => { child.kill('SIGKILL') }, 120_000)
try {
await new Promise<void>((resolve, reject) => {
child.once('error', reject)
child.once('close', (code, signal) => {
if (code === 0 && signal === null) resolve()
else reject(new Error(`SSH artifact consumer exited (${code ?? signal}):\n${output}`))
})
})
} finally { clearTimeout(force) }
}
/**
* Serialize the shared provider composition around a selected helper transport.
* @param connection - concrete Loader connection row, direct fixture or OpenSSH.
* @param workspace - target-side workspace path.
* @returns YAML consumed by the normal application boot path.
*/
export function artifactComposition(connection: { id: string; name: string; config: object }, workspace: string): string {
const rows = [
{ id: 'projections', name: '@deepseek-ai/dsh-session-projection' },
{ id: 'policy', name: '@deepseek-ai/dsh-sandbox-policy', config: { mode: 'workspace-write', workspaceRoot: workspace } },
connection,
{ id: 'fs', name: '@deepseek-ai/dsh-fs-ssh' },
{ id: 'subprocess', name: '@deepseek-ai/dsh-subprocess-ssh' },
{ id: 'sandbox', name: '@deepseek-ai/dsh-sandbox-ssh' },
]
return rows.map(row => `- ${JSON.stringify(row)}`).join('\n')
+ '\n- id: ptc\n name: "@deepseek-ai/dsh-ptc-runtime-node"\n inject: [ssh]\n config:\n launch: !!js ctx.ssh.ptcLaunch\n'
}
/**
* Verify archive integrity and all core operations after relocation outside the checkout.
* @param argv - verifier flags.
*/
export async function verifySshHelper(argv: string[]): Promise<void> {
const { values } = parseArgs({ args: argv, options: {
archive: { type: 'string' }, sandbox: { type: 'string', default: 'required' },
report: { type: 'string' },
backend: { type: 'string' },
} })
if (values.archive === undefined || !['required', 'unavailable'].includes(values.sandbox)) throw new Error('Use --archive=<file> --sandbox=required|unavailable')
if (values.backend !== undefined && !['bwrap', 'landlock-run', 'sandbox-exec'].includes(values.backend)) throw new Error('Unknown expected sandbox backend')
if (values.backend === 'landlock-run' && process.platform !== 'linux') throw new Error('Landlock verification requires Linux')
const archive = resolve(values.archive)
const checksum = (await readFile(`${archive}.sha256`, 'utf8')).split(/\s/)[0]
if (await fileDigest(archive) !== checksum) throw new Error('SSH helper archive checksum mismatch')
const temporary = await realpath(await mkdtemp(join(homedir(), '.dsh-helper-check-')))
try {
const installed = await extractHelper(archive, temporary)
const manifest = await verifyPayload(installed)
const host = `node24-${process.platform === 'darwin' ? 'macos' : process.platform}-${process.arch}`
if (manifest.target !== host) throw new Error(`Artifact ${manifest.target} cannot be validated on ${host}`)
await verifyNativePayload(installed, manifest.target)
await makeReadonly(installed, manifest)
const workspace = join(temporary, 'workspace')
const home = join(temporary, 'home')
await mkdir(workspace); await mkdir(home)
let path = '/usr/bin:/bin'
if (values.backend === 'landlock-run') {
const bin = join(temporary, 'bin')
await mkdir(bin)
await writeFile(join(bin, 'bwrap'), '#!/bin/sh\nexit 127\n')
await chmod(join(bin, 'bwrap'), 0o755)
path = `${bin}:${path}`
}
const executable = join(installed, 'dsh-ssh-helper')
const helperHash = await fileDigest(executable)
const environment = {
HOME: home, PATH: process.env.PATH, DSH_HOME: join(home, 'dsh'),
TSX_TSCONFIG_PATH: join(root, 'tsconfig.base.json'), DSH_TELEMETRY_DISABLED: '1',
...(values.backend === undefined ? {} : { DSH_EXPECT_SANDBOX_RUNNER: values.backend }),
}
const runs = await Promise.allSettled([0, 1].map(async (index) => {
const cwd = join(workspace, String(index))
await mkdir(cwd)
const config = join(temporary, `cordis-${index}.yml`)
const report = join(temporary, `report-${index}.json`)
await writeFile(config, artifactComposition({ id: 'ssh', name: join(fixtures, 'direct-helper.ts'), config: {
executable, helperHash, workspace: cwd, home, path,
} }, cwd))
await runArtifactDriver(config, report, values.sandbox, environment)
}))
const failures = runs.filter(result => result.status === 'rejected').map((result): unknown => result.reason)
if (failures.length > 0) throw new AggregateError(failures, 'Concurrent SSH artifact consumers failed')
if (process.platform === 'darwin') await probeRestrictedWorker(executable, workspace, home, await realpath(root), await realpath(process.execPath))
const evidence = { manifest, runtime: runtimeReportSchema.parse(JSON.parse(await readFile(join(temporary, 'report-0.json'), 'utf8'))),
restrictedWorker: process.platform === 'darwin', expectedBackend: values.backend ?? null,
readonlyInstall: true, concurrentColdStarts: 2 }
if (values.report !== undefined) {
const destination = resolve(values.report)
await mkdir(dirname(destination), { recursive: true })
await writeFile(destination, `${JSON.stringify(evidence, null, 2)}\n`)
}
console.log(`SSH helper ${manifest.target}: ${JSON.stringify(evidence.runtime)}`)
} finally {
await directoryPermissions(temporary, 0o755)
await rm(temporary, { recursive: true, force: true })
}
}
if (import.meta.main) await verifySshHelper(process.argv.slice(2))
+108
View File
@@ -0,0 +1,108 @@
/** Real OpenSSH acceptance against a glibc 2.28 container with no installed Node. */
import assert from 'node:assert/strict'
import { execFile, spawn } from 'node:child_process'
import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import { parseArgs, promisify } from 'node:util'
import { randomUUID } from 'node:crypto'
import { fileDigest, runtimeReportSchema, verifyPayload } from './ssh-helper/artifact.ts'
import { artifactComposition, extractHelper, runArtifactDriver } from './verify-ssh-helper-artifact.ts'
const run = promisify(execFile)
const root = resolve(import.meta.dirname, '..')
async function docker(...args: string[]): Promise<string> {
return (await run('docker', args, { timeout: 300_000, maxBuffer: 16 * 1024 * 1024 })).stdout.trim()
}
async function sshdReady(container: string): Promise<void> {
const logs = spawn('docker', ['logs', '--follow', container], { stdio: ['ignore', 'pipe', 'pipe'] })
let text = ''
const ready = Promise.withResolvers<void>()
const closed = new Promise<void>((resolve) => { logs.once('close', () => { resolve(); ready.reject(new Error(`sshd exited before readiness: ${text}`)) }) })
const read = (chunk: Buffer): void => {
text = (text + chunk.toString()).slice(-8192)
if (text.includes('Server listening on')) ready.resolve()
}
logs.stdout.on('data', read); logs.stderr.on('data', read)
logs.once('error', (error) => { ready.reject(error) })
const deadline = setTimeout(() => { ready.reject(new Error(`sshd readiness timed out: ${text}`)) }, 30_000)
try { await ready.promise }
finally { clearTimeout(deadline); logs.kill('SIGTERM'); await closed }
}
/**
* Install only a validated release into an isolated SSH server and exercise production providers.
* @param argv - archive and optional report flags.
*/
export async function verifySshTransport(argv: string[]): Promise<void> {
const { values } = parseArgs({ args: argv, options: { archive: { type: 'string' }, report: { type: 'string' } } })
if (values.archive === undefined) throw new Error('Use --archive=<Linux helper archive>')
const archive = resolve(values.archive)
assert.equal(await fileDigest(archive), (await readFile(`${archive}.sha256`, 'utf8')).split(/\s/)[0], 'Archive checksum')
const temporary = await mkdtemp(join(tmpdir(), 'dsh-helper-ssh-'))
const image = `dsh-helper-test:${randomUUID()}`
let imageBuilt = false
let container: string | undefined
try {
const installed = await extractHelper(archive, temporary)
const manifest = await verifyPayload(installed)
if (!manifest.target.includes('-linux-')) throw new Error('OpenSSH container acceptance requires a Linux artifact')
const platform = manifest.target.endsWith('-arm64') ? 'linux/arm64' : 'linux/amd64'
await docker('build', '--platform', platform, '--tag', image, join(root, 'packages/ssh/ssh-helper-runtime/tests/fixtures/sshd'))
imageBuilt = true
const identity = join(temporary, 'identity')
await run('ssh-keygen', ['-q', '-t', 'ed25519', '-N', '', '-f', identity])
container = await docker('run', '--detach', '--platform', platform, '--publish', '127.0.0.1::22',
'--mount', `type=bind,src=${installed},dst=/opt/dsh-helper,readonly`,
'--mount', `type=bind,src=${identity}.pub,dst=/fixture-key.pub,readonly`, image)
await sshdReady(container)
const published = await docker('port', container, '22/tcp')
const port = /^127\.0\.0\.1:(\d+)$/.exec(published)?.[1]
if (port === undefined) throw new Error(`Unexpected Docker port mapping: ${published}`)
const publicKey = await docker('exec', container, 'cat', '/etc/ssh/ssh_host_ed25519_key.pub')
const knownHosts = join(temporary, 'known_hosts')
await writeFile(knownHosts, `[127.0.0.1]:${port} ${publicKey}\n`, { mode: 0o600 })
const sshConfig = join(temporary, 'ssh_config')
await writeFile(sshConfig, `Host artifact\n HostName 127.0.0.1\n Port ${port}\n User dsh\n IdentityFile "${identity}"\n IdentitiesOnly yes\n UserKnownHostsFile "${knownHosts}"\n GlobalKnownHostsFile /dev/null\n`, { mode: 0o600 })
const bin = join(temporary, 'bin')
await mkdir(bin)
const quote = (value: string): string => `'${value.replaceAll("'", "'\\''")}'`
await writeFile(join(bin, 'ssh'), `#!/bin/sh\nexec /usr/bin/ssh -F ${quote(sshConfig)} "$@"\n`)
await chmod(join(bin, 'ssh'), 0o755)
const workspace = '/home/dsh/workspace'
const config = join(temporary, 'cordis.yml')
await writeFile(config, artifactComposition({ id: 'ssh', name: '@deepseek-ai/dsh-ssh', config: {
host: 'artifact', launch: { kind: 'executable' }, helper: '/opt/dsh-helper/dsh-ssh-helper',
helperHash: await fileDigest(join(installed, 'dsh-ssh-helper')), workspace,
} }, workspace))
const home = join(temporary, 'client-home')
await mkdir(home)
const report = join(temporary, 'report.json')
await runArtifactDriver(config, report, 'portable', {
HOME: home, PATH: `${bin}:${process.env.PATH ?? '/usr/bin:/bin'}`, DSH_HOME: join(home, 'dsh'),
TSX_TSCONFIG_PATH: join(root, 'tsconfig.base.json'), DSH_TELEMETRY_DISABLED: '1', DSH_VERIFY_NO_NODE: '1',
})
const evidence = { manifest, runtime: runtimeReportSchema.parse(JSON.parse(await readFile(report, 'utf8'))), glibc: await docker('exec', container, 'getconf', 'GNU_LIBC_VERSION') }
assert.equal(evidence.glibc, 'glibc 2.28')
if (values.report !== undefined) {
const destination = resolve(values.report)
await mkdir(dirname(destination), { recursive: true })
await writeFile(destination, `${JSON.stringify(evidence, null, 2)}\n`)
}
console.log(`SSH helper real SSH ${manifest.target}: ${JSON.stringify(evidence.runtime)}`)
} catch (error) {
if (container === undefined) throw error
const logs = await run('docker', ['logs', container], { maxBuffer: 1024 * 1024 })
throw new Error(`SSH container diagnostics:\n${logs.stdout}\n${logs.stderr}`, { cause: error })
} finally {
try { if (container !== undefined) await docker('rm', '--force', container) }
finally {
try { if (imageBuilt) await docker('image', 'rm', image) }
finally { await rm(temporary, { recursive: true, force: true }) }
}
}
}
if (import.meta.main) await verifySshTransport(process.argv.slice(2))
+1
View File
@@ -468,6 +468,7 @@
"@deepseek-ai/dsh-spill-local": ["./packages/spill/spill-local/src"],
"@deepseek-ai/dsh-spill-policy": ["./packages/spill/spill-policy/src"],
"@deepseek-ai/dsh-ssh": ["./packages/ssh/ssh/src"],
"@deepseek-ai/dsh-ssh-helper-runtime": ["./packages/ssh/ssh-helper-runtime/src"],
"@deepseek-ai/dsh-storage": ["./packages/storage/storage/src"],
"@deepseek-ai/dsh-storage-domain": ["./packages/storage/storage-domain/src"],
"@deepseek-ai/dsh-storage-domain/invariant": ["./packages/storage/storage-domain/src/invariant.ts"],
+1
View File
@@ -326,6 +326,7 @@
{ "path": "./packages/subprocess/subprocess" },
{ "path": "./packages/subprocess/subprocess-local" },
{ "path": "./packages/ssh/ssh" },
{ "path": "./packages/ssh/ssh-helper-runtime" },
{ "path": "./packages/ssh/fs-ssh" },
{ "path": "./packages/ssh/subprocess-ssh" },
{ "path": "./packages/ssh/sandbox-ssh" },