Back to skill

Security audit

GitLab Batch Cloner

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does the GitLab cloning and indexing it advertises, but it handles GitLab tokens and bulk local changes in risky, under-disclosed ways.

Review before installing. Use only a short-lived, least-privilege GitLab token; avoid running on shared machines; prefer fixing TLS verification and git authentication before use; set a bounded local directory and total timeout; and treat the generated Excel file as containing untrusted GitLab metadata.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/clone_and_index.py:79
Finding
GitLab API token transmitted without TLS certificate verification<![CDATA[ ## Vulnerability Details **File Location**: `scripts/clone_and_index.py`, lines 79-96 **Vulnerability Type**: Disabled TLS certificate and hostname verification **Risk Level**: High ### Vulnerable Code ```python def _make_ssl_ctx(): """Create a permissive SSL context (many internal GitLabs use self-signed certs).""" ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE return ctx _SSL_CTX = _make_ssl_ctx() def api_get(gitlab_url: str, token: str, path: str, params: dict | None = None) -> list | dict: """GET request to GitLab API. Returns parsed JSON.""" url = f"{gitlab_url}/api/v4{path}" if params: url += "?" + urllib.parse.urlencode(params) req = urllib.request.Request(url, headers={"PRIVATE-TOKEN": token}) try: with urllib.request.urlopen(req, context=_SSL_CTX, timeout=30) as resp: ``` ### Technical Analysis The global SSL context disables both certificate-chain validation and hostname verification. Every GitLab API call uses this context while sending the personal access token in the `PRIVATE-TOKEN` request header. Consequently, the client cannot authenticate the GitLab server. A network-positioned adversary can present an arbitrary certificate without triggering an error. This defeats HTTPS server authentication and permits interception or modification of API traffic. The recursive subgroup and project requests are necessary for the declared batch-cloning functionality and are scoped to user-selected groups. The vulnerability is not the API access itself, but the insecure transport configuration used for all such access. ### Attack Path 1. The user supplies a token with the documented `read_api` and `read_repository` scopes. 2. The Skill connects to the configured GitLab URL over a network controlled or observable by an attacker. 3. The attacker intercepts the connection and presents an untrusted certificate. 4. Because certificate and hostnam ...[truncated 952 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Retain the secure defaults from `ssl.create_default_context()`: - Do not set `check_hostname` to `False`. - Do not set `verify_mode` to `ssl.CERT_NONE`. - For internal GitLab installations using a private certificate authority, support an explicit CA bundle setting and load it using: ```python ctx = ssl.create_default_context() ctx.load_verify_locations(cafile=configured_ca_bundle) ``` - Reject non-HTTPS GitLab URLs unless an explicit, strongly warned development-only override is enabled. - Validate the URL scheme and hostname before transmitting the token. - Do not silently fall back to insecure TLS behavior after a certificate error. - Add tests asserting that the SSL context uses `ssl.CERT_REQUIRED` and that hostname verification remains enabled. - Document private-CA installation as the supported solution for self-signed or enterprise certificates. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/clone_and_index.py:251
Finding
Personal access token exposed in Git clone process arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/clone_and_index.py`, lines 251-274 **Vulnerability Type**: Sensitive credential exposure through process command line **Risk Level**: High ### Vulnerable Code ```python def clone_project(http_url: str, token: str, target_dir: str) -> None: """Clone a project via HTTPS with embedded token, then strip token from remote. Uses start_new_session + process-group kill to avoid orphan git processes.""" url_with_token = http_url.replace("https://", f"https://oauth2:{token}@") proc = subprocess.Popen( ["git", "clone", "--quiet", url_with_token, target_dir], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, start_new_session=True, ) try: proc.communicate(timeout=CLONE_TIMEOUT) except subprocess.TimeoutExpired: try: os.killpg(os.getpgid(proc.pid), signal.SIGKILL) except OSError: proc.kill() proc.wait() # Always try to strip token from remote (even if clone partially failed) _sanitize_remote(target_dir, http_url) raise # Always try to strip token from remote (even if clone partially failed) _sanitize_remote(target_dir, http_url) ``` ### Technical Analysis The token is embedded in the HTTPS URL supplied as an argument to `git clone`. Although the code resets the repository's remote URL after cloning, this cleanup only addresses the credential persisted in `.git/config`. It does not protect the token while the Git process is running. Command-line arguments may be visible through process inspection facilities, process-monitoring software, endpoint security products, audit logs, debugging tools, or crash reports. Parallel cloning increases the number of token-bearing processes that can exist simultaneously. Using an argument list rather than `shell=True` correctly avoids shell injection, but it does not prevent credential disclosure through the proc ...[truncated 1037 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not place the token in the repository URL or any command-line argument. - Use a short-lived `GIT_ASKPASS` helper that returns credentials through Git's credential prompt mechanism. - Store any temporary credential helper in a private directory with restrictive permissions and delete it in a `finally` block. - Alternatively, use a controlled Git credential helper or SSH authentication where appropriate. - Disable interactive prompting with `GIT_TERMINAL_PROMPT=0` so failures do not unexpectedly block execution. - Ensure error messages and captured standard error are sanitized before being logged or returned. - Continue resetting the remote URL as defense in depth, but do not treat that reset as protection for process-level exposure. - Prefer narrowly scoped, short-lived tokens and revoke or rotate tokens after suspected exposure. - Add a test that inspects the generated `Popen` argument list and verifies that it never contains the token. ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/clone_and_index.py:43
Finding
Unpinned dependency installed automatically during script import<![CDATA[ ## Vulnerability Details **File Location**: `scripts/clone_and_index.py`, lines 43-52 **Vulnerability Type**: Unsafe runtime dependency bootstrap **Risk Level**: Medium ### Vulnerable Code ```python try: import openpyxl from openpyxl.styles import Font, Alignment, Border, Side except ImportError: print("[setup] Installing openpyxl ...") subprocess.check_call([sys.executable, "-m", "pip", "install", "openpyxl", "-q"]) import openpyxl from openpyxl.styles import Font, Alignment, Border, Side ``` ### Technical Analysis If `openpyxl` is unavailable, importing the module automatically invokes pip and installs the latest package version selected by the active package-index configuration. The dependency is not version-pinned, hash-verified, or installed from an explicitly trusted source. Package installation can execute package build logic and places executable Python code into the environment. The effective code reviewed as part of the Skill can therefore change according to package-index state, resolver behavior, local pip configuration, or a compromised distribution. This behavior is broader than necessary for the Skill's runtime function. Dependency installation should be an explicit deployment step, not an automatic side effect of importing or invoking the script. ### Attack Path 1. The script runs in an environment where `openpyxl` is not installed. 2. The `ImportError` branch invokes pip automatically. 3. Pip consults the environment's configured index, mirrors, proxies, and resolver settings. 4. A compromised package release, package index, mirror, or hostile package source supplies executable package content. 5. Pip installs that content under the privileges of the Skill process. 6. The subsequent import executes attacker-controlled Python initialization code. ### Impact Assessment Malicious dependency code would execute with the same operating-system privileges as the Skill. It could access the GitLab token in the p ...[truncated 341 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove automatic pip installation from the application code. - Declare `openpyxl` in a dedicated dependency file or package manifest. - Pin the dependency to a reviewed version. - Use a lock file or hash-checked requirements file, for example with pip's `--require-hashes`. - Install dependencies during a controlled setup or build phase from an approved package repository. - Run the Skill in an isolated virtual environment with least operating-system privilege. - Fail safely with a clear installation instruction if the dependency is missing. - Incorporate dependency vulnerability scanning and scheduled review of pinned versions into the release process. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/clone_and_index.py:482
Finding
GitLab-controlled metadata is written to Excel without formula neutralization<![CDATA[ ## Vulnerability Details **File Location**: `scripts/clone_and_index.py`, lines 482-502 **Vulnerability Type**: Spreadsheet formula injection **Risk Level**: Medium ### Vulnerable Code ```python def _item_to_row(item: dict) -> list: """Convert a project data dict to a row list matching HEADERS_CN.""" return [ item["main_group"], item["sub_group"], item["project_path"], item["project_name"], item["description"], item["branches"], item["times"], item["ssh_url"], item["download_time"], item.get("project_id", ""), ] ``` The resulting values are appended directly to worksheets: ```python for item in items: ws.append(_item_to_row(item)) ``` The external description source is assigned without sanitization in the project worker: ```python description = project_info.get("description", "") or "" ``` ### Technical Analysis Project names, paths, descriptions, branch names, and repository URLs originate from GitLab project metadata or repositories. These strings are transferred directly into XLSX cells. Spreadsheet applications and `openpyxl` can interpret strings beginning with formula markers—particularly `=`—as formulas rather than inert text. Values beginning with `+`, `-`, or `@` may also be interpreted specially by some spreadsheet clients. An attacker who can modify metadata for a project visible to the supplied token can place a formula-like value in a field such as the project description. The Skill then creates a workbook containing that value without neutralization. ### Attack Path 1. An attacker creates or modifies a GitLab project accessible to the auditing user's token. 2. The attacker places a spreadsheet formula payload in project metadata, such as the description. 3. The user runs the Skill for a group containing that project. 4. The Skill retrieves the attacker-controlled metadata and appends it directly to `01.Index.xlsx`. 5. The user opens ...[truncated 834 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Treat all GitLab metadata and branch names as untrusted before writing them to a workbook. - Introduce a centralized cell-sanitization function that detects strings beginning with `=`, `+`, `-`, or `@`. - Neutralize formula-like values by prefixing an apostrophe or otherwise forcing the cell to contain literal text. - Apply the sanitizer to every externally sourced textual column, not only the description. - Preserve legitimate display values while ensuring the spreadsheet library stores them as strings rather than formulas. - Consider rejecting control characters and normalizing line breaks in metadata. - Add regression tests using formula payloads in project names, descriptions, paths, branch names, and URLs. - Verify the generated workbook by reloading it and asserting that untrusted cells have string data types rather than formula data types. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (17)

Credential Access

High
Category
Privilege Escalation
Content
| Parameter | Required | Default | Notes |
|-----------|----------|---------|-------|
| GitLab URL | ✅ | — | e.g. `https://gitlab.company.com` |
| Personal Access Token | ✅ | — | Needs `read_api` + `read_repository` scopes |
| Target Group(s) | ✅ | — | Group/sub-group/project paths, comma-separated. Supports: top-level group (`myGroup`), sub-group path (`myGroup/mySubGroup`), or direct project path (`myGroup/mySubGroup/my-project`) |
| Local Storage Path | ❌ | `~/Desktop/Code` | Where repos are stored |
| Auth Method | ❌ | HTTPS+Token | Or SSH if key is configured |
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
Environment variables:
    GITLAB_URL       - GitLab instance URL (required)
    GITLAB_TOKEN     - Personal access token (required)
    GITLAB_GROUPS    - Comma-separated group/sub-group/project paths (required)
    GITLAB_BASE_DIR  - Local storage path (default: ~/Desktop/Code)
    GITLAB_MODE      - "clone" (default), "update" (skip clone, only pull),
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

High
Confidence
99% confidence
Finding
Disabling TLS validation without explicit warning makes the authenticated API traffic susceptible to interception while hiding that risk from the operator. Because the script sends a private token on every request, this materially raises the chance of credential theft and tampered project metadata.

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill declares behavior that requires environment variables, network access, and shell execution, but it does not explicitly scope or constrain those capabilities. In an agent setting, missing tool-scope declarations weakens least-privilege controls and can cause the skill to run with broader access than users or orchestrators expect.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The workflow causes large-scale filesystem changes by cloning or updating many repositories and repeatedly writing an Excel index, but the skill does not clearly warn about the breadth and persistence of those side effects before execution. This can lead to unintended local data modification, disk consumption, and overwriting or mixing content in the chosen base directory.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
Runtime package installation is outside the narrow operational scope of cloning repositories and generating an index, and it introduces unnecessary code-execution and supply-chain exposure. An attacker who can influence package sources, indexes, TLS interception, or the environment may gain a path to execute untrusted code during setup.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
from openpyxl.styles import Font, Alignment, Border, Side
except ImportError:
    print("[setup] Installing openpyxl ...")
    subprocess.check_call([sys.executable, "-m", "pip", "install", "openpyxl", "-q"])
    import openpyxl
    from openpyxl.styles import Font, Alignment, Border, Side
Confidence
91% confidence
Finding
Automatically installing a package with pip at runtime causes the script to execute additional network-enabled code and modify the host environment beyond its stated cloning/indexing purpose. This expands supply-chain and integrity risk, especially in enterprise or restricted environments where dependency installation should be explicit and controlled.

Unbounded Resource Access

Medium
Category
Excessive Agency
Content
GIT_CMD_TIMEOUT = 60      # 1 minute for lightweight git commands
API_PER_PAGE = 100
INCREMENTAL_EXCEL_BATCH = 50  # Write Excel every N completed projects
DEFAULT_TOTAL_TIMEOUT = 0     # 0 = no global timeout; override via GITLAB_TOTAL_TIMEOUT

HEADERS_CN = [
    "主Group名称", "子Group路径", "Project路径", "Project名称",
Confidence
86% confidence
Finding
The default global timeout is disabled, allowing the batch operation to run without an overall execution bound. In a large or adversarial repository set, this can cause prolonged resource consumption, excessive network/disk use, and operational denial-of-service on the host or shared runner.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The generated Excel output uses fixed Chinese column names, which imposes a specific language on users regardless of preference or locale. The file does not indicate that this is region-specific or provide any opt-in or configuration for alternate languages.

Context-Inappropriate Capability

Medium
Confidence
99% confidence
Finding
The script globally disables TLS certificate validation and hostname checking for all GitLab API requests. That enables man-in-the-middle interception or modification of API responses and can expose the GitLab token to attackers on the network, which is especially dangerous because the tool performs authenticated enumeration of repositories.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
This code sends the GITLAB_TOKEN in HTTP headers to a remote GitLab instance, which is a sensitive credential transmission. Although the module docstring documents the environment variable, there is no user-facing warning at the operation site about network transmission of credentials or repository metadata.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def _run_git(args: list[str], cwd: str, timeout: int = GIT_CMD_TIMEOUT) -> subprocess.CompletedProcess:
    """Run a git command with timeout.  Uses a new session so the entire
    process group can be killed on timeout (prevents orphan git-remote-https)."""
    proc = subprocess.Popen(
        ["git"] + args,
        cwd=cwd,
        stdout=subprocess.PIPE,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The clone flow embeds the access token in the repository URL and provides no clear operator warning about that sensitive handling. Even if the remote is sanitized afterward, the secret may be visible during execution to local users, monitoring agents, shell history substitutes, or diagnostic artifacts.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"""Clone a project via HTTPS with embedded token, then strip token from remote.
    Uses start_new_session + process-group kill to avoid orphan git processes."""
    url_with_token = http_url.replace("https://", f"https://oauth2:{token}@")
    proc = subprocess.Popen(
        ["git", "clone", "--quiet", url_with_token, target_dir],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
Confidence
89% confidence
Finding
The script embeds the GitLab personal access token directly into the clone URL passed to git. Although it later resets the remote URL, the secret is still exposed to child-process arguments during execution and may leak via process listings, debugging tools, logs, crash reports, or other local telemetry.

Missing User Warnings

Medium
Confidence
80% confidence
Finding
This function creates or overwrites the Excel index file, and elsewhere the script also clones repositories into the configured base directory. While the script prints status messages, it does not clearly warn beforehand that it will write many repositories locally and replace an existing index file.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
tmpdir = tempfile.mkdtemp()
        try:
            # Use a git command that will hang: init a repo first, then run a slow command
            subprocess.run(["git", "init", tmpdir], capture_output=True)
            with self.assertRaises(subprocess.TimeoutExpired):
                # 'git log' on empty repo is fast, so use a sleep trick via GIT_TRACE
                # Instead, just call _run_git with a very short timeout on a command that takes time
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Natural-Language Policy Violations

Low
Confidence
98% confidence
Finding
The Excel specification hard-codes Chinese field names such as `主Group名称`, `子Group路径`, and `下载时间`. This is a natural-language locale choice imposed by the skill, and the file does not indicate user opt-in or a region-specific justification for requiring Chinese output.

Static analysis

No suspicious patterns detected.