-
Notifications
You must be signed in to change notification settings - Fork 0
🛡️ Sentinel: [CRITICAL] Fix command injection and token leak in git clone #66
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| ## 2024-05-18 - Prevent Command Injection and Token Leaks in Git Operations | ||
| **Vulnerability:** The API route `src/pages/api/github-clone.ts` used `exec` to run `git clone`, interpolating unvalidated user inputs (`repoUrl` and `repoName`) and an authentication URL that contained a sensitive GitHub token. This exposed the system to command injection risks and risked leaking the token if the shell command failed and the resulting exception message containing the command was sent to the client. | ||
| **Learning:** Using shell execution (`exec` or `execSync`) with string concatenation for commands that include secrets or user inputs is highly dangerous. A failed command will include the raw string (including the secret) in the `error.message`. | ||
| **Prevention:** Always use `execFile` or `spawn` with an array of arguments, particularly when executing system commands. Use `--` to separate flags from positional arguments, preventing argument injection. In error handling paths, deliberately sanitize `error.message` to replace any known sensitive secrets (like tokens or passwords) with placeholders (e.g., `***`) before returning the message in an API response. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,12 @@ | ||
| import type { APIRoute } from 'astro'; | ||
| import { exec } from 'node:child_process'; | ||
| import { execFile } from 'node:child_process'; | ||
| import util from 'node:util'; | ||
| import fs from 'node:fs'; | ||
| import path from 'node:path'; | ||
| import { getReposRootResolved } from '../../lib/forge-repos'; | ||
| import { getConfig } from '../../lib/config-db'; | ||
|
|
||
| const execPromise = util.promisify(exec); | ||
| const execFileAsync = util.promisify(execFile); | ||
|
|
||
| export const POST: APIRoute = async ({ request }) => { | ||
| try { | ||
|
|
@@ -33,7 +33,8 @@ export const POST: APIRoute = async ({ request }) => { | |
| const authUrl = repoUrl.replace('https://', `https://oauth2:${githubToken}@`); | ||
|
|
||
| // Clone the repository | ||
| const { stdout, stderr } = await execPromise(`git clone ${authUrl} ${repoName}`, { cwd: reposRoot }); | ||
| // Use execFile with array arguments to prevent command injection | ||
| const { stdout, stderr } = await execFileAsync('git', ['clone', '--', authUrl, repoName], { cwd: reposRoot }); | ||
|
|
||
| // Try to auto-sync it into the database | ||
| try { | ||
|
|
@@ -53,7 +54,13 @@ export const POST: APIRoute = async ({ request }) => { | |
| }); | ||
|
|
||
| } catch (error: any) { | ||
| return new Response(JSON.stringify({ error: error.message || 'Erreur lors du clonage' }), { | ||
| const githubToken = await getConfig('githubToken', true); | ||
| // Secure error handling: prevent leaking the github token | ||
| const safeError = error.message | ||
| ? (githubToken ? error.message.replaceAll(githubToken, '***') : error.message) | ||
| : 'Erreur lors du clonage'; | ||
|
Comment on lines
+57
to
+61
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Calling Additionally, if Wrap the token retrieval and replacement in a let safeError = error.message || 'Erreur lors du clonage';
try {
const githubToken = await getConfig('githubToken', true);
if (githubToken && githubToken.trim() !== '' && error.message) {
safeError = error.message.replaceAll(githubToken, '***');
}
} catch {
// Ignore DB errors to prevent double-faulting
} |
||
|
|
||
| return new Response(JSON.stringify({ error: safeError }), { | ||
| status: 500, | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| }); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
repoNameparameter is taken directly from the user request and is not validated before being used inpath.joinand passed togit clone. This can lead to a Path Traversal vulnerability, allowing an attacker to clone repositories into arbitrary directories outside ofreposRoot(e.g., by using../inrepoName).To prevent this, validate
repoNameusing a strict alphanumeric/safe character regex (matching theisSafeRepoDirNamehelper pattern) before proceeding with the clone operation.