# SSH Proxy Service for Bandit Runner

This is a standalone Node.js HTTP server that provides SSH connectivity for the Bandit Runner agent running in Cloudflare Workers.

## Why is this needed?

Cloudflare Workers have limited SSH support (no native SSH client libraries), so we use an external HTTP proxy to handle SSH connections.

## Setup

### 1. Create a new Node.js project

```bash
mkdir ssh-proxy
cd ssh-proxy
npm init -y
```

### 2. Install dependencies

```bash
npm install express ssh2 cors dotenv
npm install --save-dev @types/express @types/node typescript
```

### 3. Create `server.ts`

```typescript
import express from 'express'
import { Client } from 'ssh2'
import cors from 'cors'

const app = express()
app.use(cors())
app.use(express.json())

// Store active connections
const connections = new Map<string, Client>()

// POST /ssh/connect
app.post('/ssh/connect', async (req, res) => {
  const { host, port, username, password, testOnly } = req.body
  
  // Security: Only allow connections to Bandit server
  if (host !== 'bandit.labs.overthewire.org' || port !== 2220) {
    return res.status(403).json({ 
      success: false, 
      message: 'Only connections to bandit.labs.overthewire.org:2220 are allowed' 
    })
  }

  const client = new Client()
  const connectionId = `conn-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`

  client.on('ready', () => {
    if (testOnly) {
      client.end()
      return res.json({ 
        connectionId: null,
        success: true, 
        message: 'Password validated successfully' 
      })
    }

    connections.set(connectionId, client)
    res.json({ 
      connectionId, 
      success: true, 
      message: 'Connected successfully' 
    })
  })

  client.on('error', (err) => {
    res.status(400).json({ 
      connectionId: null,
      success: false, 
      message: `Connection failed: ${err.message}` 
    })
  })

  client.connect({
    host,
    port,
    username,
    password,
    readyTimeout: 10000,
  })
})

// POST /ssh/exec
app.post('/ssh/exec', async (req, res) => {
  const { connectionId, command, timeout = 30000 } = req.body
  const client = connections.get(connectionId)

  if (!client) {
    return res.status(404).json({ 
      success: false, 
      error: 'Connection not found' 
    })
  }

  let output = ''
  let stderr = ''
  
  const timeoutHandle = setTimeout(() => {
    res.json({
      output: output + '\n[Command timed out]',
      exitCode: 124,
      success: false,
      duration: timeout,
    })
  }, timeout)

  client.exec(command, (err, stream) => {
    if (err) {
      clearTimeout(timeoutHandle)
      return res.status(500).json({ 
        success: false, 
        error: err.message 
      })
    }

    stream.on('data', (data: Buffer) => {
      output += data.toString()
    })

    stream.stderr.on('data', (data: Buffer) => {
      stderr += data.toString()
    })

    stream.on('close', (code: number) => {
      clearTimeout(timeoutHandle)
      res.json({
        output: output || stderr,
        exitCode: code,
        success: code === 0,
        duration: Date.now() % timeout,
      })
    })
  })
})

// POST /ssh/disconnect
app.post('/ssh/disconnect', (req, res) => {
  const { connectionId } = req.body
  const client = connections.get(connectionId)

  if (client) {
    client.end()
    connections.delete(connectionId)
    res.json({ success: true, message: 'Disconnected' })
  } else {
    res.status(404).json({ success: false, message: 'Connection not found' })
  }
})

// GET /ssh/health
app.get('/ssh/health', (req, res) => {
  res.json({ 
    status: 'ok', 
    activeConnections: connections.size 
  })
})

const PORT = process.env.PORT || 3001
app.listen(PORT, () => {
  console.log(`SSH Proxy running on port ${PORT}`)
})
```

### 4. Add to `package.json`

```json
{
  "scripts": {
    "dev": "tsx watch server.ts",
    "build": "tsc",
    "start": "node dist/server.js"
  }
}
```

### 5. Run locally

```bash
npm run dev
```

### 6. Deploy (optional)

You can deploy to:
- **Fly.io** (recommended for low latency)
- **Railway**
- **Render**
- **Heroku**

Example Fly.io deployment:

```bash
fly launch
fly deploy
```

## Security Notes

- The proxy hardcodes the allowed SSH target to `bandit.labs.overthewire.org:2220`
- No other SSH connections are permitted
- Connection pooling with timeout cleanup (implement auto-cleanup after 1 hour)
- Rate limiting should be added for production

## Environment Variables

```bash
PORT=3001
MAX_CONNECTIONS=100
CONNECTION_TIMEOUT_MS=3600000  # 1 hour
```

## Testing

```bash
# Test connection
curl -X POST http://localhost:3001/ssh/connect \
  -H "Content-Type: application/json" \
  -d '{"host":"bandit.labs.overthewire.org","port":2220,"username":"bandit0","password":"bandit0"}'

# Test command execution
curl -X POST http://localhost:3001/ssh/exec \
  -H "Content-Type: application/json" \
  -d '{"connectionId":"<ID_FROM_ABOVE>","command":"ls -la"}'

# Disconnect
curl -X POST http://localhost:3001/ssh/disconnect \
  -H "Content-Type: application/json" \
  -d '{"connectionId":"<ID>"}'
```

## Next Steps

1. Build and deploy this service
2. Update `SSH_PROXY_URL` in wrangler.jsonc to point to your deployed proxy
3. Set `OPENROUTER_API_KEY` secret in Cloudflare Workers
4. Test the full integration

