Back to skill

Security audit

Git Federation Searcher

Security checks for vulnerabilities and agentic risk

Overview

The skill largely matches its search purpose, but it contains exploitable command execution and under-scoped network and credential handling risks that need review before installation.

Do not install this skill in a shared, bot-accessible, or privileged environment unless the shell=True fallback is removed, Telegram admin checks are added, custom instance URLs are restricted, and API tokens are moved out of plaintext config and URL parameters. There is no clear evidence of intentional exfiltration or destructive behavior, but the current implementation exposes high-impact abuse paths.

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
git_federation_searcher.py:258
Finding
Shell Command Injection Through the SearXNG Fallback Query<![CDATA[ ## Vulnerability Details **File Location**: `git_federation_searcher.py:258-260` **Vulnerability Type**: OS command injection **Risk Level**: High ### Vulnerable Code ```python search_query = f"site:codeberg.org OR site:gitea.com OR site:notabug.org {query}" cmd = f'SEARXNG_URL=http://127.0.0.1:8080 python3 /root/.openclaw/workspace/skills/searxng-bangs/scripts/search.py "{search_query}" --num 10' result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30) ``` ### Technical Analysis The `query` value originates from a command-line argument or Telegram command and is interpolated directly into a shell command. The command is then passed to `subprocess.run` with `shell=True`. The surrounding double quotation marks do not provide safe shell escaping. An attacker can include a closing quote followed by shell operators, command substitutions, redirections, or other shell syntax. Because the operating-system shell parses the resulting string, attacker-controlled text can become executable shell syntax rather than remaining a single search argument. The vulnerable fallback is reached through `_web_search`, which is invoked when API searches return no results. The Telegram handler explicitly calls this method with the user-controlled query: ```python web_results = self.searcher._web_search(query) ``` ### Attack Path 1. An attacker submits a crafted `/gitsearch` query through the Telegram integration or supplies a crafted CLI query. 2. The query contains shell syntax capable of terminating the quoted search argument and appending another command. 3. Searches against the configured Git instances return no results, causing the application to invoke `_web_search`. 4. `_web_search` concatenates the malicious query into `cmd`. 5. `subprocess.run(..., shell=True)` passes the complete string to the system shell. 6. The injected command executes with the same operating-system identity and privileges as the Skill process. ### Impact ...[truncated 721 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Remove `shell=True` and pass every command argument as a separate list element: ```python import os search_query = ( "site:codeberg.org OR site:gitea.com OR site:notabug.org " + query ) env = os.environ.copy() env["SEARXNG_URL"] = "http://127.0.0.1:8080" result = subprocess.run( [ "python3", "/root/.openclaw/workspace/skills/searxng-bangs/scripts/search.py", search_query, "--num", "10", ], env=env, capture_output=True, text=True, timeout=30, check=False, ) ``` Additional hardening measures should include: - Apply a reasonable maximum length to search queries. - Avoid constructing executable strings from user input. - Execute the Skill under a dedicated, unprivileged operating-system account. - Restrict filesystem and network access using container or sandbox controls. - Add regression tests containing quotes, command substitutions, semicolons, redirections, and newline characters to verify that they remain literal query data. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
git_federation_searcher.py:108
Finding
Unrestricted Custom Instance URLs Enable Server-Side Request Forgery<![CDATA[ ## Vulnerability Details **File Locations**: `git_federation_searcher.py:108-128`, `git_federation_searcher.py:153-162`, `git_search_commands.py:98-114` **Vulnerability Type**: Server-side request forgery and missing authorization **Risk Level**: Medium ### Vulnerable Code The application accepts and persists a caller-provided URL without validating its scheme, hostname, resolved address, or destination: ```python def add_instance(self, name: str, url: str, inst_type: str = "gitea", api_token: str = "") -> bool: """Add a new Git instance""" # Remove trailing slash url = url.rstrip('/') # Detect type from URL if not specified if inst_type == "auto": inst_type = self._detect_type(url) instance = GitInstance( name=name, url=url, type=inst_type, api_token=api_token ) # Test connection if self._test_instance(instance): self.instances[name.lower().replace(" ", "_")] = instance self.save_config() return True return False ``` The URL is subsequently requested by `curl`: ```python def _test_instance(self, instance: GitInstance) -> bool: """Test if instance is reachable""" try: result = subprocess.run( ["curl", "-s", "-m", "5", "-o", "/dev/null", "-w", "%{http_code}", f"{instance.url}/api/v1/version"], capture_output=True, text=True ) return result.stdout.strip() in ["200", "401", "403"] except: return False ``` The Telegram command exposes this operation without any visible administrator or user-authorization check: ```python name, url, inst_type = context.args[0], context.args[1], context.args[2] processing = await update.message.reply_text(f"➕ Teste {name}...") try: if self.searcher.add_instance(name, url, inst_type): await processing.edit_text(f"✅ Instanz '{name}' hinzugefügt!\n\nURL: {url}\nTyp: {inst_type}") else: ...[truncated 2431 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Implement both access control and strict destination validation: 1. Restrict `/gitadd`, instance removal, enabling, and disabling to explicitly configured administrators. 2. Require the `https` scheme and reject URLs containing embedded credentials. 3. Parse URLs with `urllib.parse.urlsplit` rather than relying on string concatenation. 4. Resolve the hostname and reject every address in loopback, private, link-local, multicast, reserved, unspecified, and special-use ranges. 5. Validate all resolved IPv4 and IPv6 addresses, not only the first result. 6. Disable redirects or validate the destination again after every redirect. 7. Re-resolve and revalidate the hostname immediately before each request to reduce DNS-rebinding risk. 8. Prefer an explicit allowlist of approved Git domains for deployments that do not require arbitrary private instances. 9. Apply outbound firewall rules so the Skill process cannot access metadata services, loopback administration ports, or unrelated internal networks. 10. Store configuration changes in an administrator-controlled workflow rather than allowing arbitrary chat users to persist endpoints. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
git_federation_searcher.py:96
Finding
Git API Tokens Are Stored in Plaintext and Transmitted in URL Query Strings<![CDATA[ ## Vulnerability Details **File Locations**: `git_federation_searcher.py:96-101`, `git_federation_searcher.py:180-182`, `git_federation_searcher.py:209-212` **Vulnerability Type**: Insecure credential storage and transport **Risk Level**: Medium ### Vulnerable Code The complete instance object, including `api_token`, is serialized to an ordinary JSON file without explicitly setting restrictive permissions: ```python def save_config(self): """Save configured instances""" SKILL_DIR.mkdir(parents=True, exist_ok=True) data = {name: asdict(inst) for name, inst in self.instances.items()} with open(CONFIG_FILE, 'w') as f: json.dump(data, f, indent=2) ``` For Gitea-compatible instances, the token is appended to the URL: ```python if instance.api_token: url += f"&access_token={instance.api_token}" result = subprocess.run( ["curl", "-s", "-m", "10", url], capture_output=True, text=True ) ``` For GitLab instances, the private token is also appended to the URL: ```python if instance.api_token: url += f"&private_token={instance.api_token}" result = subprocess.run( ["curl", "-s", "-m", "10", url], capture_output=True, text=True ) ``` ### Technical Analysis `GitInstance` includes an `api_token` field, and `asdict(inst)` places that field into the JSON configuration verbatim. The code does not explicitly create the file with mode `0600`, verify ownership, or use a credential store. Tokens are also embedded in request query strings. Although `subprocess.run` uses an argument list for these requests and therefore does not introduce shell injection, full URLs can be exposed through: - Process inspection facilities while `curl` is running. - HTTP access logs and reverse-proxy logs. - Monitoring and observability systems. - Error diagnostics. - Browser-like URL retention or intermediary logging. - Referrer or tracing data in some environments. Query-string credentials are especially sensitive because i ...[truncated 1404 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Do not persist raw tokens as part of the ordinary instance configuration. Instead: - Store only a secret identifier in `instances.json`. - Retrieve the actual token from an operating-system keyring, dedicated secret manager, or protected environment source when needed. - If local storage is unavoidable, create the file atomically with mode `0600`, enforce restrictive directory permissions, and verify file ownership before reading it. - Avoid writing secrets to logs, exceptions, or serialized dataclass output. - Rotate all tokens that may already have appeared in query strings or broadly accessible configuration files. Send credentials through headers rather than URL parameters. For example: ```python cmd = ["curl", "-s", "-m", "10"] if instance.type in {"gitea", "forgejo", "gogs"} and instance.api_token: cmd.extend(["-H", f"Authorization: token {instance.api_token}"]) elif instance.type == "gitlab" and instance.api_token: cmd.extend(["-H", f"PRIVATE-TOKEN: {instance.api_token}"]) cmd.append(url) result = subprocess.run( cmd, capture_output=True, text=True, ) ``` Also apply least-privilege provider scopes, short token lifetimes where supported, periodic rotation, and secret redaction in process monitoring and application telemetry. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (17)

Tool Parameter Abuse

High
Category
Tool Misuse
Content
search_query = f"site:codeberg.org OR site:gitea.com OR site:notabug.org {query}"
            cmd = f'SEARXNG_URL=http://127.0.0.1:8080 python3 /root/.openclaw/workspace/skills/searxng-bangs/scripts/search.py "{search_query}" --num 10'
            
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30)
            
            if result.returncode == 0:
                data = json.loads(result.stdout)
Confidence
99% confidence
Finding
The tool invocation at this line directly incorporates attacker-controlled query content into a shell command, allowing parameter abuse to become full command injection. Because this is in a search skill where arbitrary user input is expected, the context makes exploitation especially straightforward and severe.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill advertises capabilities consistent with network, shell, file read, and file write access, but it does not declare any explicit tool scope or permissions boundaries in the skill manifest. Because the skill supports adding custom instances and API token use, the lack of scoped permissions increases the risk of unintended outbound requests, unsafe command execution paths, or writing sensitive configuration data without clear operator control.

Description-Behavior Mismatch

Medium
Confidence
90% confidence
Finding
The skill persists instance configuration and API tokens under a local workspace path, which is more sensitive behavior than a simple transient search utility. In a multi-skill or shared environment, undisclosed credential storage increases the risk of token exposure and violates least surprise for users.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
API tokens supplied for private instances are serialized to instances.json in plaintext without warning, encryption, or access controls visible in the code. If the workspace is readable by other processes, users, backups, or logs, those tokens can be stolen and reused against private Git services.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def _detect_type(self, url: str) -> str:
        """Try to detect Git instance type"""
        try:
            result = subprocess.run(
                ["curl", "-s", "-m", "5", f"{url}/api/v1/version"],
                capture_output=True,
                text=True
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# Try GitLab
        try:
            result = subprocess.run(
                ["curl", "-s", "-m", "5", f"{url}/api/v4/version"],
                capture_output=True,
                text=True
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def _test_instance(self, instance: GitInstance) -> bool:
        """Test if instance is reachable"""
        try:
            result = subprocess.run(
                ["curl", "-s", "-m", "5", "-o", "/dev/null", "-w", "%{http_code}", 
                 f"{instance.url}/api/v1/version"],
                capture_output=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
User queries and optional access tokens are sent to remote Git instances, and the tokens are included in URL query strings instead of headers. This not only creates an undisclosed data-sharing/privacy issue but also increases leakage risk through process listings, HTTP logs, intermediary infrastructure, and server access logs.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if instance.api_token:
                    url += f"&access_token={instance.api_token}"
                
                result = subprocess.run(
                    ["curl", "-s", "-m", "10", url],
                    capture_output=True,
                    text=True
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if instance.api_token:
                    url += f"&access_token={instance.api_token}"
                
                result = subprocess.run(
                    ["curl", "-s", "-m", "10", url],
                    capture_output=True,
                    text=True
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
When primary searches fail, the skill silently forwards the user's query to a SearXNG service through an external script without warning. In context, this is riskier because it changes the destination and handling of user data unexpectedly and is implemented via a shell command that adds additional exploitation surface.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The fallback behavior expands the skill's capabilities from repository-instance search into arbitrary execution of another local script via a shell. In context, that broader execution path matters because it increases attack surface and creates an unexpected trust boundary crossing, especially when coupled with unsanitized user input.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
search_query = f"site:codeberg.org OR site:gitea.com OR site:notabug.org {query}"
            cmd = f'SEARXNG_URL=http://127.0.0.1:8080 python3 /root/.openclaw/workspace/skills/searxng-bangs/scripts/search.py "{search_query}" --num 10'
            
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30)
            
            if result.returncode == 0:
                data = json.loads(result.stdout)
Confidence
99% confidence
Finding
This builds a shell command containing the user-controlled query and executes it with shell=True, which enables command injection. An attacker can break out of the quoted search term and run arbitrary shell commands in the skill's execution context, making this materially more dangerous than the skill's stated search functionality.

Description-Behavior Mismatch

Medium
Confidence
89% confidence
Finding
The file exposes a Telegram bot command surface for the skill, which materially expands the attack surface beyond simple search functionality described in the skill metadata. A chat-driven interface can be invoked by any user the bot serves, and it forwards user-controlled input into backend search and instance-management logic, increasing the chance of misuse, data exposure, or abuse of network capabilities.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The /gitadd command allows users to add arbitrary Git instance URLs through chat, effectively exposing administrative configuration and outbound network access to untrusted users. If add_instance performs connectivity checks or stores the instance for later searches, this can enable SSRF, internal network probing, persistence of attacker-controlled endpoints, and expansion of future search activity toward malicious or private hosts.

Description-Behavior Mismatch

Low
Confidence
82% confidence
Finding
The manifest description in _meta.json narrows the skill to searching self-hosted Git instances '(Gitea, Forgejo, GitLab)'. However, the provided skill context states it also aggregates results from public services like Codeberg.org, Gitea.com, OpenDev, NotABug, and Gitdab and supports fallback to SearXNG web search, which is materially broader than the manifest text.

Natural-Language Policy Violations

Low
Confidence
98% confidence
Finding
The skill emits user-facing strings such as "Git-Suche", "Ergebnisse", and other CLI messages exclusively in German. This imposes a specific language on users without offering a locale choice or documenting a justified region-specific constraint.

Static analysis

No suspicious patterns detected.