nicholai 4a517dfa97 Fix __name polyfill - app now loads without errors
- Added globalThis.__name polyfill in layout.tsx head using dangerouslySetInnerHTML
- Fixed wrangler.jsonc to use inline DO (removed script_name reference)
- Fixed patch-worker.js duplicate detection
- Updated todos: WebSocket still needs debugging but core app is functional
2025-10-09 14:27:03 -06:00

222 lines
5.4 KiB
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 - with PTY support for full terminal capture
app.post('/ssh/exec', async (req, res) => {
const { connectionId, command, timeout = 30000, usePTY = true } = 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)
if (usePTY) {
// Use PTY mode for full terminal emulation with ANSI codes
client.exec(command, {
pty: {
term: 'xterm-256color',
cols: 120,
rows: 40,
}
}, (err, stream) => {
if (err) {
clearTimeout(timeoutHandle)
return res.status(500).json({
success: false,
error: err.message
})
}
stream.on('data', (data: Buffer) => {
output += data.toString() // Includes ANSI codes and prompts
})
stream.on('close', (code: number) => {
clearTimeout(timeoutHandle)
res.json({
output, // Full terminal output with ANSI
exitCode: code || 0,
success: (code || 0) === 0,
duration: Date.now() % timeout,
})
})
})
} else {
// Legacy mode without PTY
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
// POST /agent/run
app.post('/agent/run', async (req, res) => {
const { runId, modelName, startLevel, endLevel, apiKey } = req.body
if (!runId || !modelName || !apiKey) {
return res.status(400).json({ error: 'Missing required parameters' })
}
try {
// Set headers for Server-Sent Events / JSONL streaming
res.setHeader('Content-Type', 'application/x-ndjson')
res.setHeader('Cache-Control', 'no-cache')
res.setHeader('Connection', 'keep-alive')
// Import and create agent
const { BanditAgent } = await import('./agent.js')
const agent = new BanditAgent({
runId,
modelName,
apiKey,
startLevel: startLevel || 0,
endLevel: endLevel || 33,
responseSender: res,
})
// Run agent (it will stream events to response)
await agent.run({
runId,
currentLevel: startLevel || 0,
targetLevel: endLevel || 33,
currentPassword: startLevel === 0 ? 'bandit0' : '',
nextPassword: null,
levelGoal: '', // Will be set by agent
status: 'planning',
retryCount: 0,
maxRetries: 3,
sshConnectionId: null,
error: null,
})
} catch (error) {
console.error('Agent run error:', error)
if (!res.headersSent) {
res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' })
}
}
})
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 + LangGraph Agent running on port ${PORT}`)
})