feat(ssh): add POSIX relay file destination

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo-H
2026-07-16 12:42:49 -04:00
co-authored by Orca
parent 4945645e81
commit fac052b771
6 changed files with 832 additions and 26 deletions
@@ -286,6 +286,7 @@ jobs:
src/main/ssh/ssh-relay-runtime-source-tree.test.ts \
src/main/ssh/ssh-relay-runtime-source-scan.test.ts \
src/main/ssh/ssh-relay-runtime-source-stream.test.ts \
src/main/ssh/ssh-relay-runtime-posix-file-destination.test.ts \
src/main/ssh/ssh-relay-runtime-sftp-file-destination.test.ts \
src/main/ssh/ssh-relay-runtime-sftp-connection-transfer.test.ts \
src/main/ssh/ssh-relay-runtime-sftp-session.test.ts \
@@ -931,6 +932,7 @@ jobs:
src/main/ssh/ssh-relay-runtime-source-tree.test.ts `
src/main/ssh/ssh-relay-runtime-source-scan.test.ts `
src/main/ssh/ssh-relay-runtime-source-stream.test.ts `
src/main/ssh/ssh-relay-runtime-posix-file-destination.test.ts `
src/main/ssh/ssh-relay-runtime-sftp-file-destination.test.ts `
src/main/ssh/ssh-relay-runtime-sftp-connection-transfer.test.ts `
src/main/ssh/ssh-relay-runtime-sftp-session.test.ts `
@@ -164,6 +164,7 @@ describe('SSH relay runtime artifact workflow', () => {
'ssh-relay-runtime-source-tree.test.ts',
'ssh-relay-runtime-source-scan.test.ts',
'ssh-relay-runtime-source-stream.test.ts',
'ssh-relay-runtime-posix-file-destination.test.ts',
'ssh-relay-runtime-sftp-file-destination.test.ts',
'ssh-relay-runtime-sftp-connection-transfer.test.ts',
'ssh-relay-runtime-sftp-session.test.ts',
@@ -9,15 +9,21 @@ keeps commands, hashes, runner identities, timings, and failure details.
A checked box means the work has evidence in the detailed ledger. Design approval alone does not
complete a box.
Active checkpoint: **Milestone 6 / Work Package 5 bounded runtime transfer — exact full-size Linux
x64/arm64 OpenSSH SFTP is green; repeated adjacent Windows compiler-test timeout correction is in
progress, 2026-07-15, Codex implementation owner.** `E-M6-SFTP-LIVE-COMPOSITION-CI-001` records
complete exact-tree/mode/exclusive-root/cancellation/RSS proof against stock OpenSSH on native Linux
x64 and arm64. `E-M6-SFTP-LIVE-COMPOSITION-ADJACENT-CI-RED-001` records the only adjacent failure:
the unchanged real Windows C# compiler integration case exceeded Vitest's five-second default twice.
Current main already fixes that pre-existing test under reviewed commit `45a772cb4` / PR #8897 by
scoping 15 seconds only to the real compiler cases. Apply that exact test-only patch and require a
fresh exact-head computer-use pass before starting disconnected POSIX system-SSH transfer.
Active checkpoint: **Milestone 6 / Work Package 5 bounded runtime transfer — disconnected POSIX
system-SSH/no-tar per-file boundary is locally green and exact-head CI is next, 2026-07-15, Codex
implementation owner.** `E-M6-POSIX-SYSTEM-SSH-FILE-LOCAL-001` records 25/25 purpose cases, 87 focused
cases with one declared skip, 628 broad relay cases with six declared skips, 281/281 release
contracts, typecheck, full lint, formatting, max-lines, diff, protected-resolver, and no-product-
consumer gates. The command uses only the four declared POSIX primitives and cancellation is bounded
to a 250 ms graceful / 2,000 ms total ceiling. Exact-head all-six native and adjacent Actions proof
is required before the next package.
`E-M6-SFTP-LIVE-COMPOSITION-CI-001` records complete exact-tree/mode/exclusive-root/cancellation/RSS
proof against stock OpenSSH on native Linux x64 and arm64. The repeated unrelated Windows compiler
timeout is closed at exact head `4945645e8` by computer-use run `29515352845` under
`E-M6-SFTP-LIVE-COMPOSITION-ADJACENT-CI-001`. `E-M6-POSIX-SYSTEM-SSH-FILE-AUDIT-001` limits the next
slice to one injected command channel and one exclusively created manifest file with bounded chunk,
EOF, mode, cancellation, and forced-settlement contracts. No real SSH connection, remote tree,
semantic probe, live/full-size claim, product path, mode/fallback, or default behavior is included.
`E-M6-SFTP-LIVE-COMPOSITION-AUDIT-001` limits this package to one authenticated built-in
`SshConnection`, one raw SFTP channel, the existing one-to-four file transfer, captured-transport-
only force close with awaited teardown, and exact full-size Linux x64/arm64 runtime transfer against
@@ -8,8 +8,8 @@ work; keep exact commands, runner identities, hashes, metrics, and residual gaps
Date created: 2026-07-14<br>
Last updated: 2026-07-15<br>
Current phase: Milestone 6 / Work Package 5 bounded runtime transfer — **In progress — 2026-07-15, Codex implementation owner: close exact-head live-SFTP adjacent regression evidence before starting disconnected POSIX system-SSH transfer**. Keep every capability disconnected from every production/default SSH path. Real Apple/SignPath rehearsals remain an explicit late gate and are not on this package's critical path.<br>
Session checkpoint: **LIVE SFTP GREEN / REPEATED ADJACENT WINDOWS TIMEOUT DIAGNOSED — 2026-07-15, Codex implementation owner** — `E-M6-SFTP-LIVE-COMPOSITION-CI-001` proves exact full-size Linux x64/arm64 transfer through stock OpenSSH with complete tree/mode/exclusive-root/cancellation/RSS evidence. `E-M6-SFTP-LIVE-COMPOSITION-ADJACENT-CI-RED-001` records the only adjacent failure: the unchanged real Windows launcher compilation case exceeded Vitest's five-second default on both attempts. Current `main` independently fixed that exact pre-existing test in reviewed commit `45a772cb4` / PR #8897 by scoping 15 seconds only to the real compiler integration cases. Apply that exact test-only patch, rerun exact head, and do not credit Windows/POSIX system SSH, high-RTT, `MaxSessions=1`, product/settings/fallback wiring, tuple enablement, publication, or default behavior. Merging to `main` remains prohibited, SignPath is deferred, and legacy remains the production default.<br>
Current phase: Milestone 6 / Work Package 5 bounded runtime transfer — **In progress — 2026-07-15, Codex implementation owner: disconnected POSIX system-SSH/no-tar per-file command and destination contract**. Keep every capability disconnected from every production/default SSH path. Real Apple/SignPath rehearsals remain an explicit late gate and are not on this package's critical path.<br>
Session checkpoint: **POSIX NO-TAR FILE BOUNDARY LOCALLY GREEN / EXACT-HEAD CI NEXT — 2026-07-15, Codex implementation owner** — `E-M6-POSIX-SYSTEM-SSH-FILE-LOCAL-001` proves the isolated destination across 25 purpose cases, 628 broad relay cases, 281 release contracts, typecheck, lint, formatting, max-lines, diff, protected-resolver, and no-product-consumer gates. The module still accepts only an injected command channel and has no real SSH/tree/product caller, so exact-head all-six native and adjacent Actions evidence is required before the next package. Merging to `main` remains prohibited, SignPath is deferred, and legacy remains the production default.<br>
Primary design: [SSH relay GitHub Release plan](./2026-07-14-ssh-relay-github-release-plan.html)<br>
Motivating issues: [#8450](https://github.com/stablyai/orca/issues/8450), [#1693](https://github.com/stablyai/orca/issues/1693)
@@ -150,14 +150,12 @@ same change as the work it records.
- Completed package: disconnected full host-evidence composition is green locally and on every
primary native client under E-M5-HOST-EVIDENCE-COMPOSITION-LOCAL-001 and
E-M5-HOST-EVIDENCE-COMPOSITION-CI-001. It has no product caller.
- Active package: **In progress — 2026-07-15, Codex implementation owner** — audit, RED, and local
GREEN complete under E-M6-SOURCE-STREAM-AUDIT-001, E-M6-SOURCE-STREAM-LOCAL-RED-001, and
E-M6-SOURCE-STREAM-LOCAL-001. E-M6-SOURCE-STREAM-CI-RED-001 exposes a Windows-only test-oracle
mismatch; its POSIX-only correction is locally green under
E-M6-SOURCE-STREAM-WINDOWS-ORACLE-CORRECTION-LOCAL-001. Commit/push it, then require fresh
exact-head all-six native/full-size and adjacent CI. Do not import SSH, open channels, construct
remote paths, create remote staging, or add product/settings/fallback/tuple/publication/default
behavior.
- Active package: **Local implementation complete / exact-head CI pending — 2026-07-15, Codex
implementation owner** — the disconnected POSIX no-tar per-file command/destination boundary is
green under E-M6-POSIX-SYSTEM-SSH-FILE-LOCAL-001. It consumes the existing source-stream
destination interface and an injected command-channel factory only. Do not import or modify the
legacy tar/base64 directory uploader, wire `SshConnection`, create a remote tree, add a semantic
probe, or add product/settings/fallback/tuple/publication/default behavior.
- Completed Work Package 2 gate: target-native Windows source-signature reports from exact-head
artifact jobs 87267322867 and 87267322870 were independently downloaded and matched to their
identities and signing-stage reports under E-M3-WINDOWS-SOURCE-SIGNATURE-CI-001. PR Checks
@@ -17410,14 +17408,171 @@ src/main/ssh/ssh-relay-runtime-sftp-tree-transfer.test.ts` — PASS, 15/15 in 1.
overlap this package. The same workflow's Ubuntu job passes, and the independent all-six native
artifact jobs plus PR Checks and Golden E2E pass.
- Independent diagnosis: current `origin/main` commit
`45a772cb42f8525ce0189fe63a509e562c5b6928` (`test(windows): allow native launcher compilation
time`, reviewed PR #8897) changes only the two real Windows compiler integration tests and scopes a
`45a772cb42f8525ce0189fe63a509e562c5b6928`
(`test(windows): allow native launcher compilation time`, reviewed PR #8897) changes only the two
real Windows compiler integration tests and scopes a
15-second timeout to them because cold hosted `csc.exe` startup exceeds the unit default. It is
not an SFTP change and changes no production behavior. This feature branch predates that commit.
- Disposition: apply the exact reviewed test-only patch without broad timeout relaxation, then
require a fresh exact-head computer-use pass before advancing. No adjacent green evidence is
credited from either failed attempt.
### E-M6-SFTP-LIVE-COMPOSITION-ADJACENT-CI-001 — scoped upstream test budget closes exact-head adjacent gate
- Date/owner/head/run: 2026-07-15, Codex implementation owner; exact evidence/test-only commit
`4945645e81af971d8ba874d83ee5975abfd6e79d`, computer-use run
[29515352845](https://github.com/stablyai/orca/actions/runs/29515352845).
- Windows result: job
[87678882484](https://github.com/stablyai/orca/actions/runs/29515352845/job/87678882484) passes the
401 applicable tests with four declared skips, including both real Windows C# compiler integration
cases under their exact reviewed 15-second budgets. It then passes native verification, CLI and
packaged Electron builds, daemon boot, Windows daemon workspace-close repro, and Windows
computer-use E2E in 4 minutes 38 seconds. No product source or timeout outside the two compiler
cases changed.
- Control result: Ubuntu job
[87678882558](https://github.com/stablyai/orca/actions/runs/29515352845/job/87678882558) passes its
unit/native/build/daemon/Linux E2E path in 2 minutes 49 seconds. Local macOS runs passed three
applicable launcher cases with the two Windows cases declared skipped plus the eight-case relay
workflow oracle; formatting and diff gates passed before commit.
- Disposition: the repeated adjacent RED is resolved by the exact independently reviewed upstream
test-only correction. Prior live SFTP evidence remains anchored to `a72c32f63`; this commit changes
no SFTP/runtime/product code. The separately triggered all-six artifact rerun is redundant control
evidence and may continue while the next disconnected package begins.
### E-M6-SFTP-LIVE-COMPOSITION-CONTROL-CI-001 — redundant exact-head control classified
- Date/owner/head: 2026-07-15, Codex implementation owner; exact test/docs-only head
`4945645e81af971d8ba874d83ee5975abfd6e79d` after the adjacent correction above.
- Runtime artifact run: [29515352928](https://github.com/stablyai/orca/actions/runs/29515352928).
All six primary native jobs pass: Windows arm64 [87678883773](https://github.com/stablyai/orca/actions/runs/29515352928/job/87678883773),
Windows x64 [87678883819](https://github.com/stablyai/orca/actions/runs/29515352928/job/87678883819),
macOS arm64 [87678883853](https://github.com/stablyai/orca/actions/runs/29515352928/job/87678883853),
Linux arm64 [87678883887](https://github.com/stablyai/orca/actions/runs/29515352928/job/87678883887),
Linux x64 [87678883950](https://github.com/stablyai/orca/actions/runs/29515352928/job/87678883950),
and macOS x64 [87678883952](https://github.com/stablyai/orca/actions/runs/29515352928/job/87678883952).
Both Linux oldest-userland supplements pass in jobs 87680141129 and 87680141145, as does the
Windows x64 floor in job 87681071541.
- Declared residual: only Windows arm64 floor job
[87681071534](https://github.com/stablyai/orca/actions/runs/29515352928/job/87681071534) fails after
complete runtime smoke because the hosted runner reports build 26200 while the immutable contract
requires build 26100; platform and architecture pass, `osBuild` fails. This is the already recorded
runner gap, not a test-only correction or SFTP regression.
- Adjacent controls: PR Checks run
[29515352911](https://github.com/stablyai/orca/actions/runs/29515352911) passes lint, typecheck, Git
compatibility, tests, unpacked app build, and packaged CLI smoke. Golden E2E run
[29515352860](https://github.com/stablyai/orca/actions/runs/29515352860) passes both macOS job
87678882828 and Linux job 87678882845.
- Disposition: this closes classification of the redundant control run only. Live SFTP evidence
remains anchored to `a72c32f63`; no POSIX system-SSH claim or later-package evidence is inferred.
### E-M6-POSIX-SYSTEM-SSH-FILE-AUDIT-001 — isolated no-tar file channel boundary
- Date/owner/base: 2026-07-15, Codex implementation owner; clean exact base
`4945645e81af971d8ba874d83ee5975abfd6e79d` after the adjacent gate above.
- Existing-code audit: `system-ssh-file-transfer.ts` is the proven legacy/default uploader and uses
local plus remote tar on POSIX and a whole-tree JSON/base64 buffer on Windows. Those paths remain
available but violate the new runtime bootstrap contract and must not be reused, narrowed, or
modified in this slice. `ssh-relay-runtime-source-stream.ts` already owns the exact scanned source,
one 64 KiB buffer per worker, hash/size/snapshot checks, and joined destination cleanup.
- New boundary: add one purpose-named POSIX file-destination module that implements only
`SshRelayRuntimeSourceDestination`. It receives an absolute POSIX manifest path, authenticated
mode (`0644` or `0755`), the exact caller signal, and an injected command-channel factory. The
generated POSIX `sh` command uses only `umask`, `set -C`, byte-preserving `cat`, and `chmod`; it
shell-quotes the path, exclusively creates the file with mode `0600` during transfer, and applies
the authenticated final mode only after stdin EOF and successful `cat` settlement. It never uses
Node, Python, Perl, tar, base64, a checksum tool, a whole-file buffer, or a remote download.
- Lifecycle contract: one write may be active; each write resolves only after the channel accepts
the exact non-empty chunk so the source worker may safely reuse its buffer. Close sends EOF once
and awaits a successful remote exit before declaring completion. Abort is idempotent, settles any
active write, requests graceful channel termination, escalates after a short fixed grace to forced
termination, and fails closed if the bounded total settlement window expires. A later tree owner,
not this file boundary, owns removal of an incomplete proved-owned path after the channel settles.
- Layer-A proof: purpose tests must cover hostile/relative/newline/NUL paths, invalid modes, exact
shell quoting and forbidden-command absence, exclusive/restrictive/final-mode command order,
zero-byte EOF, multi-chunk buffer callback order, concurrent-operation rejection, remote nonzero
exit, pre/mid-write/mid-close cancellation, graceful and forced settlement, final timeout, cleanup
failure propagation, idempotent abort, and no write after cancellation. Pin the purpose suite once
in both native workflow command families before implementation.
- Explicit residual boundary: no real SSH process or connection, directory/tree staging, primitive
semantic probe, `MaxSessions=1`, one-versus-four policy, tar optimization, full-size/live transfer,
bundled-Node remote verification/install/publish/launch, product/Beta/settings/fallback/tuple/
publication/default behavior, Windows PowerShell path, or SignPath is included. Legacy remains the
sole production/default path.
### E-M6-POSIX-SYSTEM-SSH-FILE-LOCAL-RED-001 — missing production boundary fails as designed
- Date/owner/base: 2026-07-15, Codex implementation owner; exact uncommitted test/workflow state on
base `4945645e81af971d8ba874d83ee5975abfd6e79d` after
`E-M6-POSIX-SYSTEM-SSH-FILE-AUDIT-001`.
- Command: `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts
--maxWorkers=1 src/main/ssh/ssh-relay-runtime-posix-file-destination.test.ts
config/scripts/ssh-relay-runtime-workflow.test.mjs`.
- Result: expected RED, exit 1. The new purpose suite collects zero cases and fails solely with
`Cannot find module './ssh-relay-runtime-posix-file-destination'`; that production module does not
exist yet. The independent release-workflow oracle passes all 8/8 cases, proving the purpose suite
appears exactly once in each native command family before implementation.
- Resource evidence: Vitest duration 671 ms; `/usr/bin/time -l` reports 1.86 seconds real,
132,153,344 bytes maximum resident set size, zero swaps, and zero block input/output operations.
- Disposition: RED is causally narrow and accepted. Implement only the audited injected-channel
destination, then require purpose GREEN plus focused, broad, static, isolation, and exact-head
native workflow evidence. No live SSH, tree transfer, product path, or default-path claim follows
from this evidence.
### E-M6-POSIX-SYSTEM-SSH-FILE-LOCAL-TYPE-RED-001 — test-only type boundary corrected
- Date/owner/state: 2026-07-15, Codex implementation owner; first static pass on the uncommitted
implementation atop `4945645e81af971d8ba874d83ee5975abfd6e79d`.
- Command/result: `/usr/bin/time -l pnpm typecheck` — expected gate RED after runtime tests were
already green, exit 1 in 3.23 seconds real with 1,032,896,512-byte maximum RSS. TypeScript reports
only three purpose-test errors: the intentionally invalid numeric mode crosses the production
literal union, and two zero-argument `vi.fn` channel factories expose no command tuple to the
assertions. No production line fails typechecking.
- Correction: constrain the deliberate runtime-invalid mode at the test call boundary and declare
the two mock command/signal parameters. The 25-case purpose suite and repeated typecheck below pass
after that test-only correction. No product, transport, or runtime behavior changed.
### E-M6-POSIX-SYSTEM-SSH-FILE-LOCAL-001 — isolated file channel boundary is locally green
- Date/owner/environment/base: 2026-07-15, Codex implementation owner; local macOS arm64, Node
26.0.0, pnpm 10.24.0, uncommitted package atop exact base
`4945645e81af971d8ba874d83ee5975abfd6e79d`.
- Implementation: the 318-line purpose-named module accepts one absolute normalized POSIX manifest
path, authenticated `0644`/`0755`, exact signal, and injected channel factory. Its exact command
uses only `umask 077`, `set -C`, byte-preserving `cat`, and `chmod`; single-quote escaping is
covered. It awaits every non-empty write callback, sends EOF once, requires successful remote
settlement, and bounds cancellation to a 250 ms graceful window plus a 2,000 ms total ceiling with
force-close escalation. Cancellation never sends EOF and does not release an active retained
chunk until channel settlement or the fail-closed ceiling.
- Purpose command: `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts
--maxWorkers=1 src/main/ssh/ssh-relay-runtime-posix-file-destination.test.ts` — PASS, 25/25 in
275 ms Vitest / 1.31 seconds real, 132,743,168-byte maximum RSS, zero swaps, and zero block I/O.
Cases cover both final modes; exact/forbidden command content; hostile paths; empty/write-error/
retained chunks; concurrent operations; zero-byte EOF; nonzero remote exit; pre-open, mid-open,
mid-write, and mid-close cancellation; graceful/forced/timeout settlement; cleanup failure; and
idempotent abort/no later EOF or write.
- Focused command: purpose, source stream/scan/tree, and native-workflow oracle — PASS, five files,
87 cases plus one declared platform skip in 7.49 seconds Vitest / 8.98 seconds real with
144,556,032-byte maximum RSS. The workflow oracle passes 8/8 and pins the suite exactly once in
both POSIX and PowerShell native command families.
- Broad commands: `src/main/ssh/ssh-relay-*.test.ts` — PASS, 47 files plus four declared skipped,
628 cases plus six declared skips in 25.04 seconds Vitest / 26.50 seconds real with
244,596,736-byte maximum RSS; `config/scripts/ssh-relay-runtime-*.test.mjs` — PASS, 50 files and
281/281 cases in 22.34 seconds Vitest / 24.48 seconds real with 190,545,920-byte maximum RSS. Only
the established stale-lock diagnostic and local Node 26 module deprecation appear.
- Static/isolation commands: repeated `/usr/bin/time -l pnpm typecheck` passes in 3.32 seconds real
with 1,241,726,976-byte maximum RSS. Targeted `oxlint`, targeted `oxfmt --check`, standalone
max-lines ratchet, `git diff --check`, protected-resolver empty diff, and no non-test consumer pass.
Full `pnpm lint` passes in 18.10 seconds real with 2,052,751,360-byte maximum RSS: switch
exhaustiveness, 41 reliability gates, the 355-entry max-lines ratchet, bundled-skill verification,
9,837 localization references, locale parity, and zero-candidate localization coverage pass; only
pre-existing unrelated warnings remain. Module/test sizes are 318/325 lines with no bypass.
- Residual: this is an injected callback-channel contract, not a real SSH process, `SshConnection`
adapter, tree/root staging owner, semantic primitive probe, tar optimization, `MaxSessions=1`,
full-size/live transfer, or Windows PowerShell path. It has no non-test consumer. Remote bundled-
Node verification/install/publish/launch, product/Beta/fallback/tuple/publication/default behavior,
and SignPath remain absent; legacy remains the sole production/default path. Exact-head all-six
native and adjacent Actions evidence is required before the next bounded package.
## Accepted Gaps
No product gap is accepted merely because it appears in this list. Each entry requires explicit
@@ -17476,11 +17631,10 @@ The project is not complete until every applicable item below is checked with ev
## Next Required Action
Apply current-main commit `45a772cb4`'s exact scoped Windows compiler-test timeout patch, commit and
push this evidence checkpoint, and require a fresh exact-head computer-use pass. Then audit the next
smallest disconnected POSIX system-SSH/no-tar transfer package before changing code. Do not check
the broad Milestone 6 SFTP item yet: Windows remote, cross-client, high-RTT, `MaxSessions=1`, live
channel/handle, and fault-injection cells remain open. Do not add an Electron/startup/product
Commit and push the locally green isolated POSIX no-tar per-file boundary, then require exact-head
all-six native artifact jobs, both Linux supplements, the Windows x64 floor, PR Checks, Golden E2E,
and computer-use evidence before auditing the next system-SSH adapter/tree slice. Do not
check the broad Milestone 6 SFTP or POSIX system-SSH items yet. Do not add an Electron/startup/product
importer, per-target mode wiring, fallback, tuple enablement, release publication, or default behavior.
Keep Node upstream `.tar.xz` inputs, Windows ZIP, `ORCA_RELAY_PATH`, existing desktop required-assets
behavior, detached-signature byte encoding, Windows arm64 build 26100, macOS 13.5, Linux kernel 4.18,
@@ -0,0 +1,325 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import {
openSshRelayRuntimePosixFileDestination,
SSH_RELAY_RUNTIME_POSIX_FILE_DESTINATION_LIMITS,
type SshRelayRuntimePosixFileChannel
} from './ssh-relay-runtime-posix-file-destination'
type Callback = (error?: Error) => void
function createChannel(): {
channel: SshRelayRuntimePosixFileChannel
resolve: () => void
reject: (error: Error) => void
} {
let resolveSettled: () => void = () => {}
let rejectSettled: (error: Error) => void = () => {}
const settled = new Promise<void>((resolve, reject) => {
resolveSettled = resolve
rejectSettled = reject
})
const channel: SshRelayRuntimePosixFileChannel = {
write: vi.fn((_chunk: Buffer, callback: Callback) => callback()),
end: vi.fn(),
settled,
requestClose: vi.fn(),
forceClose: vi.fn()
}
return { channel, resolve: resolveSettled, reject: rejectSettled }
}
function openDestination(
channel: SshRelayRuntimePosixFileChannel,
options: {
remotePath?: string
mode?: number
signal?: AbortSignal
openChannel?: (command: string, signal: AbortSignal) => Promise<SshRelayRuntimePosixFileChannel>
} = {}
) {
return openSshRelayRuntimePosixFileDestination({
remotePath: options.remotePath ?? "/owned stage/bin/no'de",
mode: (options.mode ?? 0o755) as 0o644 | 0o755,
signal: options.signal ?? new AbortController().signal,
openChannel: options.openChannel ?? vi.fn(async () => channel)
})
}
afterEach(() => {
vi.useRealTimers()
})
describe('SSH relay runtime POSIX no-tar file destination', () => {
it('builds one exclusive restrictive command with exact quoting and final mode order', async () => {
const { channel, resolve } = createChannel()
const controller = new AbortController()
const openChannel = vi.fn(async (_command: string, _signal: AbortSignal) => channel)
const destination = await openDestination(channel, {
signal: controller.signal,
openChannel
})
expect(openChannel).toHaveBeenCalledWith(
"umask 077; set -C; cat > '/owned stage/bin/no'\\''de' && chmod 0755 '/owned stage/bin/no'\\''de'",
controller.signal
)
const command = openChannel.mock.calls[0]?.[0] ?? ''
for (const forbidden of ['node ', 'python', 'perl', 'tar ', 'base64', 'sha256sum', 'shasum']) {
expect(command.toLowerCase()).not.toContain(forbidden)
}
const closing = destination.close()
expect(channel.end).toHaveBeenCalledOnce()
resolve()
await closing
})
it('authenticates a non-executable final mode in the same command', async () => {
const { channel, resolve } = createChannel()
const openChannel = vi.fn(async (_command: string, _signal: AbortSignal) => channel)
const destination = await openDestination(channel, {
remotePath: '/owned-stage/目录/manifest.json',
mode: 0o644,
openChannel
})
expect(openChannel.mock.calls[0]?.[0]).toBe(
"umask 077; set -C; cat > '/owned-stage/目录/manifest.json' && chmod 0644 '/owned-stage/目录/manifest.json'"
)
const closing = destination.close()
resolve()
await closing
})
it.each([
['', 0o755],
['relative/file', 0o755],
['/', 0o755],
['/owned//file', 0o755],
['/owned/../file', 0o755],
['/owned/./file', 0o755],
['/owned/file/', 0o755],
['/owned/file\nnext', 0o755],
['/owned/file\0next', 0o755],
['/owned/file', 0o600]
])('rejects hostile path %j or mode %d before opening a channel', async (remotePath, mode) => {
const { channel } = createChannel()
const openChannel = vi.fn(async () => channel)
await expect(openDestination(channel, { remotePath, mode, openChannel })).rejects.toThrow(
/invalid/i
)
expect(openChannel).not.toHaveBeenCalled()
})
it('sends EOF once for a zero-byte file and awaits remote settlement', async () => {
const { channel, resolve } = createChannel()
const destination = await openDestination(channel)
let settled = false
const closing = destination.close().then(() => {
settled = true
})
await Promise.resolve()
expect(channel.end).toHaveBeenCalledOnce()
expect(settled).toBe(false)
resolve()
await closing
await expect(destination.close()).rejects.toThrow(/closed/i)
expect(channel.end).toHaveBeenCalledOnce()
})
it('awaits exact chunk callbacks and rejects concurrent operations', async () => {
const { channel, resolve } = createChannel()
let firstCallback: Callback | undefined
vi.mocked(channel.write).mockImplementationOnce((_chunk, callback) => {
firstCallback = callback
})
const destination = await openDestination(channel)
const first = Buffer.from('first')
let settled = false
const writing = destination.write(first).then(() => {
settled = true
})
await Promise.resolve()
expect(channel.write).toHaveBeenCalledWith(first, expect.any(Function))
expect(settled).toBe(false)
await expect(destination.write(Buffer.from('second'))).rejects.toThrow(/concurrent/i)
await expect(destination.close()).rejects.toThrow(/concurrent/i)
firstCallback?.()
await writing
await destination.write(Buffer.from('second'))
const closing = destination.close()
resolve()
await closing
})
it('rejects empty chunks and propagates channel write failures', async () => {
const { channel, resolve } = createChannel()
vi.mocked(channel.write).mockImplementationOnce((_chunk, callback) => {
callback(new Error('channel write failed'))
})
const destination = await openDestination(channel)
await expect(destination.write(Buffer.alloc(0))).rejects.toThrow(/empty/i)
await expect(destination.write(Buffer.from('payload'))).rejects.toThrow('channel write failed')
const closing = destination.close()
resolve()
await closing
})
it('propagates remote nonzero settlement on close', async () => {
const { channel, reject } = createChannel()
const destination = await openDestination(channel)
const closing = destination.close()
reject(new Error('remote cat failed (exit 1)'))
await expect(closing).rejects.toThrow('remote cat failed')
})
it('rejects pre-open cancellation without opening a channel', async () => {
const { channel } = createChannel()
const controller = new AbortController()
const reason = new Error('cancelled before open')
controller.abort(reason)
const openChannel = vi.fn(async () => channel)
await expect(openDestination(channel, { signal: controller.signal, openChannel })).rejects.toBe(
reason
)
expect(openChannel).not.toHaveBeenCalled()
})
it('settles a channel returned after mid-open cancellation', async () => {
const { channel, resolve } = createChannel()
const controller = new AbortController()
const reason = new Error('cancelled during open')
vi.mocked(channel.requestClose).mockImplementation(() => resolve())
const openChannel = vi.fn(async () => {
controller.abort(reason)
return channel
})
await expect(openDestination(channel, { signal: controller.signal, openChannel })).rejects.toBe(
reason
)
expect(channel.requestClose).toHaveBeenCalledOnce()
expect(channel.forceClose).not.toHaveBeenCalled()
})
it('holds a retained write buffer until cancellation settles the channel', async () => {
const { channel, resolve } = createChannel()
const controller = new AbortController()
const reason = new Error('cancelled during write')
vi.mocked(channel.write).mockImplementation(() => {})
vi.mocked(channel.requestClose).mockImplementation(() => resolve())
const destination = await openDestination(channel, { signal: controller.signal })
const chunk = Buffer.alloc(64 * 1024, 7)
let settled = false
const writing = destination.write(chunk).finally(() => {
settled = true
})
controller.abort(reason)
await Promise.resolve()
expect(channel.requestClose).toHaveBeenCalledOnce()
await expect(writing).rejects.toBe(reason)
expect(settled).toBe(true)
await expect(destination.abort(reason)).resolves.toBeUndefined()
await expect(destination.write(Buffer.from('later'))).rejects.toThrow(/closed/i)
expect(channel.write).toHaveBeenCalledOnce()
})
it('cancels a close only after the command channel settles', async () => {
const { channel, resolve } = createChannel()
const controller = new AbortController()
const reason = new Error('cancelled during close')
vi.mocked(channel.requestClose).mockImplementation(() => resolve())
const destination = await openDestination(channel, { signal: controller.signal })
const closing = destination.close()
controller.abort(reason)
await expect(destination.abort(reason)).resolves.toBeUndefined()
await expect(closing).rejects.toBe(reason)
expect(channel.end).toHaveBeenCalledOnce()
expect(channel.requestClose).toHaveBeenCalledOnce()
})
it('settles ordinary cancellation during the graceful window', async () => {
const { channel, resolve } = createChannel()
const controller = new AbortController()
vi.mocked(channel.requestClose).mockImplementation(() => resolve())
const destination = await openDestination(channel, { signal: controller.signal })
controller.abort(new Error('cancelled'))
await expect(destination.abort(controller.signal.reason)).resolves.toBeUndefined()
expect(channel.requestClose).toHaveBeenCalledOnce()
expect(channel.forceClose).not.toHaveBeenCalled()
})
it('forces a channel that does not settle during the graceful window', async () => {
vi.useFakeTimers()
const { channel, resolve } = createChannel()
const controller = new AbortController()
vi.mocked(channel.forceClose).mockImplementation(() => resolve())
const destination = await openDestination(channel, { signal: controller.signal })
controller.abort(new Error('cancelled'))
const aborting = destination.abort(controller.signal.reason)
await vi.advanceTimersByTimeAsync(
SSH_RELAY_RUNTIME_POSIX_FILE_DESTINATION_LIMITS.gracefulCloseMs
)
await expect(aborting).resolves.toBeUndefined()
expect(channel.requestClose).toHaveBeenCalledOnce()
expect(channel.forceClose).toHaveBeenCalledOnce()
})
it('fails closed when forced channel settlement reaches the total ceiling', async () => {
vi.useFakeTimers()
const { channel } = createChannel()
const controller = new AbortController()
const destination = await openDestination(channel, { signal: controller.signal })
controller.abort(new Error('cancelled'))
const aborting = destination.abort(controller.signal.reason)
await vi.advanceTimersByTimeAsync(SSH_RELAY_RUNTIME_POSIX_FILE_DESTINATION_LIMITS.totalCloseMs)
await expect(aborting).rejects.toThrow(/settlement timed out/i)
expect(channel.requestClose).toHaveBeenCalledOnce()
expect(channel.forceClose).toHaveBeenCalledOnce()
})
it('propagates cleanup request failures after the channel settles', async () => {
const { channel, resolve } = createChannel()
const controller = new AbortController()
vi.mocked(channel.requestClose).mockImplementation(() => {
resolve()
throw new Error('graceful close failed')
})
const destination = await openDestination(channel, { signal: controller.signal })
controller.abort(new Error('cancelled'))
await expect(destination.abort(controller.signal.reason)).rejects.toThrow(
'graceful close failed'
)
})
it('makes abort idempotent and never sends EOF after cancellation', async () => {
const { channel, resolve } = createChannel()
const controller = new AbortController()
vi.mocked(channel.requestClose).mockImplementation(() => resolve())
const destination = await openDestination(channel, { signal: controller.signal })
const reason = new Error('cancelled')
controller.abort(reason)
const first = destination.abort(reason)
const second = destination.abort(reason)
expect(first).toBe(second)
await first
expect(channel.requestClose).toHaveBeenCalledOnce()
expect(channel.end).not.toHaveBeenCalled()
await expect(destination.close()).rejects.toThrow(/closed/i)
})
})
@@ -0,0 +1,318 @@
import type { SshRelayRuntimeSourceDestination } from './ssh-relay-runtime-source-stream'
type ChannelWriteCallback = (error?: Error) => void
export type SshRelayRuntimePosixFileChannel = Readonly<{
write: (chunk: Buffer, callback: ChannelWriteCallback) => void
end: () => void
settled: Promise<void>
requestClose: () => void
forceClose: () => void
}>
export type OpenSshRelayRuntimePosixFileDestinationOptions = Readonly<{
remotePath: string
mode: 0o644 | 0o755
signal: AbortSignal
openChannel: (command: string, signal: AbortSignal) => Promise<SshRelayRuntimePosixFileChannel>
}>
export const SSH_RELAY_RUNTIME_POSIX_FILE_DESTINATION_LIMITS = Object.freeze({
gracefulCloseMs: 250,
totalCloseMs: 2_000
})
type DestinationState = 'open' | 'closing' | 'complete' | 'aborting' | 'aborted'
type ChannelSettlement = Readonly<{ ok: true } | { ok: false; error: unknown }>
type AbortOutcome = Readonly<{ reason: unknown }>
function validateRemotePath(remotePath: string): void {
const segments = remotePath.split('/')
if (
remotePath === '/' ||
!remotePath.startsWith('/') ||
remotePath.includes('\0') ||
remotePath.includes('\n') ||
remotePath.includes('\r') ||
segments.slice(1).some((segment) => segment === '' || segment === '.' || segment === '..')
) {
throw new Error('SSH relay runtime POSIX file destination path is invalid')
}
}
function validateOptions(options: OpenSshRelayRuntimePosixFileDestinationOptions): void {
if (
!options ||
typeof options.remotePath !== 'string' ||
typeof options.openChannel !== 'function' ||
!options.signal
) {
throw new Error('SSH relay runtime POSIX file destination input is invalid')
}
validateRemotePath(options.remotePath)
if (options.mode !== 0o644 && options.mode !== 0o755) {
throw new Error('SSH relay runtime POSIX file destination mode is invalid')
}
}
function validateChannel(channel: SshRelayRuntimePosixFileChannel): void {
if (
!channel ||
typeof channel.write !== 'function' ||
typeof channel.end !== 'function' ||
typeof channel.settled?.then !== 'function' ||
typeof channel.requestClose !== 'function' ||
typeof channel.forceClose !== 'function'
) {
throw new Error('SSH relay runtime POSIX file channel is invalid')
}
}
function quotePosixShellArgument(value: string): string {
return `'${value.replaceAll("'", "'\\''")}'`
}
function buildCommand(remotePath: string, mode: 0o644 | 0o755): string {
const quotedPath = quotePosixShellArgument(remotePath)
const finalMode = mode.toString(8).padStart(4, '0')
// Why: noclobber plus umask makes authenticated staging exclusive and non-readable until EOF.
return `umask 077; set -C; cat > ${quotedPath} && chmod ${finalMode} ${quotedPath}`
}
function cleanupFailure(failures: readonly unknown[]): unknown | undefined {
if (failures.length === 0) {
return undefined
}
return failures.length === 1
? failures[0]
: new AggregateError(failures, 'SSH relay runtime POSIX file channel cleanup failed')
}
function waitForSettlement(
settlement: Promise<ChannelSettlement>,
timeoutMs: number
): Promise<boolean> {
return new Promise((resolve) => {
const timeout = setTimeout(() => resolve(false), timeoutMs)
void settlement.then(() => {
clearTimeout(timeout)
resolve(true)
})
})
}
async function settleCancelledChannel(
channel: SshRelayRuntimePosixFileChannel,
settlement: Promise<ChannelSettlement>
): Promise<void> {
const failures: unknown[] = []
try {
channel.requestClose()
} catch (error) {
failures.push(error)
}
const graceful = await waitForSettlement(
settlement,
SSH_RELAY_RUNTIME_POSIX_FILE_DESTINATION_LIMITS.gracefulCloseMs
)
if (!graceful) {
try {
channel.forceClose()
} catch (error) {
failures.push(error)
}
const forced = await waitForSettlement(
settlement,
SSH_RELAY_RUNTIME_POSIX_FILE_DESTINATION_LIMITS.totalCloseMs -
SSH_RELAY_RUNTIME_POSIX_FILE_DESTINATION_LIMITS.gracefulCloseMs
)
if (!forced) {
failures.unshift(new Error('SSH relay runtime POSIX file channel settlement timed out'))
}
}
const failure = cleanupFailure(failures)
if (failure !== undefined) {
throw failure
}
}
function createDestination(
options: OpenSshRelayRuntimePosixFileDestinationOptions,
channel: SshRelayRuntimePosixFileChannel
): SshRelayRuntimeSourceDestination {
const { signal } = options
const channelSettlement: Promise<ChannelSettlement> = channel.settled.then(
() => ({ ok: true }),
(error: unknown) => ({ ok: false, error })
)
let state: DestinationState = 'open'
let activeOperation: Promise<void> | undefined
let abortPromise: Promise<void> | undefined
let abortReason: unknown
let resolveAbortOutcome: (outcome: AbortOutcome) => void = () => {}
const abortOutcome = new Promise<AbortOutcome>((resolve) => {
resolveAbortOutcome = resolve
})
const assertWritable = (): void => {
if (state !== 'open') {
throw new Error('SSH relay runtime POSIX file destination is closed')
}
if (activeOperation) {
throw new Error('SSH relay runtime POSIX file destination has a concurrent operation')
}
}
const runExclusive = (operation: () => Promise<void>): Promise<void> => {
const pending = operation()
activeOperation = pending
void pending
.finally(() => {
if (activeOperation === pending) {
activeOperation = undefined
}
})
.catch(() => {})
return pending
}
const waitForOperation = async (operation: Promise<void>): Promise<void> => {
const result = await Promise.race([
operation.then(
() => ({ kind: 'complete' as const }),
(error: unknown) => ({ kind: 'failed' as const, error })
),
abortOutcome.then((outcome) => ({ kind: 'aborted' as const, outcome }))
])
if (result.kind === 'aborted') {
throw result.outcome.reason
}
if (state === 'aborting' || state === 'aborted') {
const outcome = await abortOutcome
throw outcome.reason
}
if (result.kind === 'failed') {
throw result.error
}
}
const performAbort = async (): Promise<void> => {
const reason = abortReason
let failure: unknown
try {
await settleCancelledChannel(channel, channelSettlement)
} catch (error) {
failure = error
} finally {
state = 'aborted'
signal.removeEventListener('abort', onSignalAbort)
// Why: retained transport buffers are released only after the channel has settled or timed out.
resolveAbortOutcome({ reason })
}
if (failure !== undefined) {
throw failure
}
}
const abort = (reason: unknown): Promise<void> => {
if (abortPromise) {
return abortPromise
}
if (state === 'complete' || state === 'aborted') {
abortPromise = Promise.resolve()
return abortPromise
}
state = 'aborting'
abortReason = reason
abortPromise = performAbort()
return abortPromise
}
function onSignalAbort(): void {
void abort(signal.reason).catch(() => {})
}
const write = (chunk: Buffer): Promise<void> => {
try {
assertWritable()
} catch (error) {
return Promise.reject(error)
}
if (!Buffer.isBuffer(chunk) || chunk.length === 0) {
return Promise.reject(new Error('SSH relay runtime POSIX file destination chunk is empty'))
}
return runExclusive(async () => {
signal.throwIfAborted()
// Why: the callback is the byte-retention boundary before a source worker reuses its buffer.
const accepted = new Promise<void>((resolve, reject) => {
channel.write(chunk, (error) => (error ? reject(error) : resolve()))
})
await waitForOperation(accepted)
signal.throwIfAborted()
})
}
const close = (): Promise<void> => {
try {
assertWritable()
} catch (error) {
return Promise.reject(error)
}
state = 'closing'
return runExclusive(async () => {
signal.throwIfAborted()
channel.end()
const settled = await Promise.race([
channelSettlement,
abortOutcome.then((outcome) => ({ ok: false as const, error: outcome.reason }))
])
if (state === 'aborting' || state === 'aborted') {
const outcome = await abortOutcome
throw outcome.reason
}
if (!settled.ok) {
throw settled.error
}
signal.throwIfAborted()
state = 'complete'
signal.removeEventListener('abort', onSignalAbort)
})
}
signal.addEventListener('abort', onSignalAbort, { once: true })
if (signal.aborted) {
onSignalAbort()
}
return Object.freeze({ write, close, abort })
}
export async function openSshRelayRuntimePosixFileDestination(
options: OpenSshRelayRuntimePosixFileDestinationOptions
): Promise<SshRelayRuntimeSourceDestination> {
validateOptions(options)
options.signal.throwIfAborted()
const channel = await options.openChannel(
buildCommand(options.remotePath, options.mode),
options.signal
)
validateChannel(channel)
const destination = createDestination(options, channel)
try {
options.signal.throwIfAborted()
return destination
} catch (error) {
try {
await destination.abort(error)
} catch (cleanupError) {
throw new AggregateError(
[error, cleanupError],
'SSH relay runtime POSIX file open cancellation cleanup failed'
)
}
throw error
}
}