Skip to content

🛡️ Sentinel: [CRITICAL] Fix command injection and secret leak in github clone API#71

Open
bobdivx wants to merge 1 commit into
devfrom
sentinel-github-clone-injection-4931847268730697963
Open

🛡️ Sentinel: [CRITICAL] Fix command injection and secret leak in github clone API#71
bobdivx wants to merge 1 commit into
devfrom
sentinel-github-clone-injection-4931847268730697963

Conversation

@bobdivx
Copy link
Copy Markdown
Owner

@bobdivx bobdivx commented May 28, 2026

🚨 Severity: CRITICAL
💡 Vulnerability: The API endpoint github-clone.ts used exec for cloning repositories, which parses inputs via the shell, exposing the application to Command Injection vulnerabilities. Furthermore, failure cases directly exposed the error.message to the client, leading to a potential leak of the Github OAuth token embedded within the clone URL.
🎯 Impact: An attacker could execute arbitrary code on the server by crafting malicious inputs in the repoUrl or repoName payload. Additionally, token leakage compromises the Github account's security.
🔧 Fix: Replaced exec with execFile, utilizing an argument array ['clone', '--', authUrl, repoName] which isolates inputs strictly as arguments and prevents option injection via --. Intercepted the error payload in the catch block to sanitize output with .replace(/https:\/\/[^@]+@/g, 'https://***@'), preventing secret spillage. Added a journal entry to .jules/sentinel.md regarding proper process output sanitization.
Verification: Ran pnpm run check and pnpm test (vitest) to confirm functional stability. The patch uses exact replacement methodologies to ensure identical behavior with strict shell isolation.


PR created automatically by Jules for task 4931847268730697963 started by @bobdivx

…ub clone API

Co-authored-by: bobdivx <6737167+bobdivx@users.noreply.github.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
forge Ready Ready Preview, Comment May 28, 2026 5:40pm

@google-labs-jules
Copy link
Copy Markdown

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request improves security in the github-clone.ts API endpoint by replacing exec with execFile to prevent command injection, and sanitizing error messages to avoid leaking GitHub OAuth tokens. The review feedback highlights that repoName remains vulnerable to path traversal and suggests validating both repoName and repoUrl. Additionally, it is recommended to use optional chaining on the error object in the catch block to prevent secondary crashes, and to log the sanitized error for server-side observability.

Comment on lines +35 to +37
// Clone the repository securely using execFile to prevent command injection.
// We use '--' to prevent argument injection if the repo URL or name starts with '-'.
const { stdout, stderr } = await execFilePromise('git', ['clone', '--', authUrl, repoName], { cwd: reposRoot });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

While using execFile with -- prevents command and argument injection, repoName is still vulnerable to path traversal. If repoName contains path traversal sequences (e.g., ../../), it can escape the intended reposRoot directory during cloning. Furthermore, on line 25, path.join(reposRoot, repoName) is used with fs.existsSync, which allows an attacker to probe the existence of arbitrary files on the server (File Existence Disclosure).

To prevent both issues, we should validate repoName to ensure it is a safe directory name (matching the isSafeRepoDirName helper regex). Additionally, we should validate that repoUrl starts with https:// to prevent cloning local paths or using unexpected protocols.

Note: Ideally, the repoName validation should be moved to the top of the handler (right after parsing the request body) to fully protect the fs.existsSync check on line 25.

    if (!/^[a-zA-Z0-9._-]{1,128}$/.test(repoName)) {
      return new Response(JSON.stringify({ error: 'Nom de dépôt invalide' }), { status: 400 });
    }

    if (!repoUrl.startsWith('https://')) {
      return new Response(JSON.stringify({ error: 'URL de dépôt invalide (HTTPS uniquement)' }), { status: 400 });
    }

    // Clone the repository securely using execFile to prevent command injection.
    // We use '--' to prevent argument injection if the repo URL or name starts with '-'.
    const { stdout, stderr } = await execFilePromise('git', ['clone', '--', authUrl, repoName], { cwd: reposRoot });

Comment on lines +57 to 64
// 🛡️ Sentinel: Prevent GitHub token from leaking in error messages
const rawError = error.message || 'Erreur lors du clonage';
const safeError = rawError.replace(/https:\/\/[^@]+@/g, 'https://***@');

return new Response(JSON.stringify({ error: safeError }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If error is not an object (e.g., if a promise rejects with null, undefined, or a string, or if a non-Error exception is thrown), accessing error.message directly will throw a TypeError, causing a secondary crash in the catch block. Using optional chaining (error?.message) and a fallback to String(error) prevents this.

Additionally, logging the sanitized error message to console.error is highly recommended for server-side observability and troubleshooting, while ensuring sensitive tokens remain redacted.

    // 🛡️ Sentinel: Prevent GitHub token from leaking in error messages
    const rawError = error?.message || (typeof error === 'string' ? error : 'Erreur lors du clonage');
    const safeError = rawError.replace(/https:\/\/[^@]+@/g, 'https://***@');

    // Log the sanitized error for server-side observability
    console.error('[github-clone] Error:', safeError);

    return new Response(JSON.stringify({ error: safeError }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' },
    });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant