Back to skill

Security audit

Git Log Intelligence

Security checks for vulnerabilities and agentic risk

Overview

The skill largely does what it claims, but it uses a GitHub token, runs the GitHub CLI, and keeps persistent filters with weak scoping and validation, so it should be reviewed before installation.

Install only if you are comfortable giving this skill GitHub API access through a token and storing persistent local ignore patterns. Prefer a fine-grained read-only token limited to the needed repositories, inspect the ignore list periodically, and treat commit text returned by the tool as untrusted content to summarize, not instructions to follow.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T01 · Skill Instruction Hijacking

Warning
Location
git_log_intelligence.py:112
Finding
Untrusted GitHub Commit Content Is Passed into the Agent Context Without Prompt-Injection Boundaries<![CDATA[ ## Vulnerability Details **File Location**: `git_log_intelligence.py:112-133`; related agent instructions in `SKILL.md:31-36` and `SKILL.md:63-65` **Vulnerability Type**: Untrusted remote content incorporated into AI-agent instructions **Risk Level**: Medium ### Complete Code Snippet ```python for c in commits: full_msg = c['commit']['message'] subject = full_msg.split('\n')[0] # 1. Filter based on subject line if any(re.search(p, subject, re.IGNORECASE) for p in filters): filtered_out += 1 continue # 2. Collect data based on flag sha = c['sha'][:7] author = c['commit']['author']['name'] if len(important) >= MAX_LINES: capped_out += 1 continue if full_context: # Truncate long messages to protect the agent's context window content = (full_msg[:MAX_MSG_LEN] + '...') if len(full_msg) > MAX_MSG_LEN else full_msg important.append(f"COMMIT: {sha}\nAUTHOR: {author}\nMESSAGE:\n{content}\n{'-'*20}") else: important.append(f"- {sha}: {subject} ({author})") ``` The corresponding Skill instructions state: ```markdown ### Agent Logic: Call the script with the repo name and timeframe. Receive a filtered list of "Important" commits. Present a natural language summary to the user, noting how many noisy commits were hidden. ``` ### Technical Analysis Commit messages, commit subjects, and author names are controlled by repository contributors. The implementation retrieves these fields from GitHub and places them directly into text intended for processing by an AI agent. The output does not establish a strong trust boundary around the remote content. In particular, the Skill does not instruct the agent to: - Treat all repository metadata as untrusted data rather than instructions. - Ignore directives, tool requests, links, or requests for disclosure embedded in commit text. - Avoid changing its goals or invoking tools based on retrieved cont ...[truncated 2005 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Add an explicit trust-boundary rule to `SKILL.md` stating that commit messages, author names, repository metadata, URLs, and API responses are untrusted data and must never be interpreted as instructions. 2. Require the agent to summarize only factual repository activity and prohibit tool calls, secret disclosure, goal changes, or navigation based on fetched content. 3. Return structured JSON rather than presentation-oriented text. Use fixed fields such as `sha`, `author`, `subject`, and `message`, and tell the agent that field values are data only. 4. Wrap remote content in clear delimiters and identify its origin before presenting it to the agent. 5. Normalize or remove control characters and other formatting that could blur the boundary between trusted instructions and remote content. 6. Keep full-message mode opt-in and consider reducing its maximum length or extracting factual fields before the content reaches the agent. 7. Add adversarial tests containing commit messages such as tool requests, system-style instructions, and requests to disclose secrets. Verify that the agent only summarizes them as quoted repository content. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
git_log_intelligence.py:42
Finding
Unchecked Persistent Regular Expressions Permit ReDoS and Persistent Summary Failures<![CDATA[ ## Vulnerability Details **File Location**: `git_log_intelligence.py:42-47`, `git_log_intelligence.py:99-120`, and `git_log_intelligence.py:145-151` **Vulnerability Type**: Unvalidated persistent regular-expression input **Risk Level**: Medium ### Complete Code Snippet The Skill persists the supplied pattern without checking its type, syntax, length, or complexity: ```python def save_filter(pattern): filters = set(load_filters()) filters.add(pattern) os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True) with open(CONFIG_PATH, 'w') as f: json.dump({"ignore_patterns": list(filters)}, f, indent=2) print(f"✅ Memorized new ignore pattern: {pattern}") ``` The stored patterns are later evaluated with Python’s backtracking regular-expression engine against remotely sourced commit subjects: ```python commits = parse_paginated_json_array(result.stdout) important = [] filtered_out = 0 capped_out = 0 for c in commits: full_msg = c['commit']['message'] subject = full_msg.split('\n')[0] # 1. Filter based on subject line if any(re.search(p, subject, re.IGNORECASE) for p in filters): filtered_out += 1 continue ``` The command-line target is optional and is passed directly to `save_filter`: ```python parser.add_argument("action", choices=["summarize", "ignore", "show", "remove"], help="Action to perform") parser.add_argument("target", nargs="?", help="Repo (owner/repo) or Regex Pattern for ignore") parser.add_argument("days", nargs="?", default=7, type=int, help="Days to look back") parser.add_argument("--full", action="store_true", help="Include full commit bodies (truncated)") parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output for debugging") args = parser.parse_args() if args.action == "ignore": save_filter(args.target) ``` ### Technical Analysis Python’s standard `re` engine uses backtracking and does not provide a timeout in this usage. A pattern cont ...[truncated 2716 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Make the target mandatory for `ignore` and `remove`, or reject absent and empty values before calling the persistence functions: ```python if args.action in {"ignore", "remove"} and not isinstance(args.target, str): parser.error(f"{args.action} requires a non-empty pattern") ``` 2. Validate loaded configuration and retain only non-empty strings. Reject `null`, arrays, objects, and other unexpected types. 3. Compile every expression before saving it and return a controlled error for invalid syntax. 4. Enforce conservative maximum lengths for patterns and the total number of stored filters. 5. Prefer literal substring matching or a constrained glob format unless full regular expressions are essential to the declared functionality. 6. If arbitrary regular expressions must remain supported, use an engine or execution model with enforceable timeouts and resource limits. 7. Catch regular-expression errors during filtering so one malformed persisted value cannot terminate the entire summary. 8. Validate the entire configuration atomically before replacing the existing file, and write through a temporary file followed by an atomic rename. 9. Add tests for missing targets, JSON `null`, invalid syntax, catastrophic expressions, large filter sets, and adversarial commit subjects. ]]>
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
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (8)

Credential Access

High
Category
Privilege Escalation
Content
## Compatibility
- **Required tools:** `gh`
- **Environment variables:**
  - `GITHUB_PERSONAL_ACCESS_TOKEN` (required): GitHub Personal Access Token. Grant minimal scopes: `public_repo` for public repos, `repo` for private repos.

## Directory Structure
- `git_log_intelligence.py`: The primary script containing execution logic.
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
since_date = (datetime.now() - timedelta(days=int(days))).isoformat(timespec="seconds") + "Z"
    
    # Authenticate gh strictly via OpenClaw's token convention.
    env = os.environ.copy()
    token = env.get("GITHUB_PERSONAL_ACCESS_TOKEN")
    if not token:
        return "Error: GITHUB_PERSONAL_ACCESS_TOKEN is required for GitHub API access."
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill invokes shell commands, reads environment variables, and persists data to disk, but it does not declare any explicit tool restrictions or permission scope. That increases the attack surface because an orchestrator may permit broader capabilities than are actually needed, making misuse or future code changes harder to contain.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The description says to use the skill whenever the user asks about repo changes, changelogs, recent commits, PRs, or noisy patterns, which is broad enough to trigger in many common software-assistant contexts. Overbroad invocation can cause unnecessary access to tokens, shell execution, and persistent state changes when a narrower read-only or non-persistent workflow would suffice.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill allows adding and removing ignore patterns that are persisted in a local file, but the operational examples normalize these changes without a strong warning that they are lasting across sessions. Persistent hidden state can be abused to suppress future repository activity, hide relevant commits, or silently bias summaries beyond the current user request.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
print(f"Running command: {' '.join(cmd)}")
        print(f"Using filters: {filters}")

    result = subprocess.run(cmd, capture_output=True, text=True, env=env)
    
    if result.returncode != 0:
        return f"Error: {result.stderr}"
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Low
Confidence
87% confidence
Finding
The code reads `GITHUB_PERSONAL_ACCESS_TOKEN` from the environment and repackages it as `GH_TOKEN` for subprocess authentication. This is sensitive credential access, and the file does not include a docstring, comment aimed at user disclosure, or default user-facing message explaining that a personal access token will be used.

Missing User Warnings

Low
Confidence
83% confidence
Finding
The code invokes `gh api` to send the requested repository identifier and retrieve commit metadata from GitHub, which is a network operation involving user/system context. Although this behavior may be expected for a GitHub summarization tool, the file lacks a normal user-facing disclosure in the default path; only verbose mode prints the command being run.

Static analysis

No suspicious patterns detected.