mirror of
https://github.com/stablyai/orca.git
synced 2026-10-19 16:03:26 +00:00
feat: validate relay manifest accepted keys
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -268,6 +268,7 @@ jobs:
|
||||
src/main/ssh/ssh-relay-artifact-cache-entry.test.ts \
|
||||
src/main/ssh/ssh-relay-artifact-cache-in-use-lease.test.ts \
|
||||
src/main/ssh/ssh-relay-artifact-cache-eviction.test.ts \
|
||||
src/main/ssh/ssh-relay-manifest-accepted-keys.test.ts \
|
||||
src/main/ssh/ssh-relay-packaged-manifest.test.ts \
|
||||
src/main/ssh/ssh-relay-runtime-identity.test.ts \
|
||||
config/scripts/ssh-relay-runtime-compatibility.test.mjs \
|
||||
@@ -803,6 +804,7 @@ jobs:
|
||||
src/main/ssh/ssh-relay-artifact-cache-entry.test.ts `
|
||||
src/main/ssh/ssh-relay-artifact-cache-in-use-lease.test.ts `
|
||||
src/main/ssh/ssh-relay-artifact-cache-eviction.test.ts `
|
||||
src/main/ssh/ssh-relay-manifest-accepted-keys.test.ts `
|
||||
src/main/ssh/ssh-relay-packaged-manifest.test.ts `
|
||||
src/main/ssh/ssh-relay-runtime-identity.test.ts `
|
||||
config/scripts/ssh-relay-runtime-compatibility.test.mjs `
|
||||
|
||||
@@ -126,6 +126,7 @@ describe('SSH relay runtime artifact workflow', () => {
|
||||
'ssh-relay-artifact-cache-entry.test.ts',
|
||||
'ssh-relay-artifact-cache-in-use-lease.test.ts',
|
||||
'ssh-relay-artifact-cache-eviction.test.ts',
|
||||
'ssh-relay-manifest-accepted-keys.test.ts',
|
||||
'ssh-relay-packaged-manifest.test.ts',
|
||||
'ssh-relay-runtime-identity.test.ts'
|
||||
]) {
|
||||
|
||||
+6
-3
@@ -9,9 +9,12 @@ 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 5 / Work Package 4 desktop resolver/cache — disconnected
|
||||
official-build manifest loader proven on all six native clients; accepted-key/resource-input audit
|
||||
next, 2026-07-15, Codex implementation owner.** `E-M5-PACKAGED-MANIFEST-LOCAL-001` passes 6/6
|
||||
Active checkpoint: **Milestone 5 / Work Package 4 desktop resolver/cache — disconnected desktop
|
||||
accepted-public-key parser locally proven and wired; isolated commit/all-six native proof next,
|
||||
2026-07-15, Codex implementation owner.** `E-M5-ACCEPTED-KEYS-AUDIT-001` and its RED precede
|
||||
`E-M5-ACCEPTED-KEYS-LOCAL-001`: 4/4 focused, 19/19 trust, 304/304 non-full-size SSH relay, 12/12
|
||||
workflow/aggregate, typecheck, and lint gates pass. No production public key file or secret exists,
|
||||
and no resource or consumer is connected. The prior `E-M5-PACKAGED-MANIFEST-LOCAL-001` passes 6/6
|
||||
purpose-named tests, 300/300 non-full-size SSH relay tests, 279/279 release-script tests, typecheck,
|
||||
lint, format, max-lines, and diff gates. `E-M5-PACKAGED-MANIFEST-CI-WIRING-LOCAL-001` pins the suite
|
||||
into both native job families; exact-head run
|
||||
|
||||
+76
-1
@@ -8,7 +8,7 @@ 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 5 / Work Package 4 desktop resolver/cache — **In progress — 2026-07-15, Codex implementation owner: all-six native packaged-manifest source gate closed; audit the next disconnected accepted-key/resource-embedding prerequisite**. `E-M5-PACKAGED-MANIFEST-CI-001` proves the fixed-resource loader on Linux, macOS, and Windows, x64 and arm64, under Node 24.18.0. Keep production keys/secrets, signed embedded bytes, Electron/cache-root/downloader/SSH/settings/tuples/publication/default behavior disconnected while selecting the next purpose-named RED.<br>
|
||||
Current phase: Milestone 5 / Work Package 4 desktop resolver/cache — **In progress — 2026-07-15, Codex implementation owner: checkpoint and obtain all-six native evidence for the locally proven disconnected accepted-public-key parser**. `E-M5-ACCEPTED-KEYS-LOCAL-001` proves strict protected-aggregate document parity and both native workflow command lists locally. Keep the production key file/seed/environment, signed embedded bytes, builder/resource/consumer/cache/downloader/SSH/settings/tuples/publication/default behavior absent until exact-head native proof passes.<br>
|
||||
Session checkpoint: **In progress — 2026-07-15, Codex implementation owner** — user authority covers end-to-end PR-contained implementation, commits, pushes, CI, rehearsals, and release writes; merging to `main` remains explicitly prohibited. Commit `b79bcbd04` passes all six native source/full-size cache jobs, PR Checks, and Golden E2E. The artifact workflow is red only for the retained hosted Windows arm64 build-26200 versus required-26100 floor rejection after its primary proof. No Electron, downloader, SSH, setting, tuple, publication, or default-path consumer exists. 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)
|
||||
@@ -13933,6 +13933,81 @@ config/scripts/ssh-relay-runtime-workflow.test.mjs`.
|
||||
and immutable resource-input contract. External protected-environment/secret provisioning remains
|
||||
a separate repository-administrator gate and must not be inferred from source tests.
|
||||
|
||||
### E-M5-ACCEPTED-KEYS-AUDIT-001 — Desktop accepted-public-key document parity
|
||||
|
||||
- Date: 2026-07-15
|
||||
- Owner: Codex implementation owner
|
||||
- Source: exact pushed evidence head `6358fb466b2b2099f4c9add9b8e2f772664ec3ea`.
|
||||
- Audit: complete reads of the desktop signature/loader modules and tests, protected manifest
|
||||
workflow, aggregate command/signing handoff and tests, Electron builder config/tests, package
|
||||
release scripts, and Linux/Windows/macOS release workflow build/package boundaries; targeted `rg`
|
||||
over accepted keys, public-key encoding, `extraResources`, manifest signing, desktop build inputs,
|
||||
and release packaging.
|
||||
- Findings:
|
||||
- the protected aggregate already defines the authoritative source format: exact
|
||||
`{schemaVersion, keys}` fields, schema version 1, one to four keys, exact
|
||||
`{keyId, publicKeyBase64}` entries, canonical base64 for exactly 32 Ed25519 public-key bytes,
|
||||
derived SHA-256 key IDs, uniqueness, key-ID sorting, and a SHA-256 fingerprint over canonical
|
||||
JSON;
|
||||
- the protected workflow deliberately references an absent
|
||||
`config/ssh-relay-runtime-manifest-accepted-keys.json` and tests ENOENT so no placeholder can be
|
||||
mistaken for a reviewed trust root. Release-cut and macOS release workflows remain disconnected;
|
||||
- the desktop signature verifier accepts decoded key bytes but has no strict parser for the
|
||||
aggregate document. Adding that parser with deterministic test keys is the smallest useful
|
||||
PR-contained parity boundary before production key review or immutable resource embedding;
|
||||
- accepted keys must ultimately be compile-time desktop inputs. Loading a mutable adjacent key
|
||||
file beside the manifest would let the trust root move with untrusted bytes and is not allowed;
|
||||
- builder mapping, aggregate-to-desktop artifact download, tag checkout dependencies, byte-identical
|
||||
packaged extraction, real public key review, and protected seed/environment provisioning remain
|
||||
separate gates.
|
||||
- Selected package: pure desktop parser returning cloned/sorted accepted key bytes plus the canonical
|
||||
accepted-key document fingerprint; strict hostile-input tests and release-format parity; no file
|
||||
I/O, Electron, resource mapping, manifest, signer, secret, cache, network, SSH, mode, or consumer.
|
||||
|
||||
### E-M5-ACCEPTED-KEYS-LOCAL-RED-001 — Desktop accepted-key parser is absent
|
||||
|
||||
- Date: 2026-07-15
|
||||
- Owner: Codex implementation owner
|
||||
- Source: purpose-named `src/main/ssh/ssh-relay-manifest-accepted-keys.test.ts` atop exact pushed
|
||||
evidence head `6358fb466b2b2099f4c9add9b8e2f772664ec3ea`; production parser deliberately absent.
|
||||
- Exact command: `/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts
|
||||
--maxWorkers=1 src/main/ssh/ssh-relay-manifest-accepted-keys.test.ts`.
|
||||
- Environment: macOS 26.2 arm64 build 25C56, Node v26.0.0, pnpm 10.24.0, Vitest 4.1.5.
|
||||
- Result: required RED; one failed suite / zero collected tests in 174ms Vitest / 1.16s wall,
|
||||
131,989,504-byte maximum RSS, 96,179,600-byte peak memory footprint, and zero swaps. Import fails
|
||||
because `./ssh-relay-manifest-accepted-keys` does not exist.
|
||||
- Contracts encoded before implementation: canonical sort/fingerprint, cloned key bytes, frozen
|
||||
container/records, one-to-four bound, exact fields/schema, canonical base64/32-byte keys, derived
|
||||
identity, duplicate rejection, and input-order independence.
|
||||
- Consumer-disconnection oracle: only the purpose-named test imports the absent module; fixtures use
|
||||
deterministic test seeds. No production key file/seed/environment, resource mapping, Electron,
|
||||
cache, network, SSH, setting, tuple, publication, or default call site is added.
|
||||
|
||||
### E-M5-ACCEPTED-KEYS-LOCAL-001 — Desktop accepted-key parser and native wiring
|
||||
|
||||
- Date: 2026-07-15
|
||||
- Owner: Codex implementation owner
|
||||
- Source: working package atop exact pushed evidence head
|
||||
`6358fb466b2b2099f4c9add9b8e2f772664ec3ea`; implementation/test are
|
||||
`src/main/ssh/ssh-relay-manifest-accepted-keys.ts` and its purpose-named test.
|
||||
- Focused command/result: `/usr/bin/time -l pnpm exec vitest run --config
|
||||
config/vitest.config.ts --maxWorkers=1 src/main/ssh/ssh-relay-manifest-accepted-keys.test.ts` passes
|
||||
1 file / 4 tests in 301ms Vitest / 1.23s wall, 131,874,816-byte maximum RSS,
|
||||
96,048,504-byte peak memory footprint, and zero swaps.
|
||||
- Proven: exact schema/fields; one-to-four bound; canonical 32-byte base64; derived SHA-256 IDs;
|
||||
duplicate rejection; input-order-independent sorting; cloned bytes; frozen container/records; and
|
||||
the exact canonical JSON fingerprint used by the protected aggregate.
|
||||
- Regression commands/results: accepted-keys/signature/packaged-loader trust suites pass 19/19;
|
||||
complete `src/main/ssh/ssh-relay-*.test.ts` passes 23 files / 304 tests with three declared
|
||||
full-size skips; workflow plus aggregate-command suites pass 12/12; full `pnpm typecheck` and
|
||||
`pnpm lint` pass, including 41 reliability gates and no new max-lines bypass.
|
||||
- Native wiring: the purpose-named suite is present once in each POSIX/PowerShell command list; the
|
||||
workflow oracle requires exactly two occurrences. Exact-head all-six Node 24 execution remains
|
||||
required before this source gate closes.
|
||||
- Consumer-disconnection oracle: repository consumers remain test-only. The protected workflow still
|
||||
requires the production accepted-key file to be absent; no key/seed/environment, resource bytes,
|
||||
builder mapping, Electron/cache/network/SSH/mode/tuple/publication/default caller is added.
|
||||
|
||||
## Accepted Gaps
|
||||
|
||||
No product gap is accepted merely because it appears in this list. Each entry requires explicit
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
|
||||
import nacl from 'tweetnacl'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { parseSshRelayManifestAcceptedKeyDocument } from './ssh-relay-manifest-accepted-keys'
|
||||
import { sshRelayManifestKeyId } from './ssh-relay-manifest-signature'
|
||||
|
||||
const first = nacl.sign.keyPair.fromSeed(Uint8Array.from({ length: 32 }, (_, index) => index))
|
||||
const second = nacl.sign.keyPair.fromSeed(Uint8Array.from({ length: 32 }, (_, index) => 31 - index))
|
||||
|
||||
function record(publicKey: Uint8Array) {
|
||||
return {
|
||||
keyId: sshRelayManifestKeyId(publicKey),
|
||||
publicKeyBase64: Buffer.from(publicKey).toString('base64')
|
||||
}
|
||||
}
|
||||
|
||||
function document(keys = [record(first.publicKey)]) {
|
||||
return { schemaVersion: 1, keys }
|
||||
}
|
||||
|
||||
describe('SSH relay manifest accepted keys', () => {
|
||||
it('parses, clones, sorts, freezes, and fingerprints the canonical release document', () => {
|
||||
const input = document([record(second.publicKey), record(first.publicKey)])
|
||||
const parsed = parseSshRelayManifestAcceptedKeyDocument(input)
|
||||
const sortedRecords = [...input.keys].sort((left, right) =>
|
||||
left.keyId < right.keyId ? -1 : left.keyId > right.keyId ? 1 : 0
|
||||
)
|
||||
const canonicalBytes = Buffer.from(
|
||||
JSON.stringify({ schemaVersion: 1, keys: sortedRecords }),
|
||||
'utf8'
|
||||
)
|
||||
|
||||
expect(parsed.acceptedKeys.map(({ keyId }) => keyId)).toEqual(
|
||||
sortedRecords.map(({ keyId }) => keyId)
|
||||
)
|
||||
expect(parsed.acceptedKeys[0].publicKey).not.toBe(first.publicKey)
|
||||
expect(parsed.sha256).toBe(
|
||||
`sha256:${createHash('sha256').update(canonicalBytes).digest('hex')}`
|
||||
)
|
||||
expect(Object.isFrozen(parsed)).toBe(true)
|
||||
expect(Object.isFrozen(parsed.acceptedKeys)).toBe(true)
|
||||
expect(parsed.acceptedKeys.every(Object.isFrozen)).toBe(true)
|
||||
})
|
||||
|
||||
it('accepts one to four unique keys independent of input order', () => {
|
||||
const records = [record(first.publicKey), record(second.publicKey)]
|
||||
const forward = parseSshRelayManifestAcceptedKeyDocument(document(records))
|
||||
const reverse = parseSshRelayManifestAcceptedKeyDocument(document(records.toReversed()))
|
||||
|
||||
expect(forward.sha256).toBe(reverse.sha256)
|
||||
expect(forward.acceptedKeys.map(({ keyId }) => keyId)).toEqual(
|
||||
reverse.acceptedKeys.map(({ keyId }) => keyId)
|
||||
)
|
||||
})
|
||||
|
||||
it('rejects malformed document shape and key counts', () => {
|
||||
for (const input of [
|
||||
null,
|
||||
[],
|
||||
{},
|
||||
{ schemaVersion: 2, keys: [record(first.publicKey)] },
|
||||
{ schemaVersion: 1, keys: [], extra: true },
|
||||
{ schemaVersion: 1, keys: [] },
|
||||
{ schemaVersion: 1, keys: Array.from({ length: 5 }, () => record(first.publicKey)) }
|
||||
]) {
|
||||
expect(() => parseSshRelayManifestAcceptedKeyDocument(input)).toThrow(/accepted|key|field/i)
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects extra fields, non-canonical base64, wrong sizes, identity drift, and duplicates', () => {
|
||||
const valid = record(first.publicKey)
|
||||
for (const key of [
|
||||
{ ...valid, extra: true },
|
||||
{ ...valid, publicKeyBase64: `${valid.publicKeyBase64}\n` },
|
||||
{ ...valid, publicKeyBase64: Buffer.alloc(31).toString('base64') },
|
||||
{ ...valid, keyId: `sha256:${'0'.repeat(64)}` }
|
||||
]) {
|
||||
expect(() =>
|
||||
parseSshRelayManifestAcceptedKeyDocument({ schemaVersion: 1, keys: [key] })
|
||||
).toThrow(/accepted|key|base64|field|32/i)
|
||||
}
|
||||
expect(() =>
|
||||
parseSshRelayManifestAcceptedKeyDocument(document([valid, structuredClone(valid)]))
|
||||
).toThrow(/duplicate/i)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,99 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
|
||||
import {
|
||||
sshRelayManifestKeyId,
|
||||
type SshRelayManifestAcceptedKey
|
||||
} from './ssh-relay-manifest-signature'
|
||||
import type { SshRelayDigest } from './ssh-relay-runtime-identity'
|
||||
|
||||
const BASE64 = /^[A-Za-z0-9+/]+={0,2}$/u
|
||||
const MAXIMUM_KEYS = 4
|
||||
|
||||
type AcceptedKeyRecord = {
|
||||
keyId: string
|
||||
publicKeyBase64: string
|
||||
}
|
||||
|
||||
function assertObject(value: unknown, label: string): asserts value is Record<string, unknown> {
|
||||
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
||||
throw new Error(`SSH relay manifest ${label} must be an object`)
|
||||
}
|
||||
}
|
||||
|
||||
function assertExactFields(value: Record<string, unknown>, fields: string[], label: string): void {
|
||||
const actual = Object.keys(value).sort()
|
||||
const expected = [...fields].sort()
|
||||
if (
|
||||
actual.length !== expected.length ||
|
||||
actual.some((field, index) => field !== expected[index])
|
||||
) {
|
||||
throw new Error(`SSH relay manifest ${label} has unexpected or missing fields`)
|
||||
}
|
||||
}
|
||||
|
||||
function decodePublicKey(value: unknown): { bytes: Buffer; base64: string } {
|
||||
if (typeof value !== 'string' || !BASE64.test(value)) {
|
||||
throw new Error('SSH relay manifest accepted public key is not canonical base64')
|
||||
}
|
||||
const bytes = Buffer.from(value, 'base64')
|
||||
if (bytes.byteLength !== 32 || bytes.toString('base64') !== value) {
|
||||
throw new Error('SSH relay manifest accepted public key must contain exactly 32 bytes')
|
||||
}
|
||||
return { bytes, base64: value }
|
||||
}
|
||||
|
||||
function sha256(bytes: Uint8Array): SshRelayDigest {
|
||||
return `sha256:${createHash('sha256').update(bytes).digest('hex')}`
|
||||
}
|
||||
|
||||
export function parseSshRelayManifestAcceptedKeyDocument(input: unknown): Readonly<{
|
||||
acceptedKeys: readonly Readonly<SshRelayManifestAcceptedKey>[]
|
||||
sha256: SshRelayDigest
|
||||
}> {
|
||||
assertObject(input, 'accepted-key document')
|
||||
assertExactFields(input, ['keys', 'schemaVersion'], 'accepted-key document')
|
||||
if (
|
||||
input.schemaVersion !== 1 ||
|
||||
!Array.isArray(input.keys) ||
|
||||
input.keys.length === 0 ||
|
||||
input.keys.length > MAXIMUM_KEYS
|
||||
) {
|
||||
throw new Error('SSH relay manifest accepted keys must be a bounded schema-version-1 array')
|
||||
}
|
||||
|
||||
const seen = new Set<string>()
|
||||
const parsed = input.keys
|
||||
.map((value, index) => {
|
||||
assertObject(value, `accepted key ${index}`)
|
||||
assertExactFields(value, ['keyId', 'publicKeyBase64'], `accepted key ${index}`)
|
||||
const decoded = decodePublicKey(value.publicKeyBase64)
|
||||
const publicKey = decoded.bytes
|
||||
const keyId = sshRelayManifestKeyId(publicKey)
|
||||
if (value.keyId !== keyId) {
|
||||
throw new Error('SSH relay manifest accepted key ID disagrees with its public key bytes')
|
||||
}
|
||||
if (seen.has(keyId)) {
|
||||
throw new Error(`Duplicate accepted SSH relay manifest key: ${keyId}`)
|
||||
}
|
||||
seen.add(keyId)
|
||||
return { keyId, publicKeyBase64: decoded.base64, publicKey }
|
||||
})
|
||||
.sort((left, right) => (left.keyId < right.keyId ? -1 : left.keyId > right.keyId ? 1 : 0))
|
||||
|
||||
// Why: the protected aggregate fingerprints this exact sorted projection, so desktop build
|
||||
// evidence can prove it embedded the reviewed public-key set without trusting input order.
|
||||
const canonicalRecords: AcceptedKeyRecord[] = parsed.map(({ keyId, publicKeyBase64 }) => ({
|
||||
keyId,
|
||||
publicKeyBase64
|
||||
}))
|
||||
const canonicalBytes = Buffer.from(
|
||||
JSON.stringify({ schemaVersion: 1, keys: canonicalRecords }),
|
||||
'utf8'
|
||||
)
|
||||
const acceptedKeys = Object.freeze(
|
||||
parsed.map(({ keyId, publicKey }) => Object.freeze({ keyId, publicKey }))
|
||||
)
|
||||
return Object.freeze({ acceptedKeys, sha256: sha256(canonicalBytes) })
|
||||
}
|
||||
|
||||
export const SSH_RELAY_MANIFEST_ACCEPTED_KEY_LIMITS = Object.freeze({ maximumKeys: MAXIMUM_KEYS })
|
||||
Reference in New Issue
Block a user