Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -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.
10 changes: 8 additions & 2 deletions src/pages/api/docker-health.ts
Original file line number Diff line number Diff line change
@@ -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: [] }), {
Expand Down
12 changes: 8 additions & 4 deletions src/pages/api/docker.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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));

Expand Down