fix(ssh): accept GitHub restricted-shell SSH probes (#6988) (#7659)

* fix(ssh): accept GitHub restricted-shell SSH probes (#6988)

* fix: match first stderr line for GitHub restricted-shell probe (bug-bash takeover)

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Rod Boev 2026-07-24 03:24:53 -04:00 committed by GitHub
parent c1d2c4be08
commit 6d39e49480
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 267 additions and 7 deletions

View File

@ -1099,6 +1099,228 @@ describe('SshConnection', () => {
expect(conn.canRunConcurrentExecCommands()).toBe(false)
})
it('accepts GitHub restricted-shell SSH probes with resolved user fallback', async () => {
vi.mocked(resolveWithSshG).mockResolvedValueOnce(
createResolvedConfig({ hostname: 'github.com', user: 'git' })
)
spawnSystemSshCommandMock.mockImplementation(() =>
createFailingSystemCommandChannel(1, 'Invalid command: echo ORCA-SYSTEM-SSH-OK')
)
const conn = new SshConnection(
createTarget({
configHost: 'github.com',
host: 'github.com',
username: undefined
}),
createCallbacks()
)
await conn.connect()
expect(conn.getState().status).toBe('connected')
expect(conn.usesSystemSshTransport()).toBe(true)
})
it('accepts GitHub restricted-shell SSH probes with resolved host and target username', async () => {
vi.mocked(resolveWithSshG).mockResolvedValueOnce(
createResolvedConfig({ hostname: 'github.com', user: undefined })
)
spawnSystemSshCommandMock.mockImplementation(() =>
createFailingSystemCommandChannel(1, 'Invalid command: echo ORCA-SYSTEM-SSH-OK')
)
const conn = new SshConnection(
createTarget({
configHost: 'github.com',
host: 'github.com',
username: 'git'
}),
createCallbacks()
)
await conn.connect()
expect(conn.getState().status).toBe('connected')
expect(conn.usesSystemSshTransport()).toBe(true)
})
it('accepts ssh.github.com restricted-shell SSH probes', async () => {
vi.mocked(resolveWithSshG).mockResolvedValueOnce(
createResolvedConfig({ hostname: 'ssh.github.com', user: 'git' })
)
spawnSystemSshCommandMock.mockImplementation(() =>
createFailingSystemCommandChannel(1, 'Invalid command: echo ORCA-SYSTEM-SSH-OK')
)
const conn = new SshConnection(
createTarget({
configHost: 'ssh.github.com',
host: 'ssh.github.com',
username: 'git'
}),
createCallbacks()
)
await conn.connect()
expect(conn.getState().status).toBe('connected')
expect(conn.usesSystemSshTransport()).toBe(true)
})
it('accepts GitHub restricted-shell SSH probes with the real git:// advisory transcript', async () => {
// Real 4-line stderr GitHub returns for an invalid command (issue #6988).
vi.mocked(resolveWithSshG).mockResolvedValueOnce(
createResolvedConfig({ hostname: 'github.com', user: 'git' })
)
spawnSystemSshCommandMock.mockImplementation(() =>
createFailingSystemCommandChannel(
1,
'Invalid command: echo ORCA-SYSTEM-SSH-OK\n' +
' You appear to be using ssh to clone a git:// URL.\n' +
' Make sure your core.gitProxy config option and the\n' +
' GIT_PROXY_COMMAND environment variable are NOT set.'
)
)
const conn = new SshConnection(
createTarget({
configHost: 'github.com',
host: 'github.com',
username: 'git'
}),
createCallbacks()
)
await conn.connect()
expect(conn.getState().status).toBe('connected')
expect(conn.usesSystemSshTransport()).toBe(true)
})
it('accepts GitHub restricted-shell SSH probes when OpenSSH config resolution fails', async () => {
vi.stubEnv('ORCA_SSH_FORCE_SYSTEM_TRANSPORT', '1')
vi.mocked(resolveWithSshG).mockRejectedValueOnce(new Error('ssh -G failed'))
spawnSystemSshCommandMock.mockImplementation(() =>
createFailingSystemCommandChannel(1, 'Invalid command: echo ORCA-SYSTEM-SSH-OK')
)
const conn = new SshConnection(
createTarget({
configHost: 'github.com',
host: 'github.com',
username: 'git'
}),
createCallbacks()
)
await conn.connect()
expect(conn.getState().status).toBe('connected')
expect(conn.usesSystemSshTransport()).toBe(true)
expect(conn.getSystemSshResolvedConfig()).toBeNull()
})
it('rejects non-GitHub SSH probes with GitHub invalid-command text', async () => {
vi.mocked(resolveWithSshG).mockResolvedValueOnce(
createResolvedConfig({ hostname: 'gitlab.com', user: 'git' })
)
spawnSystemSshCommandMock.mockImplementation(() =>
createFailingSystemCommandChannel(1, 'Invalid command: echo ORCA-SYSTEM-SSH-OK')
)
const conn = new SshConnection(
createTarget({
configHost: 'github.com',
host: 'github.com',
username: 'git'
}),
createCallbacks()
)
await expect(conn.connect()).rejects.toThrow('System SSH probe failed (exit 1)')
expect(conn.usesSystemSshTransport()).toBe(false)
})
it('accepts GitHub restricted-shell SSH probes when target username overrides resolved user', async () => {
vi.mocked(resolveWithSshG).mockResolvedValueOnce(
createResolvedConfig({ hostname: 'github.com', user: 'deploy' })
)
spawnSystemSshCommandMock.mockImplementation(() =>
createFailingSystemCommandChannel(1, 'Invalid command: echo ORCA-SYSTEM-SSH-OK')
)
const conn = new SshConnection(
createTarget({
configHost: 'github.com',
host: 'github.com',
username: 'git'
}),
createCallbacks()
)
await conn.connect()
expect(conn.getState().status).toBe('connected')
expect(conn.usesSystemSshTransport()).toBe(true)
})
it('rejects GitHub restricted-shell SSH probes when target username overrides resolved git user', async () => {
vi.mocked(resolveWithSshG).mockResolvedValueOnce(
createResolvedConfig({ hostname: 'github.com', user: 'git' })
)
spawnSystemSshCommandMock.mockImplementation(() =>
createFailingSystemCommandChannel(1, 'Invalid command: echo ORCA-SYSTEM-SSH-OK')
)
const conn = new SshConnection(
createTarget({
configHost: 'github.com',
host: 'github.com',
username: 'deploy'
}),
createCallbacks()
)
await expect(conn.connect()).rejects.toThrow('System SSH probe failed (exit 1)')
expect(conn.usesSystemSshTransport()).toBe(false)
})
it('rejects GitHub restricted-shell SSH probes with extra stderr text', async () => {
vi.mocked(resolveWithSshG).mockResolvedValueOnce(
createResolvedConfig({ hostname: 'github.com', user: 'git' })
)
spawnSystemSshCommandMock.mockImplementation(() =>
createFailingSystemCommandChannel(
1,
'remote: rejected\nInvalid command: echo ORCA-SYSTEM-SSH-OK\ntry again'
)
)
const conn = new SshConnection(
createTarget({
configHost: 'github.com',
host: 'github.com',
username: 'git'
}),
createCallbacks()
)
await expect(conn.connect()).rejects.toThrow('System SSH probe failed (exit 1)')
expect(conn.usesSystemSshTransport()).toBe(false)
})
it('rejects GitHub restricted-shell SSH probes for non-git users', async () => {
vi.mocked(resolveWithSshG).mockResolvedValueOnce(
createResolvedConfig({ hostname: 'github.com', user: 'deploy' })
)
spawnSystemSshCommandMock.mockImplementation(() =>
createFailingSystemCommandChannel(1, 'Invalid command: echo ORCA-SYSTEM-SSH-OK')
)
const conn = new SshConnection(
createTarget({
configHost: 'github.com',
host: 'github.com',
username: 'deploy'
}),
createCallbacks()
)
await expect(conn.connect()).rejects.toThrow('System SSH probe failed (exit 1)')
expect(conn.usesSystemSshTransport()).toBe(false)
})
it('retries a failed system SSH probe without ControlMaster and disables mux for the session', async () => {
getOrcaControlSocketPathMock.mockImplementation(
(_target: SshTarget, options?: { disableControlMaster?: boolean }) =>

View File

@ -66,6 +66,36 @@ function cloneResolvedConfig(config: SshResolvedConfig | null): SshResolvedConfi
return { ...config, identityFile: [...config.identityFile] }
}
function isGitHubRestrictedShellProbeSuccess(
target: SshTarget,
resolvedConfig: SshResolvedConfig | null,
code: number | null,
stderr: string
): boolean {
if (code !== 1) {
return false
}
const effectiveUser = (target.username?.trim() || resolvedConfig?.user?.trim())?.toLowerCase()
if (effectiveUser !== 'git') {
return false
}
// GitHub appends git:// advisory lines after the invalid-command line (issue #6988), so match the first line only.
const firstLine = stderr.split('\n', 1)[0]?.trim()
if (firstLine !== 'Invalid command: echo ORCA-SYSTEM-SSH-OK') {
return false
}
const resolvedHost = resolvedConfig?.hostname?.trim()
const hostCandidates = resolvedHost ? [resolvedHost] : [target.host, target.configHost]
return hostCandidates.some((host) => {
const normalizedHost = host?.trim().toLowerCase()
return normalizedHost === 'github.com' || normalizedHost === 'ssh.github.com'
})
}
export class SshConnection {
private client: SshClient | null = null
private proxyProcess: ChildProcess | null = null
@ -847,16 +877,24 @@ export class SshConnection {
reject(new Error('SSH connection attempt was cancelled'))
return
}
if (code !== 0 || !stdout.includes('ORCA-SYSTEM-SSH-OK')) {
reject(
new Error(
`System SSH probe failed${code != null ? ` (exit ${code})` : ''}.${stderr ? ` stderr: ${stderr.trim()}` : ''}`
)
if (
(code === 0 && stdout.includes('ORCA-SYSTEM-SSH-OK')) ||
isGitHubRestrictedShellProbeSuccess(
this.target,
this.systemSshResolvedConfig,
code,
stderr
)
) {
this.setState('connected')
resolve()
return
}
this.setState('connected')
resolve()
reject(
new Error(
`System SSH probe failed${code != null ? ` (exit ${code})` : ''}.${stderr ? ` stderr: ${stderr.trim()}` : ''}`
)
)
})
}
const timeout = setTimeout(() => {