Back to skill

Security audit

GitHub Semantic Search

Security checks for vulnerabilities and agentic risk

Overview

This GitHub assistant is mostly purpose-aligned, but it has under-disclosed high-impact behavior around external Feishu alerts, authenticated GitHub CLI use, and destructive local index operations.

Install only if you are comfortable letting the skill query GitHub through your authenticated gh session, store issue and PR text in a local Qdrant collection, send text to local Ollama for embeddings, and potentially forward repository activity to Feishu. Before use, remove the hard-coded Feishu recipient, add explicit repo validation, change subprocess calls to argument lists, and avoid running init on an existing collection unless you intend to delete and recreate it.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (4)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/github_indexer.py:28
Finding

GitHub CLI Argument Injection in Repository Indexing

Content
View full analysis
dict: result = subprocess.run( f'"{GH_EXE}" {args}', capture_output=True, encoding="utf-8", errors="replace" ) ``` Representative user-controlled call: ```python def fetch_issues(repo: str, state: str = "all", limit: int = 100) -> List[GitHubItem]: data = gh_list(f"issue list --repo {repo} --state {state} --limit {limit} --json number,title,body,state,author,labels,url,createdAt,updatedAt") ``` Related call sites construct commands in the same manner: ```python data = gh_list(f"pr list --repo {repo} --state {state} --limit {limit} --json number,title,body,state,author,labels,url,createdAt,updatedAt,isDraft") ``` ```python data = gh(f"repo view {repo} --json name,description,stargazerCount,url,languages,repositoryTopics") ``` ### Technical Analysis The repository name and other arguments are interpolated into a single command-line string. On the intended Windows environment, process argument parsing can interpret spaces and quotes in an attacker-controlled repository value as argument boundaries. The input is not validated as an `owner/repository` identifier. Because `shell=False` is used, this is not a confirmed shell-metacharacter or arbitrary operating-system command injection vulnerability. It is an argument-injection weakness: crafted input may introduce additional GitHub CLI flags or otherwise alter the intended request. The command executes under the operator's existing authenticated GitHub CLI identity. ### Attack Path 1. An attacker able to invoke the script supplies a crafted value for the positional `repo` argument. 2. The value passes through `argparse` without repository-format validation. 3. ` ...[truncated 705 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/github_search.py:103
Finding

GitHub CLI Argument Injection in CI Status Lookup

Content
View full analysis
str: try: result = subprocess.run( f'"{GH_EXE}" run list --repo {repo} --limit {limit} --json name,status,conclusion,createdAt,headBranch,url', capture_output=True, encoding="utf-8", errors="replace" ) ``` The unvalidated value originates from the command line: ```python parser.add_argument("--repo", help="Filter by repo (owner/repo)") ``` It reaches the subprocess when CI output is requested: ```python if args.ci and args.repo: print(get_ci_status(args.repo, limit=5)) ``` ### Technical Analysis The user-supplied repository value is inserted into a single command-line string. In the intended Windows environment, spaces or quotes in this value can affect argument boundaries and introduce unintended GitHub CLI options. The use of `shell=False` limits this to argument injection rather than confirmed arbitrary shell-command execution. Nevertheless, GitHub CLI still runs with the operator's authenticated credentials and may accept injected options that alter the intended API request. ### Attack Path 1. An attacker or untrusted caller invokes the search command with `--ci` and a crafted `--repo` value. 2. The repository value is accepted without syntax validation. 3. `get_ci_status()` concatenates it into the `gh run list` command string. 4. Process argument parsing may treat portions of the value as additional CLI arguments. 5. GitHub CLI performs the modified CI lookup with the operator's authenticated permissions. ### Impact Assessment A successful attack may cause CI information to be requested from an unintended repository or change supported listing behavior. For accounts with access to private repositories, this may expose workflow names, branches, c ...[truncated 182 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/github_monitor.py:51
Finding

Unvalidated Monitor State Enables GitHub CLI Argument Injection

Content
View full analysis
dict: result = subprocess.run( f'"{GH_EXE}" {args}', capture_output=True, text=True, shell=False ) if result.returncode != 0: return {} try: return json.loads(result.stdout) if result.stdout.strip() else {} except: return {} def gh_list(args: str) -> list: result = subprocess.run( f'"{GH_EXE}" {args}', capture_output=True, text=True, shell=False ) ``` Repository values loaded from monitor state reach the command builder: ```python def check_new_issues(repo: str, since: str) -> list: """Find issues created after `since`.""" items = gh_list(f"issue list --repo {repo} --state open --limit 50 --json number,title,body,createdAt,labels,url,author") return [i for i in items if i.get("createdAt", "") > since] def check_new_prs(repo: str, since: str) -> list: """Find PRs created after `since`.""" items = gh_list(f"pr list --repo {repo} --state open --limit 50 --json number,title,body,createdAt,labels,url,author,isDraft") return [i for i in items if i.get("createdAt", "") > since] def check_ci_failures(repo: str, since: str) -> list: """Find failed CI runs after `since`.""" items = gh_list(f"run list --repo {repo} --limit 30 --json id,name,status,conclusion,createdAt,headBranch,url") ``` ### Technical Analysis Repository values are stored in a writable JSON state file and restored without schema or syntax validation. The restored value is concatenated into GitHub CLI command strings. On the intended Windows platform, crafted whitespace or quoting can affect argument parsing and add unintended CLI option ...[truncated 1455 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/github_monitor.py:73
Finding

Hard-Coded Feishu Recipient Can Receive Repository Activity

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (27)

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

A second description-behavior mismatch indicates the skill markets an Embedder+Qdrant+LLM assistant while the underlying behavior is narrower and different. Overstated AI and indexing claims can mislead users into granting trust and permissions under false assumptions, which is especially risky for a GitHub-integrated skill handling repository metadata and workflow context.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

A second description-behavior mismatch indicates the skill markets an Embedder+Qdrant+LLM assistant while the underlying behavior is narrower and different. Overstated AI and indexing claims can mislead users into granting trust and permissions under false assumptions, which is especially risky for a GitHub-integrated skill handling repository metadata and workflow context.

Content

No source excerpt is available for this finding.

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
95% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

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

md
python github_indexer.py add owner/repo --repo        # Index repo metadata
  python github_indexer.py add owner/repo --all         # Index all
  python github_indexer.py status                      # Show indexed repos
  python github_indexer.py rm owner/repo               # Remove repo from index
"""
import argparse
import json

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
95% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · scripts/github_indexer.py (reported line 11)May include surrounding context.

python
python github_indexer.py add owner/repo --repo        # Index repo metadata
  python github_indexer.py add owner/repo --all         # Index all
  python github_indexer.py status                      # Show indexed repos
  python github_indexer.py rm owner/repo               # Remove repo from index
"""
import argparse
import json

Missing User Warnings

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The init command always calls ensure_collection(qc, force=True), which deletes any existing Qdrant collection before recreating it. This creates a destructive operation with no confirmation, backup, or safety check, making accidental or automated data loss likely in an agent-driven environment.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill advertises shell, network, file read/write, and environment-capable behavior but does not declare any explicit tool scope or permission boundaries. In an agent setting, this increases the chance of overbroad execution, data access, or unintended side effects because the operator cannot tell what capabilities are intended or restricted.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The monitoring feature mentions Feishu alerts but does not warn that repository activity or matched content may be transmitted to an external messaging platform. This can leak confidential project information, issue titles, CI failures, or keywords to third-party systems and audiences beyond GitHub's original access model.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The monitoring feature mentions Feishu alerts but does not warn that repository activity or matched content may be transmitted to an external messaging platform. This can leak confidential project information, issue titles, CI failures, or keywords to third-party systems and audiences beyond GitHub's original access model.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
97% confidence
Finding

The code constructs a command string with untrusted input (args) and passes it to subprocess.run without an argument list. Because user-controlled repository names are interpolated into CLI arguments such as --repo {repo}, this creates a command-injection risk if shell parsing is used by the platform/runtime; at minimum it is unsafe command construction around external process execution.

Content

Scanner excerpt · scripts/github_indexer.py (reported line 31)May include surrounding context.

python
def gh(args: str) -> dict:
    result = subprocess.run(
        f'"{GH_EXE}" {args}',
        capture_output=True, encoding="utf-8", errors="replace"
    )

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
97% confidence
Finding

This repeats the same unsafe pattern in gh_list: a formatted command string includes untrusted arguments and is sent to subprocess.run. In a GitHub assistant context where repo identifiers may originate from user prompts or other automation, this increases the chance of attacker-controlled input reaching process execution.

Content

Scanner excerpt · scripts/github_indexer.py (reported line 47)May include surrounding context.

python
def gh_list(args: str) -> list:
    result = subprocess.run(
        f'"{GH_EXE}" {args}',
        capture_output=True, encoding="utf-8", errors="replace"
    )

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The indexer sends issue, PR, and repository text to a local embedding service with no consent, classification, or redaction step. Even though the endpoint is localhost, this still exports potentially sensitive repository content to another service boundary, which may log, retain, or further process private data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The rm command deletes all indexed items for a repository immediately with no confirmation or dry-run mode. In an assistant skill, where commands may be triggered from natural language workflows, lack of friction on destructive actions increases the chance of accidental deletion or abuse.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
94% confidence
Finding

The code constructs a single command string from a caller-controlled args value and passes it to subprocess.run with shell=False. On Python, supplying a string with shell=False still delegates parsing to the target process/runtime rather than safely separating arguments, so untrusted repo names or other fields embedded into args can become argument-injection into gh (e.g. adding extra flags or changing behavior). In this skill, repository identifiers come from user input and persisted state, which makes the monitor context materially increase exploitability.

Content

Scanner excerpt · scripts/github_monitor.py (reported line 48)May include surrounding context.

python
def gh(args: str) -> dict:
    result = subprocess.run(
        f'"{GH_EXE}" {args}',
        capture_output=True, text=True, shell=False
    )

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
94% confidence
Finding

This has the same unsafe pattern as gh(): a single formatted command string is built from untrusted args and executed via subprocess.run. Because args is derived from values like repo that originate from CLI input or local state, an attacker can inject additional gh options or alter which repository/data is queried, leading to unintended command execution semantics and possible data exposure.

Content

Scanner excerpt · scripts/github_monitor.py (reported line 61)May include surrounding context.

python
def gh_list(args: str) -> list:
    result = subprocess.run(
        f'"{GH_EXE}" {args}',
        capture_output=True, text=True, shell=False
    )

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
87% confidence
Finding

The Feishu sender also builds a single command string containing user-controlled message and potentially user_id, then executes it with subprocess.run. Even though json.dumps reduces some quoting issues, this still relies on downstream parsing of a flat command string and can permit argument confusion or malformed delivery; additionally, it transmits potentially sensitive repository content to an external messaging channel.

Content

Scanner excerpt · scripts/github_monitor.py (reported line 76)May include surrounding context.

python
def send_feishu(message: str, user_id: str = "ou_4bf3393b288ddc97e3bfe1759bf99f43"):
    """Send alert to Feishu via openclaw."""
    result = subprocess.run(
        f'openclaw message send --channel feishu --to user:{user_id} --message {json.dumps(message)}',
        capture_output=True, text=True, shell=False
    )

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The docstring says this function will 'Format alert as readable text', implying it can format the alerts produced by the monitor. However, CI failure alerts created in L165-L174 do not include 'number' or 'title', while the formatter unconditionally accesses alert['number'] and alert['title'] on L188, contradicting the documented intent for this formatter in the context of all alert types used by the script.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

High-priority alerts are automatically sent to Feishu and include issue/PR titles, labels, URLs, workflow names, and potentially body-derived keyword context without an explicit confirmation step at execution time. In a GitHub monitoring skill, repository content may be private or sensitive, so automatic forwarding to an external messaging platform increases data-leak risk, especially if users do not realize the check path exfiltrates data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The function posts the user's natural-language query to an HTTP service at localhost:11434 to generate embeddings. Although the behavior is part of semantic search, there is no explicit warning in the docstring or inline comments that user-supplied text is transmitted to another service.

Content

No source excerpt is available for this finding.

Internal Network Request

Medium
Category
Server-Side Request Forgery
Confidence
70% confidence
Finding

Code issues a request to a loopback, link-local, or private-range host. This can reach internal services not meant to be exposed and is a common SSRF pivot.

Content

Scanner excerpt · scripts/github_indexer.py (reported line 110)May include surrounding context.

python
def get_embedding(text: str) -> list:
    payload = {"model": EMBED_MODEL, "prompt": text}
    req = urllib.request.Request(
        "http://localhost:11434/api/embeddings",
        data=json.dumps(payload).encode(),
        headers={"Content-Type": "application/json"},

Internal Network Request

Medium
Category
Server-Side Request Forgery
Confidence
70% confidence
Finding

Code issues a request to a loopback, link-local, or private-range host. This can reach internal services not meant to be exposed and is a common SSRF pivot.

Content

Scanner excerpt · scripts/github_search.py (reported line 34)May include surrounding context.

python
def get_embedding(text: str) -> list:
    payload = {"model": EMBED_MODEL, "prompt": text}
    req = urllib.request.Request(
        "http://localhost:11434/api/embeddings",
        data=json.dumps(payload).encode(),
        headers={"Content-Type": "application/json"},

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The helper executes the GitHub CLI via subprocess, which is a safety-relevant operation for code files under this rule. While the script has a top-level docstring describing search usage, it does not disclose that it will invoke an external executable, and this helper itself has no confirmation prompt, comment, or warning.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
95% confidence
Finding

The gh(args) helper builds a command string by concatenating arbitrary args into subprocess.run without argument separation or validation. If this helper is ever called with untrusted input, it can enable command/argument injection or unintended GitHub CLI operations, and the function is broader than needed because it allows effectively arbitrary gh subcommands.

Content

Scanner excerpt · scripts/github_search.py (reported line 69)May include surrounding context.

python
def gh(args: str) -> dict:
    result = subprocess.run(
        f'"{GH_EXE}" {args}',
        capture_output=True, encoding="utf-8", errors="replace"
    )

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The file is documented and presented as a semantic search tool for indexed GitHub data, but it also invokes GitHub CLI to fetch live workflow run status when --ci is used. While repo monitoring is mentioned in the broader skill manifest, this specific script's stated purpose and docstring are limited to natural-language search, so the added live CI inspection behavior exceeds the described operation of this file.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
86% confidence
Finding

When --ci is used, the script runs an external GitHub CLI command to query workflow runs for a repository. This is a subprocess-based operation and the file lacks any warning or explanatory comment disclosing that the flag triggers external command execution and data retrieval.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
90% confidence
Finding

The CI status command interpolates repo directly into a subprocess command string. Although argparse constrains typical usage, using a string command instead of an argv list still creates unnecessary injection risk and can cause unintended gh behavior if repo is attacker-controlled in another invocation context.

Content

Scanner excerpt · scripts/github_search.py (reported line 105)May include surrounding context.

python
def get_ci_status(repo: str, limit: int = 5) -> str:
    try:
        result = subprocess.run(
            f'"{GH_EXE}" run list --repo {repo} --limit {limit} --json name,status,conclusion,createdAt,headBranch,url',
            capture_output=True, encoding="utf-8", errors="replace"
        )