Back to skill

Security audit

gitlab-code-reviewer

Security checks for vulnerabilities and agentic risk

Overview

This GitLab review skill is coherent, but it can expose your GitLab token to a host taken from the MR URL and can post comments with your account.

Install only if you trust the MR URLs you will provide and understand that the skill may use your GitLab token to read private MR data and post comments. Prefer a least-privilege, short-lived token, do not use untrusted or http MR URLs, and fix host validation before use so the token is sent only to the configured GitLab origin.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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/gitlab_client.py:38
Finding

GitLab Token Can Be Exfiltrated to an Attacker-Controlled Host

Content
View full analysis
tuple[str, str, str]: """ Parse a GitLab MR URL into (host, project_path_encoded, mr_iid). Supports: https://gitlab.com/group/subgroup/project/-/merge_requests/123 https://gitlab.example.com/namespace/project/-/merge_requests/456 Returns (host, url_encoded_project_path, mr_iid). """ pattern = r"(https?://[^/]+)/(.+)/-/merge_requests/(\d+)" m = re.match(pattern, url.rstrip("/")) if not m: raise ValueError(f"Cannot parse MR URL: {url}") host = m.group(1) project_path = m.group(2) mr_iid = m.group(3) encoded_path = project_path.replace("/", "%2F") return host, encoded_path, mr_iid def api_get(host: str, token: str, path: str, params: dict | None = None) -> dict | list: url = f"{host}/api/v4{path}" if params: url += "?" + urlencode(params) req = Request(url, headers={"PRIVATE-TOKEN": token, "Content-Type": "application/json"}) try: with urlopen(req) as resp: return json.loads(resp.read().decode()) except HTTPError as e: body = e.read().decode() raise RuntimeError(f"GitLab API error {e.code} on GET {path}: {body}") from e ``` The vulnerable data flow is invoked as follows: ```python def fetch_mr(mr_url: str) -> dict: """Return MR metadata (title, author, source_branch, target_branch, description, state).""" creds = load_credentials() host, project, iid = parse_mr_url(mr_url) data = api_get(host, creds["token"], f"/projects/{project}/merge_requests/{iid}") ``` ### Technical Analysis The Skill loads a privileged GitLab token from `~/.openclaw/credentials/gitlab.json`, but it does not bind that token to the configured GitLab ho ...[truncated 1865 chars]
Remediation
View remediation
None: supplied = urlsplit(mr_url) configured = urlsplit(configured_host) if supplied.scheme != "https": raise ValueError("MR URLs must use HTTPS") if ( supplied.hostname != configured.hostname or supplied.port != configured.port or supplied.username is not None or supplied.password is not None ): raise ValueError("MR URL origin does not match the configured GitLab host") def fetch_mr(mr_url: str) -> dict: creds = load_credentials() validate_mr_origin(mr_url, creds["host"]) _, project, iid = parse_mr_url(mr_url) return api_get( creds["host"].rstrip("/"), creds["token"], f"/projects/{project}/merge_requests/{iid}", ) ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:99
Finding

Review Content Is Written to a Predictable Shared Temporary File

Content
View full analysis
/tmp/mr_comments.json << 'EOF' [ { "file_path": "src/main/UserService.java", "line": 42, "body": "[CRITICAL] Transaction wraps HTTP call...\n\nSuggestion:\n```java\n// fix\n```" } ] EOF # 2. Post via script python scripts/post_comments.py /tmp/mr_comments.json ``` ### Technical Analysis The documented workflow directs the Agent to write review comments to the fixed path `/tmp/mr_comments.json`. Comment bodies may contain excerpts or corrected versions of proprietary source code. The fixed name creates a race condition in a shared temporary directory. Shell redirection follows an existing symbolic link, so another local user may pre-create that path as a symlink. The workflow also does not set restrictive permissions or remove the file after posting. As a result, review content can remain on disk beyond the Skill run, contrary to the stated rule not to persist source-code content. ### Attack Path 1. A local attacker anticipates execution of the Skill. 2. The attacker monitors the predictable file path or pre-creates `/tmp/mr_comments.json` as a symbolic link. 3. The workflow writes review comments and possible source snippets to that path. 4. The attacker reads the resulting file where local permissions permit, or causes the invoking user to overwrite another file reachable through the symlink. 5. Because no cleanup is specified, the content may remain available after the review completes. ### Impact Assessment The primary impact is local disclosure of MR findings and proprietary source-code fragments. In a multi-user environment, another local account may obtain data from private repositories. Symlink exploitation may also overwrite a file writable by the invoking account. Th ...[truncated 162 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Note
Location
scripts/ignore_matcher.py:51
Finding

Ignore Matcher Unnecessarily Reads the Token-Bearing Credential File

Content
View full analysis
list[str]: if not CREDS_PATH.exists(): return [] try: with open(CREDS_PATH) as f: creds = json.load(f) return creds.get("ignore_patterns", []) except Exception: return [] ``` ### Technical Analysis The ignore matcher only needs non-secret filename patterns, but it opens and parses the same JSON object that contains the GitLab token and host. This unnecessarily places a credential-bearing object in the filtering component’s process memory and expands the number of code paths that require access to secret storage. No direct network transmission or logging of the token was identified in `ignore_matcher.py`. The issue is therefore a least-privilege design weakness rather than a demonstrated credential leak. Nevertheless, local filtering should not require access to an API token. The blanket `except Exception` also conceals malformed configuration and permission errors, making misuse or configuration failures harder to detect. ### Attack Path 1. The ignore matcher is invoked to classify an MR file. 2. It opens `~/.openclaw/credentials/gitlab.json`. 3. The complete credential object, including the token, is loaded into process memory even though only `ignore_patterns` is needed. 4. A future defect, debugging hook, compromised runtime component, or instrumentation in this otherwise non-secret processing path could access the token-bearing object. There is no confirmed direct exfiltration path in the current matcher implementation; the risk is the avoidable expansion of credential exposure. ### Impact Assessment The matcher gains read access to a GitLab token that is not necessary for its filtering function. If this unnecessary access is combined with ...[truncated 265 chars]
Remediation
View remediation
list[str]: if not CONFIG_PATH.exists(): return [] try: with open(CONFIG_PATH, encoding="utf-8") as f: config = json.load(f) except (OSError, json.JSONDecodeError) as exc: raise RuntimeError(f"Cannot load ignore-pattern configuration: {exc}") from exc patterns = config.get("ignore_patterns", []) if not isinstance(patterns, list) or not all(isinstance(p, str) for p in patterns): raise ValueError("ignore_patterns must be a list of strings") return patterns ``` ]]>
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
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (7)

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 · scripts/gitlab_client.py (reported line 102)May include surrounding context.

python
+ ").\n"
                "Analysis will proceed, but inline comments CANNOT be posted.\n"
                "To enable posting: regenerate the token with scope 'api' at\n"
                "GitLab → User Settings → Access Tokens, then update\n"
                "~/.openclaw/credentials/gitlab.json.",
                file=sys.stderr,
            )

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 · scripts/gitlab_client.py (reported line 258)May include surrounding context.

python
+ ").\n"
                "Analysis will proceed, but inline comments CANNOT be posted.\n"
                "To enable posting: regenerate the token with scope 'api' at\n"
                "GitLab → User Settings → Access Tokens, then update\n"
                "~/.openclaw/credentials/gitlab.json.",
                file=sys.stderr,
            )

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The description says the skill performs code review on GitLab merge requests, but it does not clearly disclose that it will read credentials from a local file and may post comments to the remote GitLab MR. This weakens informed user consent and increases the risk of surprising outbound actions performed with stored tokens.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The trigger phrases are broad enough to match common user requests like 'review this MR' or 'check this merge request', which can cause the skill to activate in situations where the user did not explicitly consent to GitLab API access or outbound actions. In this skill, unintended invocation is more dangerous because the workflow includes reading stored credentials and potentially posting comments back to GitLab.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
85% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

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

text

**Constraints:**
- Do not auto-approve the MR.
- Do not add labels or trigger pipelines.
- Only post comment-type discussions (no approval API calls).
- If a line is not in the diff, the API returns an error — log it and continue with the next comment.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
75% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

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

md
**Constraints:**
- Do not auto-approve the MR.
- Do not add labels or trigger pipelines.
- Only post comment-type discussions (no approval API calls).
- If a line is not in the diff, the API returns an error — log it and continue with the next comment.
- On HTTP 403 `insufficient_scope`, the script stops immediately and prints a fix instruction. Do not retry.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · scripts/gitlab_client.py (reported line 94)May include surrounding context.

python
try:
        info = api_get(host, creds["token"], "/personal_access_tokens/self")
        scopes = info.get("scopes", [])
        can_write = "api" in scopes
        if not can_write:
            print(
                "WARNING: Token scope is read-only (scopes: "