Files
peardock/server/handlers/docker-cli.js
T
snxraven 0ee9b67834 Complete production roadmap: vault, peers, swarm, streams, ops
Ship remaining roadmap items: encrypted registry vault, peer invite/revoke,
Swarm/plugins behind flags, binary streams, engine create validation, deploy
rollback, schema validation, fleet/access UI, metrics, fuzz/load/soak tests,
systemd packaging, and release tooling. Mark ROADMAP fully complete.
2026-07-10 20:50:55 -04:00

90 lines
2.7 KiB
JavaScript

/**
* Restricted Docker CLI command execution.
* Set PEARDOCK_UNRESTRICTED_CLI=1 to allow broader docker CLI (still admin-gated via roles).
*/
import { spawn } from 'child_process'
import * as validation from '../utils/validation.js'
import { Pushes } from '../../shared/protocol.js'
import { Roles } from '../../shared/protocol.js'
const UNRESTRICTED =
process.env.PEARDOCK_UNRESTRICTED_CLI === '1' ||
process.env.PEARDOCK_UNRESTRICTED_CLI === 'true'
const DANGEROUS_PATTERNS = ['exec', 'run', 'rm -f', 'prune', 'system prune', 'swarm', 'plugin']
export function registerDockerCliHandlers(session) {
session.respond('dockerCommand', async (args) => {
const commandStr = validation.sanitizeString(args.data || args.command, 500)
if (!commandStr || !commandStr.startsWith('docker ')) {
throw new Error('Invalid command format')
}
if (UNRESTRICTED) {
if (session.role !== Roles.admin) {
throw new Error('Unrestricted Docker CLI requires admin role')
}
} else if (DANGEROUS_PATTERNS.some((pattern) => commandStr.includes(pattern))) {
throw new Error(
'Command not allowed for security reasons (set PEARDOCK_UNRESTRICTED_CLI=1 for admin full CLI)'
)
}
const parts = commandStr.split(' ')
const executable = parts[0]
const cmdArgs = parts.slice(1)
if (executable !== 'docker') {
throw new Error('Only docker commands are allowed')
}
const connectionId = args.connectionId
return new Promise((resolve, reject) => {
const child = spawn(executable, cmdArgs)
let settled = false
child.stdout.on('data', (data) => {
session.push(Pushes.dockerOutput, {
type: 'dockerOutput',
connectionId,
data: data.toString('base64'),
encoding: 'base64',
})
})
child.stderr.on('data', (data) => {
session.push(Pushes.dockerOutput, {
type: 'dockerOutput',
connectionId,
data: Buffer.from(`[ERROR] ${data.toString()}`).toString('base64'),
encoding: 'base64',
})
})
child.on('close', (code) => {
session.push(Pushes.dockerOutput, {
type: 'dockerOutput',
connectionId,
data: `[INFO] Command exited with code ${code}`,
})
if (!settled) {
settled = true
resolve({ success: true, exitCode: code })
}
})
child.on('error', (err) => {
if (!settled) {
settled = true
reject(err)
}
})
})
})
session.respond('dockerTerminalResize', async () => {
// No PTY for docker CLI yet — acknowledge for UI
return { success: true }
})
}