feat(ssh): add verified relay artifact downloader

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo-H
2026-07-15 15:40:00 -04:00
co-authored by Orca
parent 1e3356cc6f
commit ef9681d325
3 changed files with 429 additions and 0 deletions
@@ -0,0 +1,240 @@
import { createHash } from 'node:crypto'
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import nacl from 'tweetnacl'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { downloadSshRelayArtifact } from './ssh-relay-artifact-download'
import { createSshRelayArtifactTestManifest } from './ssh-relay-artifact-test-manifest'
import {
selectSshRelayArtifact,
type SshRelaySelectedArtifact
} from './ssh-relay-artifact-selector'
import {
signSshRelayArtifactManifest,
sshRelayManifestKeyId,
verifySshRelayArtifactManifest
} from './ssh-relay-manifest-signature'
const { netFetchMock } = vi.hoisted(() => ({ netFetchMock: vi.fn() }))
vi.mock('electron', () => ({ net: { fetch: netFetchMock } }))
const keyPair = nacl.sign.keyPair.fromSeed(Uint8Array.from({ length: 32 }, (_, index) => index))
const bytes = Buffer.from('verified relay runtime bytes')
const sha256 = `sha256:${createHash('sha256').update(bytes).digest('hex')}` as const
const temporaryDirectories: string[] = []
function selectedArtifact(): SshRelaySelectedArtifact {
const manifest = createSshRelayArtifactTestManifest()
manifest.tuples[0].archive.size = bytes.length
manifest.tuples[0].archive.sha256 = sha256
manifest.signatures = [signSshRelayArtifactManifest(manifest, keyPair.secretKey)]
const verified = verifySshRelayArtifactManifest(manifest, [
{ keyId: sshRelayManifestKeyId(keyPair.publicKey), publicKey: keyPair.publicKey }
])
const selected = selectSshRelayArtifact(verified, {
os: 'linux',
architecture: 'x64',
processTranslated: false,
kernelVersion: '6.8',
libc: { family: 'glibc', version: '2.39' },
libstdcxxVersion: '6.0.33',
glibcxxVersion: '3.4.33'
})
if (selected.kind !== 'selected') {
throw new Error('Expected compatible test artifact')
}
return selected
}
async function destination(name = 'runtime.partial'): Promise<string> {
const directory = await mkdtemp(join(tmpdir(), 'orca-relay-download-'))
temporaryDirectories.push(directory)
return join(directory, name)
}
function exactResponse(body: BodyInit = bytes): Response {
return new Response(body, { status: 200, headers: { 'content-length': String(bytes.length) } })
}
afterEach(async () => {
netFetchMock.mockReset()
await Promise.all(
temporaryDirectories
.splice(0)
.map((directory) => rm(directory, { recursive: true, force: true }))
)
})
describe('SSH relay artifact download', () => {
it('streams exact signed bytes into an exclusive temporary file', async () => {
const artifact = selectedArtifact()
const destinationPath = await destination()
netFetchMock.mockResolvedValueOnce(exactResponse())
await expect(downloadSshRelayArtifact({ artifact, destinationPath })).resolves.toEqual({
destinationPath,
finalUrl: artifact.archive.downloadUrl,
size: bytes.length,
sha256
})
expect(await readFile(destinationPath)).toEqual(bytes)
expect(netFetchMock).toHaveBeenCalledWith(artifact.archive.downloadUrl, {
credentials: 'omit',
headers: { Accept: 'application/octet-stream' },
redirect: 'manual',
signal: expect.any(AbortSignal)
})
})
it('follows one approved GitHub asset redirect without forwarding sensitive headers', async () => {
const artifact = selectedArtifact()
const destinationPath = await destination()
const redirectedUrl = 'https://release-assets.githubusercontent.com/example/runtime?sig=secret'
netFetchMock
.mockResolvedValueOnce(
new Response(null, { status: 302, headers: { location: redirectedUrl } })
)
.mockResolvedValueOnce(exactResponse())
await downloadSshRelayArtifact({ artifact, destinationPath })
expect(netFetchMock).toHaveBeenCalledTimes(2)
expect(netFetchMock.mock.calls[1]).toEqual([
redirectedUrl,
{
credentials: 'omit',
headers: { Accept: 'application/octet-stream' },
redirect: 'manual',
signal: expect.any(AbortSignal)
}
])
})
it.each([
['http://release-assets.githubusercontent.com/runtime', /https/i],
['https://example.com/runtime', /origin/i],
['https://user:secret@release-assets.githubusercontent.com/runtime', /credentials/i],
['https://release-assets.githubusercontent.com:444/runtime', /port/i],
[null, /location/i]
])(
'rejects an unsafe redirect location %s and removes partial output',
async (location, error) => {
const destinationPath = await destination()
netFetchMock.mockResolvedValueOnce(
new Response(null, { status: 302, headers: location ? { location } : undefined })
)
await expect(
downloadSshRelayArtifact({ artifact: selectedArtifact(), destinationPath })
).rejects.toThrow(error)
await expect(readFile(destinationPath)).rejects.toMatchObject({ code: 'ENOENT' })
}
)
it('rejects redirect chains and non-200 responses', async () => {
for (const responses of [
[new Response(null, { status: 404 })],
[new Response(null, { status: 429 })],
[new Response(null, { status: 503 })],
[
new Response(null, {
status: 302,
headers: { location: 'https://release-assets.githubusercontent.com/first' }
}),
new Response(null, {
status: 302,
headers: { location: 'https://release-assets.githubusercontent.com/second' }
})
]
]) {
const destinationPath = await destination()
netFetchMock.mockReset()
netFetchMock.mockResolvedValueOnce(responses[0])
if (responses[1]) {
netFetchMock.mockResolvedValueOnce(responses[1])
}
await expect(
downloadSshRelayArtifact({ artifact: selectedArtifact(), destinationPath })
).rejects.toThrow(/status|redirect/i)
await expect(readFile(destinationPath)).rejects.toMatchObject({ code: 'ENOENT' })
}
})
it.each([
[new Response(null, { status: 200 }), /body/i],
[new Response(bytes.subarray(1)), /size/i],
[new Response(Buffer.concat([bytes, bytes])), /size/i],
[new Response(Buffer.alloc(bytes.length, 1)), /sha-?256/i]
])('rejects malformed or unverified response bytes', async (response, error) => {
const destinationPath = await destination()
netFetchMock.mockResolvedValueOnce(response)
await expect(
downloadSshRelayArtifact({ artifact: selectedArtifact(), destinationPath })
).rejects.toThrow(error)
await expect(readFile(destinationPath)).rejects.toMatchObject({ code: 'ENOENT' })
})
it('cancels a response whose declared length disagrees with the signed manifest', async () => {
const destinationPath = await destination()
const cancelled = vi.fn()
const body = new ReadableStream<Uint8Array>({
start(stream) {
stream.enqueue(bytes)
},
cancel: cancelled
})
netFetchMock.mockResolvedValueOnce(
new Response(body, { headers: { 'content-length': String(bytes.length + 1) } })
)
await expect(
downloadSshRelayArtifact({ artifact: selectedArtifact(), destinationPath })
).rejects.toThrow(/length/i)
expect(cancelled).toHaveBeenCalledOnce()
await expect(readFile(destinationPath)).rejects.toMatchObject({ code: 'ENOENT' })
})
it('cancels a stalled body and removes partial output', async () => {
const destinationPath = await destination()
const controller = new AbortController()
const cancelled = vi.fn()
const body = new ReadableStream<Uint8Array>({
start(stream) {
stream.enqueue(bytes.subarray(0, 3))
},
cancel: cancelled
})
netFetchMock.mockResolvedValueOnce(
new Response(body, { headers: { 'content-length': String(bytes.length) } })
)
const download = downloadSshRelayArtifact({
artifact: selectedArtifact(),
destinationPath,
signal: controller.signal
})
await vi.waitFor(() => expect(netFetchMock).toHaveBeenCalledOnce())
controller.abort(new Error('cancel relay download'))
await expect(download).rejects.toThrow(/cancel relay download/i)
expect(cancelled).toHaveBeenCalledOnce()
await expect(readFile(destinationPath)).rejects.toMatchObject({ code: 'ENOENT' })
})
it('never overwrites an existing destination', async () => {
const destinationPath = await destination()
await writeFile(destinationPath, 'owner bytes')
await expect(
downloadSshRelayArtifact({ artifact: selectedArtifact(), destinationPath })
).rejects.toMatchObject({ code: 'EEXIST' })
expect(await readFile(destinationPath, 'utf8')).toBe('owner bytes')
expect(netFetchMock).not.toHaveBeenCalled()
})
})
+187
View File
@@ -0,0 +1,187 @@
import { createHash } from 'node:crypto'
import { open, rm, type FileHandle } from 'node:fs/promises'
import { net } from 'electron'
import type { SshRelaySelectedArtifact } from './ssh-relay-artifact-selector'
const DOWNLOAD_TIMEOUT_MS = 5 * 60_000
const RELEASE_ASSET_HOST = 'release-assets.githubusercontent.com'
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308])
const REQUEST_INIT = {
credentials: 'omit',
headers: { Accept: 'application/octet-stream' },
redirect: 'manual'
} as const
export type SshRelayArtifactDownloadResult = {
destinationPath: string
finalUrl: string
size: number
sha256: SshRelaySelectedArtifact['archive']['sha256']
}
async function discardResponse(response: Response): Promise<void> {
await response.body?.cancel().catch(() => {})
}
function approvedRedirect(response: Response, sourceUrl: string): string {
const location = response.headers.get('location')
if (!location) {
throw new Error('SSH relay artifact redirect is missing Location')
}
const url = new URL(location, sourceUrl)
if (url.protocol !== 'https:') {
throw new Error('SSH relay artifact redirect must use HTTPS')
}
if (url.hostname !== RELEASE_ASSET_HOST) {
throw new Error('SSH relay artifact redirect has an unapproved origin')
}
if (url.username || url.password) {
throw new Error('SSH relay artifact redirect must not contain credentials')
}
if (url.port) {
throw new Error('SSH relay artifact redirect must not use a custom port')
}
return url.href
}
async function fetchArtifactResponse(
artifact: SshRelaySelectedArtifact,
signal: AbortSignal
): Promise<{ response: Response; finalUrl: string }> {
const initialUrl = artifact.archive.downloadUrl
const initial = await net.fetch(initialUrl, { ...REQUEST_INIT, signal })
if (initial.status === 200) {
return { response: initial, finalUrl: initialUrl }
}
if (!REDIRECT_STATUSES.has(initial.status)) {
await discardResponse(initial)
throw new Error(`SSH relay artifact download failed with status ${initial.status}`)
}
let finalUrl: string
try {
finalUrl = approvedRedirect(initial, initialUrl)
} finally {
await discardResponse(initial)
}
// Why: every request uses a fresh fixed header set and omits session credentials so a signed CDN
// redirect cannot inherit authorization, cookies, or caller-controlled headers.
const redirected = await net.fetch(finalUrl, { ...REQUEST_INIT, signal })
if (REDIRECT_STATUSES.has(redirected.status)) {
await discardResponse(redirected)
throw new Error('SSH relay artifact download exceeded the approved redirect limit')
}
if (redirected.status !== 200) {
await discardResponse(redirected)
throw new Error(`SSH relay artifact download failed with status ${redirected.status}`)
}
return { response: redirected, finalUrl }
}
async function writeCompleteChunk(file: FileHandle, buffer: Buffer): Promise<void> {
let offset = 0
while (offset < buffer.length) {
const { bytesWritten } = await file.write(buffer, offset, buffer.length - offset, null)
if (bytesWritten <= 0) {
throw new Error('SSH relay artifact download could not persist response bytes')
}
offset += bytesWritten
}
}
async function streamVerifiedResponse(
response: Response,
artifact: SshRelaySelectedArtifact,
signal: AbortSignal,
file: FileHandle
): Promise<{ size: number; sha256: SshRelaySelectedArtifact['archive']['sha256'] }> {
const expected = artifact.archive
const contentLength = response.headers.get('content-length')
if (contentLength !== null) {
const parsed = Number(contentLength)
if (!Number.isSafeInteger(parsed) || parsed !== expected.size) {
await discardResponse(response)
throw new Error('SSH relay artifact Content-Length disagrees with the signed manifest')
}
}
if (!response.body) {
throw new Error('SSH relay artifact response body is missing')
}
const hash = createHash('sha256')
const reader = response.body.getReader()
let size = 0
let complete = false
const cancelRead = (): void => {
void reader.cancel(signal.reason).catch(() => {})
}
signal.addEventListener('abort', cancelRead, { once: true })
try {
while (true) {
signal.throwIfAborted()
const { done, value } = await reader.read()
signal.throwIfAborted()
if (done) {
complete = true
break
}
const buffer = Buffer.from(value)
size += buffer.length
if (size > expected.size) {
throw new Error('SSH relay artifact response exceeds the signed size')
}
hash.update(buffer)
await writeCompleteChunk(file, buffer)
}
} finally {
signal.removeEventListener('abort', cancelRead)
if (!complete) {
await reader.cancel(signal.aborted ? signal.reason : undefined).catch(() => {})
}
reader.releaseLock()
}
if (size !== expected.size) {
throw new Error('SSH relay artifact response size does not match the signed manifest')
}
const sha256 = `sha256:${hash.digest('hex')}` as SshRelaySelectedArtifact['archive']['sha256']
if (sha256 !== expected.sha256) {
throw new Error('SSH relay artifact response SHA-256 does not match the signed manifest')
}
return { size, sha256 }
}
export async function downloadSshRelayArtifact({
artifact,
destinationPath,
signal
}: {
artifact: SshRelaySelectedArtifact
destinationPath: string
signal?: AbortSignal
}): Promise<SshRelayArtifactDownloadResult> {
const effectiveSignal = signal
? AbortSignal.any([signal, AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS)])
: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS)
effectiveSignal.throwIfAborted()
// Why: this boundary owns only a new staging file; cache publication and replacement are later,
// separately gated transactions.
const file = await open(destinationPath, 'wx', 0o600)
let complete = false
try {
const { response, finalUrl } = await fetchArtifactResponse(artifact, effectiveSignal)
const verified = await streamVerifiedResponse(response, artifact, effectiveSignal, file)
await file.sync()
effectiveSignal.throwIfAborted()
complete = true
return { destinationPath, finalUrl, ...verified }
} finally {
await file.close().catch(() => {})
if (!complete) {
await rm(destinationPath, { force: true }).catch(() => {})
}
}
}
@@ -67,6 +67,8 @@ export type SshRelayArtifactSelection =
}
| { kind: 'legacy'; reason: SshRelayArtifactLegacyReason }
export type SshRelaySelectedArtifact = Extract<SshRelayArtifactSelection, { kind: 'selected' }>
function selectedArtifact(
tuple: VerifiedSshRelayRuntimeTuple,
releaseTag: string