Skip to content

security(commit-repo): replace execSync tar with node-tar#799

Merged
BYK merged 1 commit into
masterfrom
security/commit-repo-node-tar
Apr 21, 2026
Merged

security(commit-repo): replace execSync tar with node-tar#799
BYK merged 1 commit into
masterfrom
security/commit-repo-node-tar

Conversation

@BYK
Copy link
Copy Markdown
Member

@BYK BYK commented Apr 21, 2026

Summary

Replaces the shell-interpolated execSync tar invocation in pushArchiveToGitRepository() with the tar npm package (already a dep). Closes a latent shell-injection hazard and makes the unpack step robust against adversarial artifact paths.

The hazard

src/targets/commitOnGitRepository.ts used to unpack the downloaded tarball with:

childProcess.execSync(`tar -zxvf ${archivePath}${stripComponentsArg}`, {
  cwd: directory,
});

archivePath comes from this.artifactProvider.downloadArtifact(...) so today it's Craft-constructed and not directly attacker-controllable. But the pattern is fragile: any future artifact provider that returns a path containing ;, $(), backticks, spaces, or | becomes a silent arbitrary-command-execution vector during release. stripComponents is zod-validated as a number so that vector is blocked, but the archive path is not so constrained.

The fix

Use tar.x({ file, cwd, gzip, strip }) from the tar npm package. It takes the path as a function parameter with no shell parsing at any layer, so even a path like /tmp/evil;touch /tmp/pwned.tgz is treated as a literal filename.

tar 7.x is already a dep (package.json); src/utils/system.ts already uses tar.extract elsewhere.

Tests

src/targets/__tests__/commitOnGitRepository.test.ts:

  • Existing "basic functionality" test updated to assert on the tar.x mock instead of the old execSync spy.
  • New test "No strip-components when not configured" covers the strip: 0 path.
  • New regression test "Shell-metacharacter archivePath does not reach a shell" plants a path like /tmp/evil;touch /tmp/CRAFT_INJECTION.tgz and asserts tar.x received it literally. This would have been a shell injection under the old code.

All 4 tests pass; lint / build clean.

pushArchiveToGitRepository() previously unpacked the downloaded
tarball by shelling out to

    execSync(`tar -zxvf ${archivePath}${stripComponentsArg}`)

with archivePath interpolated directly into the shell command
string. archivePath is currently craft-constructed so the injection
isn't exploitable today, but the pattern is fragile against future
refactors — any artifact provider that returned a path containing
`;`, `$()`, backticks, or a space would become a command-injection
vector, and the failure mode would be silent arbitrary code
execution during release.

Switch to the `tar` npm package (already a dependency; used
elsewhere in src/utils/system.ts). `tar.x({ file, cwd, gzip,
strip })` takes the path as a parameter with no shell parsing at any
layer, so even adversarial input is treated as a literal filename.

Tests:
- Existing basic-functionality test updated to assert on the
  tar.x mock instead of execSync.
- New regression test that passes an archivePath containing `;` and
  verifies it reaches tar.x as a literal string (would have been a
  shell injection vector under the old code).
@BYK BYK merged commit 9036325 into master Apr 21, 2026
16 checks passed
@BYK BYK deleted the security/commit-repo-node-tar branch April 21, 2026 19:55
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