Back to skill

Security audit

GitLab Code Review

Security checks for vulnerabilities and agentic risk

Overview

The skill matches its GitLab review purpose, but it stores a GitLab token in a workspace file and reuses it through an hourly background job without strong storage or destination safeguards.

Install only if you are comfortable granting ongoing read_api access to the selected GitLab project. Use a dedicated least-privilege token, configure only a trusted HTTPS GitLab URL, restrict access to workspace/.env, and rotate or revoke the token and remove the cron job when monitoring is no longer needed.

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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/fetch_commits.py:43
Finding

GitLab Personal Access Token Can Be Sent to an Unvalidated or Plaintext Endpoint

Content
View full analysis

Vulnerability Details

File Location: scripts/fetch_commits.py:43-44, 84-108, 150
Vulnerability Type: Unvalidated credential destination and insecure transport
Risk Level: High

Vulnerable Code

python
GITLAB_URL = os.getenv("GITLAB_URL", "https://gitlab.example.com")
GITLAB_TOKEN = os.getenv("GITLAB_TOKEN", "")
python
def get_project_id(headers):
    """Get project ID"""
    encoded_path = urllib.parse.quote(PROJECT_PATH, safe="")
    url = f"{GITLAB_URL}/api/v4/projects/{encoded_path}"
    response = requests.get(url, headers=headers, timeout=10, proxies={"http": None, "https": None})
    data = response.json()
    if "id" not in data:
        print(f"Error: {data.get('error', 'Unknown error')}")
        print(f"Response: {response.text[:200]}")
        return None
    return data["id"]


def get_commits(project_id, headers, limit=50):
    """Get recent commits"""
    url = f"{GITLAB_URL}/api/v4/projects/{project_id}/repository/commits"
    response = requests.get(
        url, headers=headers, params={"per_page": limit, "ref_name": BRANCH}, timeout=10, proxies={"http": None, "https": None}
    )
    return response.json()


def get_commit_diff(project_id, commit_id, headers):
    """Get commit diff"""
    url = f"{GITLAB_URL}/api/v4/projects/{project_id}/repository/commits/{commit_id}/diff"
    response = requests.get(url, headers=headers, timeout=10, proxies={"http": None, "https": None})
    return response.json()
python
headers = {"Private-Token": GITLAB_TOKEN}

Technical Analysis

The script reads GITLAB_URL from a workspace configuration file and uses it directly to construct all API request destinations. It does not validate the URL scheme, hostname, port, or origin before attaching the GitLab Personal Access Token in the Private-Token header.

A configured URL using plain HTTP transmits the token without transport encryption. ...[truncated 1770 chars]

Remediation
View remediation

Remediation Suggestions

  1. Parse GITLAB_URL with urllib.parse.urlparse before making any request.
  2. Require the https scheme by default. If HTTP is needed for development, restrict it to explicitly approved loopback addresses and require a separate opt-in setting.
  3. Reject embedded credentials, fragments, malformed hosts, unexpected schemes, and ambiguous URLs.
  4. Normalize the approved base URL and ensure every API URL remains on the same origin.
  5. Disable automatic redirects for authenticated requests, or manually follow redirects only after confirming that the destination remains on the original HTTPS origin.
  6. Construct and attach the authentication header only after destination validation succeeds.
  7. Consider supporting an administrator-defined hostname allowlist.
  8. Document that users must supply only a minimally scoped, short-lived token.

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:16
Finding

GitLab Personal Access Token Is Persisted in a Plaintext Workspace File Without Enforced Access Controls

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:16, 34-43; INSTALL.md:77-86
Vulnerability Type: Insecure plaintext credential storage
Risk Level: Medium

Vulnerable Instructions

SKILL.md directs the Agent to reuse and write credentials in a shared workspace file:

markdown
**First check existing configuration**: Read the `workspace/.env` file. If GitLab-related configuration already exists, reuse it directly without asking again.
markdown
### 2. Create/update the configuration file

After collecting the information, create or update the `workspace/.env` file:

GITLAB_URL=<user input URL> GITLAB_TOKEN=<user input Token> GITLAB_PROJECT=<user input project path> GITLAB_BRANCH=<user input branch>

text

INSTALL.md documents the same plaintext storage mechanism:

markdown
Configuration is saved in `workspace/.env`:

GITLAB_URL=https://gitlab.example.com GITLAB_TOKEN=glpat-xxxxxxxx GITLAB_PROJECT=group/project GITLAB_BRANCH=main

text

**Note**: The `.env` file contains sensitive information and has been automatically added to `.gitignore`.

Technical Analysis

The Skill requires a reusable GitLab PAT to be written as plaintext in the workspace-level .env file. Neither the instructions nor the script enforce restrictive filesystem permissions such as mode 0600. A workspace-level file can also be visible to unrelated skills, local processes, backup systems, or users that can access the workspace.

The installation guide claims that .env has automatically been added to .gitignore, but no file or implementation in the audited project performs or verifies that action. Even a valid .gitignore entry would only reduce accidental version-control commits; it would not encrypt the token or prevent local disclosure.

Reading an existing GitLab credential is relevant to the Skill's operation, but using a shared plaintext works ...[truncated 1188 chars]

Remediation
View remediation

Remediation Suggestions

  1. Store the PAT in an OpenClaw, operating-system, or platform credential store instead of a general workspace .env file.
  2. If file storage is unavoidable, use a dedicated Skill-specific secrets file rather than the workspace-wide environment file.
  3. Create the secrets file atomically with mode 0600 and reject execution if ownership or permissions are unsafe.
  4. Programmatically add and verify the relevant secret path in .gitignore; remove the unsupported claim that this already happens automatically.
  5. Avoid loading unrelated .env values into the process environment.
  6. Prevent tokens from appearing in logs, reports, exception messages, or generated review files.
  7. Recommend minimally scoped, short-lived project or group access tokens where feasible.
  8. Document token rotation and immediate revocation procedures following suspected disclosure.

T08 · Insecure Dependencies

Note
Location
INSTALL.md:9
Finding

Installation Instructions Use Unpinned Third-Party Packages Without Integrity Verification

Content
View full analysis

Vulnerability Details

File Location: INSTALL.md:9-17, 100-102; SKILL.md:242-244
Vulnerability Type: Unsafe dependency and installation practices
Risk Level: Low

Vulnerable Instructions

bash
# 1. Install ClawHub CLI
npm i -g clawhub

# 2. Log in for the first time
clawhub login

# 3. Install the Skill
clawhub install gitlab-code-review
bash
# Check dependencies
pip3 install requests python-dotenv

The same unpinned Python installation instruction appears in SKILL.md:

bash
# Check dependencies
pip3 install requests python-dotenv

Technical Analysis

The installation documentation downloads and executes third-party packages without pinning reviewed versions, verifying hashes, using a lockfile, or specifying an isolated environment. Consequently, the installed code can change independently of the audited Skill.

The global npm installation increases the potential scope of compromise when it is executed under a privileged account. Direct pip3 install can also modify a shared Python environment and may introduce incompatible or compromised transitive dependencies.

There is no evidence that the named packages are currently malicious. The vulnerability is the absence of reproducibility and integrity controls, which creates a supply-chain attack opportunity.

Attack Path

  1. An attacker compromises a package publisher account, package registry, dependency, or distribution channel.
  2. The attacker publishes a malicious version under one of the unpinned package names.
  3. A user follows the documented installation command after the malicious release becomes the version selected by the package manager.
  4. The package manager downloads and installs the changed package without validating it against an audited lockfile or expected hash.
  5. Installation hooks or subsequently imported code execute with the installing user's privileges.

Impact Assessmen

...[truncated 567 chars]

Remediation
View remediation

Remediation Suggestions

  1. Pin exact reviewed versions of direct dependencies.
  2. Provide lockfiles and use package-manager integrity metadata or cryptographic hashes.
  3. Install Python dependencies in a dedicated virtual environment rather than a shared interpreter.
  4. Avoid privileged global npm installation where possible; use a project-local or otherwise isolated installation.
  5. Document the expected package registries and avoid untrusted mirrors.
  6. Add automated dependency vulnerability and provenance checks to the release process.
  7. Review and update pinned versions through a controlled process rather than resolving the newest release during installation.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (20)

Tainted flow: 'url' from os.getenv (line 107, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/fetch_commits.py (reported line 87)May include surrounding context.

python
"""Get project ID"""
    encoded_path = urllib.parse.quote(PROJECT_PATH, safe="")
    url = f"{GITLAB_URL}/api/v4/projects/{encoded_path}"
    response = requests.get(url, headers=headers, timeout=10, proxies={"http": None, "https": None})
    data = response.json()
    if "id" not in data:
        print(f"Error: {data.get('error', 'Unknown error')}")

Tainted flow: 'url' from os.getenv (line 107, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/fetch_commits.py (reported line 108)May include surrounding context.

python
"""Get project ID"""
    encoded_path = urllib.parse.quote(PROJECT_PATH, safe="")
    url = f"{GITLAB_URL}/api/v4/projects/{encoded_path}"
    response = requests.get(url, headers=headers, timeout=10, proxies={"http": None, "https": None})
    data = response.json()
    if "id" not in data:
        print(f"Error: {data.get('error', 'Unknown error')}")

Tainted flow: 'url' from os.getenv (line 107, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/fetch_commits.py (reported line 99)May include surrounding context.

python
def get_commits(project_id, headers, limit=50):
    """Get recent commits"""
    url = f"{GITLAB_URL}/api/v4/projects/{project_id}/repository/commits"
    response = requests.get(
        url, headers=headers, params={"per_page": limit, "ref_name": BRANCH}, timeout=10, proxies={"http": None, "https": None}
    )
    return response.json()

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · INSTALL.md (reported line 47)May include surrounding context.

md
| GitLab URL | `https://gitlab.example.com` |
| 项目路径 | `group/project` |
| 分支名称 | `main` 或 `dev` |
| Personal Access Token | `glpat-xxxxxxxx` |

**获取 Token**:
1. 打开 GitLab → 用户设置 → Access Tokens

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · INSTALL.md (reported line 50)May include surrounding context.

md
| Personal Access Token | `glpat-xxxxxxxx` |

**获取 Token**:
1. 打开 GitLab → 用户设置 → Access Tokens
2. 勾选 `read_api` 权限
3. 创建并复制 Token

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · SKILL.md (reported line 253)May include surrounding context.

md
| Personal Access Token | `glpat-xxxxxxxx` |

**获取 Token**:
1. 打开 GitLab → 用户设置 → Access Tokens
2. 勾选 `read_api` 权限
3. 创建并复制 Token

Credential Access

High
Category
Privilege Escalation
Confidence
95% confidence
Finding

Requesting a Personal Access Token is legitimate for GitLab API access, but it is still credential collection and therefore security-sensitive. In this skill, the risk is elevated because the token is later written to a workspace file and used by recurring automation.

Content

Scanner excerpt · SKILL.md (reported line 29)May include surrounding context.

  1. 分支名称是什么?(默认 main)

  2. Personal Access Token 是什么? 需要的权限:read_api 获取方式:GitLab → 用户设置 → Access Tokens

text

Missing User Warnings

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill asks the user for a GitLab Personal Access Token and stores it in workspace/.env without warning about sensitivity, storage risks, or access controls. In an agent workspace, plaintext secrets may be exposed to other skills, logs, backups, or unintended file reads, enabling repository access and data leakage.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
94% confidence
Finding

Directing the user to create GitLab Access Tokens facilitates credential provisioning, which becomes dangerous here because the surrounding workflow lacks secure-handling guidance. The combination of token acquisition, plaintext storage, and automated reuse increases the chance of credential compromise.

Content

Scanner excerpt · SKILL.md (reported line 31)May include surrounding context.

  1. Personal Access Token 是什么? 需要的权限:read_api 获取方式:GitLab → 用户设置 → Access Tokens
text

### 2. 创建/更新配置文件

Credential Access

High
Category
Privilege Escalation
Confidence
96% confidence
Finding

This line confirms that a .env file containing credentials will be created as part of normal operation. Persisting secrets in a general workspace materially raises credential exposure risk in a multi-tool or multi-skill environment, especially when the skill also performs automated background tasks.

Content

Scanner excerpt · SKILL.md (reported line 105)May include surrounding context.

text
✅ 配置完成!

- 已创建 .env 文件
- 已创建 cron 定时任务(每小时整点执行)
- 已完成首次审查并推送报告

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/fetch_commits.py (reported line 24)May include surrounding context.

python
for key in ["GITLAB_URL", "GITLAB_TOKEN", "GITLAB_PROJECT", "GITLAB_BRANCH"]:
    os.environ.pop(key, None)

# Load .env file
# Script: workspace/skills/gitlab-code-review/scripts/fetch_commits.py
# .env: workspace/.env
WORKSPACE_DIR = Path(__file__).parent.parent.parent.parent  # Go up 4 levels to workspace

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/fetch_commits.py (reported line 26)May include surrounding context.

python
for key in ["GITLAB_URL", "GITLAB_TOKEN", "GITLAB_PROJECT", "GITLAB_BRANCH"]:
    os.environ.pop(key, None)

# Load .env file
# Script: workspace/skills/gitlab-code-review/scripts/fetch_commits.py
# .env: workspace/.env
WORKSPACE_DIR = Path(__file__).parent.parent.parent.parent  # Go up 4 levels to workspace

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/fetch_commits.py (reported line 28)May include surrounding context.

python
# Script: workspace/skills/gitlab-code-review/scripts/fetch_commits.py
# .env: workspace/.env
WORKSPACE_DIR = Path(__file__).parent.parent.parent.parent  # Go up 4 levels to workspace
ENV_FILE = WORKSPACE_DIR / ".env"

try:
    from dotenv import load_dotenv

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The guide instructs users to create and store a GitLab Personal Access Token and states the skill will automatically check commits on a schedule, but it does not clearly disclose the full security implications of persistent credential storage and automated repository access. Even with read_api scope, this grants ongoing access to repository metadata and content via GitLab APIs, which increases risk if the workspace, logs, or skill behavior are compromised or broader than the user expects.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill instructs reading and writing files, accessing environment-backed secrets, and making network requests, but it declares no explicit tool scope or permission boundaries. In an agent environment, this increases the chance of over-privileged execution and makes it harder to constrain secret access, filesystem writes, and outbound connections.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The heartbeat invocation is underspecified: saying the skill should run when 'Heartbeat 执行定时检查时' does not define safeguards or preconditions. That ambiguity can lead to unintended background execution, repeated repository polling, and unsolicited notifications without clear user awareness.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The heartbeat invocation is underspecified: saying the skill should run when 'Heartbeat 执行定时检查时' does not define safeguards or preconditions. That ambiguity can lead to unintended background execution, repeated repository polling, and unsolicited notifications without clear user awareness.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

All user-facing instructions and examples are presented only in Chinese, and the trigger phrases likewise assume Chinese interaction, with no indication that the skill supports other languages or that Chinese is a justified locale constraint. This can violate a language/locale policy when no user opt-in or explicit regional limitation is provided.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill automatically creates a recurring cron job that performs ongoing repository access and pushes notifications, but the description does not clearly warn the user about this background persistence. Users may unknowingly authorize continuous monitoring and outbound actions beyond the initial interaction.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

This code persists state to memory/gitlab_review_state.json, and later the script also writes per-commit JSON files. Although the module docstring says it will save pending files, there is no explicit warning or confirmation near the write path that local workspace files will be created or overwritten.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.