T09 · Insecure Skill Coding Practices
- Location
lib/resolve.js:62- Finding
OS Command Injection Through a User-Controlled GitHub URL
- Content
View full analysis
Vulnerability Details
File Location:
lib/resolve.js:11,lib/resolve.js:62-76
Vulnerability Type: OS command injection
Risk Level: CriticalVulnerable Code
js const GITHUB_URL_REGEX = /^https?:\/\/(?:www\.)?github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/.*)?$/i;js async function resolveGitHubUrl(url) { const match = url.match(GITHUB_URL_REGEX); if (!match) { throw new Error('Invalid GitHub URL.'); } const [, owner, repo] = match; const repoName = repo.replace(/\.git$/i, ''); const cloneDir = path.join(os.tmpdir(), `safehub-scan-${repoName}-${Date.now()}`); try { execSync(`git clone --depth 1 "${url}" "${cloneDir}"`, { stdio: 'pipe', timeout: 60000 });Technical Analysis
The scan target is supplied by the user and is validated only with a permissive regular expression. Repository path components are not restricted to GitHub-valid owner and repository characters, so they may contain shell metacharacters, quotation marks, command substitutions, or whitespace.
The complete URL is then interpolated into a string passed to
execSync. String-formexecSyncinvokes a shell, and surrounding the value with double quotation marks does not make it safe: embedded quotation marks can terminate the quoted argument, while some shell expansions remain active inside double quotes.Network access for cloning a GitHub target is necessary for the declared scanning functionality. Passing the target through a command shell is not necessary and exceeds the minimum privilege required to invoke Git.
Attack Path
- An attacker convinces a user or automation system to scan a specially constructed URL that begins with a valid GitHub prefix.
- The permissive regular expression accepts the target as a GitHub URL.
- The complete attacker-controlled value is inserted into the
git cloneshell command. - The shell interprets injected synt ...[truncated 608 chars]
- Remediation
View remediation
Remediation Suggestions
-
Eliminate shell-string execution. Invoke Git with an argument array and explicitly disable the shell, for example:
js const result = spawnSync( 'git', ['clone', '--depth', '1', '--', url, cloneDir], { stdio: 'pipe', timeout: 60000, shell: false } ); -
Parse input with
new URL()and require:- Protocol equal to
https:. - Hostname exactly equal to
github.com. - No username, password, unexpected port, query, or fragment.
- Exactly valid owner and repository path components.
- Protocol equal to
-
Apply strict GitHub-compatible allowlists to owner and repository names.
-
Construct the canonical clone URL from validated components instead of reusing the original input.
-
Add regression tests with quotation marks, semicolons, whitespace, command substitutions, encoded delimiters, and extra path components.
-
