diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 00000000..fa8a2b82 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2024-06-01 - Fix Command Injection in API endpoints using Docker logs +**Vulnerability:** The API route `src/pages/api/docker-logs.ts` constructed shell commands using unvalidated, unsanitized user input (`id` and `tail` query parameters) directly into `execSync`, creating a severe command injection vulnerability. +**Learning:** Shell-based execution (`execSync` or `exec`) implicitly runs commands within a shell environment where operators like `;` and `&&` evaluate, allowing an attacker to inject arbitrary system commands via user-supplied parameters if not strictly validated or escaped. +**Prevention:** Always use `execFile` or `execFileAsync` which execute specific binaries directly and accept an array of arguments, bypassing shell parsing. Additionally, strictly validate all query parameters (e.g., using regex `^[a-zA-Z0-9_-]+$`) to prevent flag/argument injection. diff --git a/src/pages/api/docker-logs.ts b/src/pages/api/docker-logs.ts index cb72e09e..6ed8e4f0 100644 --- a/src/pages/api/docker-logs.ts +++ b/src/pages/api/docker-logs.ts @@ -1,5 +1,8 @@ import type { APIRoute } from 'astro'; -import { execSync } from 'child_process'; +import { execFile } from 'child_process'; +import { promisify } from 'util'; + +const execFileAsync = promisify(execFile); export const GET: APIRoute = async ({ url }) => { try { @@ -21,12 +24,25 @@ export const GET: APIRoute = async ({ url }) => { }); } - // Commande Docker pour récupérer les logs - const command = `docker logs --tail ${tail} ${containerId}`; + // Validation stricte pour éviter l'injection de commandes/flags + if (!/^[a-zA-Z0-9_-]+$/.test(containerId) || !/^\d+$/.test(tail)) { + return new Response(JSON.stringify({ error: "Paramètres invalides" }), { + status: 400, + headers: { 'Content-Type': 'application/json' } + }); + } + + // Utilisation d'un tableau d'arguments pour empêcher l'injection via shell let logs = []; try { - const output = execSync(command, { stdio: ['pipe', 'pipe', 'pipe'] }).toString(); - logs = output.trim().split('\n'); + const { stdout, stderr } = await execFileAsync('docker', ['logs', '--tail', tail, containerId]); + // Docker logs output can be stdout or stderr + logs = stdout ? stdout.trim().split('\n') : []; + if (stderr && stderr.trim().length > 0 && logs.length === 0) { + logs = stderr.trim().split('\n'); + } else if (stderr && stderr.trim().length > 0) { + logs.push(...stderr.trim().split('\n')); + } } catch (err: any) { // Certains logs sortent sur stderr, checkons stderr si stdout est vide ou si erreur if (err.stderr) {