diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 00000000..ad8bd256 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2024-05-30 - Prevent Event Loop Blocking and Command Injection in API Routes +**Vulnerability:** Usage of `execSync` in API routes (e.g. `src/pages/api/docker.ts`) blocks the Node.js event loop, creating a Denial of Service (DoS) risk. Furthermore, passing unsanitized strings directly to shell execution functions exposes the application to command injection vulnerabilities. +**Learning:** Shell execution within server endpoints must always be asynchronous to avoid blocking concurrent requests. Additionally, argument arrays with safe execution methods (`execFile` / `execFileAsync`) are strictly required over shell string interpolation (`exec` / `execSync`) to prevent injection risks. +**Prevention:** Always use asynchronous execution like `execFileAsync` (promisified `execFile`) with explicit argument arrays when executing system commands in API routes or other server-side operations handling concurrent traffic. diff --git a/src/pages/api/docker-health.ts b/src/pages/api/docker-health.ts index 94251167..63112305 100644 --- a/src/pages/api/docker-health.ts +++ b/src/pages/api/docker-health.ts @@ -1,10 +1,16 @@ import type { APIRoute } from 'astro'; -import { execSync } from 'child_process'; +import { execFile } from 'node:child_process'; +import { promisify } from 'node:util'; + +const execFileAsync = promisify(execFile); export const GET: APIRoute = async () => { try { + // 🛡️ Sentinel: Use execFileAsync to prevent DoS via event loop blocking + // and argument arrays to prevent command injection. const format = '{"ID":"{{.ID}}","Names":"{{.Names}}","Image":"{{.Image}}","Status":"{{.Status}}","State":"{{.State}}","Ports":"{{.Ports}}"}'; - const output = execSync(`docker ps -a --format '${format}'`).toString().trim(); + const { stdout } = await execFileAsync('docker', ['ps', '-a', '--format', format]); + const output = stdout.trim(); if (!output) { return new Response(JSON.stringify({ containers: [] }), { diff --git a/src/pages/api/docker.ts b/src/pages/api/docker.ts index 27c06485..aebc5f38 100644 --- a/src/pages/api/docker.ts +++ b/src/pages/api/docker.ts @@ -1,5 +1,8 @@ import type { APIRoute } from 'astro'; -import { execSync } from 'child_process'; +import { execFile } from 'node:child_process'; +import { promisify } from 'node:util'; + +const execFileAsync = promisify(execFile); export const GET: APIRoute = async () => { try { @@ -15,9 +18,10 @@ export const GET: APIRoute = async () => { }); } - const command = "docker ps --format '{{json .}}'"; - const output = execSync(command).toString(); - const containers = output.trim().split('\n') + // 🛡️ Sentinel: Use execFileAsync to prevent DoS via event loop blocking + // and argument arrays to prevent command injection. + const { stdout } = await execFileAsync('docker', ['ps', '--format', '{{json .}}']); + const containers = stdout.trim().split('\n') .filter(line => line.trim() !== '') .map(line => JSON.parse(line));