From e774fa41a1ddd123649865eacb5753f8214afe1b Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2026 17:42:15 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITICAL]?= =?UTF-8?q?=20Fix=20command=20injection=20in=20docker=20logs=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: bobdivx <6737167+bobdivx@users.noreply.github.com> --- .jules/sentinel.md | 4 ++++ src/pages/api/docker-logs.ts | 26 +++++++++++++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 .jules/sentinel.md 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) {