Back to skill

Security audit

ebusy-courts

Security checks for vulnerabilities and agentic risk

Overview

This tennis-court lookup skill is mostly purpose-aligned, but it can send your eBusy password to any configured URL despite claiming eBusy-only network access.

Review this skill before installing. It needs your eBusy username and password, and the current code should only be used where you fully control EBUSY_BASE_URL or the runtime enforces egress to real https://*.ebusy.de hosts. Prefer a dedicated low-privilege eBusy account, remove unnecessary filesystem read permission, and validate the base URL before any login request.

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

T09 · Insecure Skill Coding Practices

Error
Location
ebusy_api.py:39
Finding

Unvalidated Base URL Allows Credential Exfiltration and Server-Side Request Forgery

Content
View full analysis

Vulnerability Details

File Location: ebusy_api.py, lines 39-75
Vulnerability Type: Unrestricted credential destination and server-side request forgery
Risk Level: High

Vulnerable Code

python
BASE_URL = os.getenv("EBUSY_BASE_URL", "https://medenhalle.ebusy.de")
USERNAME = os.getenv("EBUSY_USERNAME")
PASSWORD = os.getenv("EBUSY_PASSWORD")
COURT_ID = int(os.getenv("EBUSY_COURT_ID", "1"))
FIRST_COURT_NO = int(os.getenv("EBUSY_FIRST_COURT_NO", "1"))


class EbusyAPI:
    def __init__(self, base_url: str | None = None):
        self.base_url = base_url or BASE_URL
        self.session = requests.Session()

    def login(self, username: str, password: str) -> bool:
        """Log into the eBusy instance using CSRF token + session.

        Returns True on success, False otherwise.
        """
        login_page = self.session.get(f"{self.base_url}/login")
        login_page.raise_for_status()

        soup = BeautifulSoup(login_page.text, "html.parser")
        csrf_el = soup.find("input", {"name": "_csrf"})
        if not csrf_el or not csrf_el.get("value"):
            raise RuntimeError("Could not find CSRF token on login page")

        csrf_token = csrf_el["value"]

        login_data = {
            "username": username,
            "password": password,
            "_csrf": csrf_token,
            "remember-me": "on",
        }

        response = self.session.post(
            f"{self.base_url}/login",
            data=login_data,
            headers={"Accept": "application/xml, text/xml"},
        )

Technical Analysis

EBUSY_BASE_URL is accepted directly from the environment and used to construct both the initial login-page request and the request containing the configured username and password. The code does not validate the URL scheme, hostname, port, embedded user information, resolved IP address, or redirect destination.

This contradicts th ...[truncated 2220 chars]

Remediation
View remediation

Remediation Suggestions

  1. Parse the configured URL with urllib.parse.urlsplit before creating any request.
  2. Require the scheme to be exactly https.
  3. Require a nonempty hostname equal to ebusy.de or ending exactly in .ebusy.de. A plain substring or endswith("ebusy.de") check is insufficient because it may accept deceptive domains.
  4. Reject embedded usernames or passwords, fragments, unexpected paths, and nonstandard ports unless a specific port is demonstrably required.
  5. Resolve the hostname and reject loopback, private, link-local, multicast, reserved, and unspecified IP addresses. Consider DNS rebinding when designing this control.
  6. Disable automatic redirects with allow_redirects=False, or manually validate the scheme, host, port, and resolved address of every redirect before following it.
  7. Enforce the same allowlist through runtime egress controls so application validation is not the only security boundary.
  8. Remove "remember-me": "on" unless persistent server-side login is required for the availability query.
  9. Add tests covering HTTP URLs, unrelated domains, deceptive suffixes, embedded credentials, localhost, private IP addresses, alternate ports, and cross-origin redirects.

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
mcporter.json:7
Finding

Unnecessary Generic Filesystem Read Permission Violates Least Privilege

Content
View full analysis

Vulnerability Details

File Location: mcporter.json, line 7
Vulnerability Type: Excessive filesystem permission
Risk Level: Medium

Vulnerable Code

json
{
  "name": "ebusy-courts",
  "description": "Query eBusy-based tennis hall bookings",
  "permissions": {
    "env": ["EBUSY_BASE_URL", "EBUSY_USERNAME", "EBUSY_PASSWORD", "EBUSY_COURT_ID", "EBUSY_FIRST_COURT_NO"],
    "network": ["https://*.ebusy.de"],
    "fs": ["read"]
  }
}

Technical Analysis

The Skill's Python implementation does not read files at runtime. Its declared functionality only requires environment-variable access and network communication with an eBusy instance. Nevertheless, the manifest requests generic filesystem read permission.

The documentation states that filesystem access is limited to the Skill's own files, but the manifest does not express any path-level restriction. If the runtime interprets ["read"] as unrestricted filesystem read access, this grants broader authority than the declared court-availability task requires.

Excessive permission is a security boundary issue even when the current Python script does not directly exercise it. A compromised agent session, future vulnerable code, or manipulated invocation could potentially use the granted capability to inspect unrelated files.

Attack Path

  1. The Skill is installed or invoked with the permissions declared in mcporter.json.
  2. The host runtime grants generic filesystem read access to the Skill or associated agent.
  3. An attacker gains influence over the agent through a separate prompt-injection, configuration, or code-compromise path.
  4. The attacker directs the privileged context to read files unrelated to eBusy availability checks.
  5. Data from accessible configuration, credential, or user files may be exposed through agent output or another available channel.

This finding does not establish that such a separate compromise already ...[truncated 668 chars]

Remediation
View remediation

Remediation Suggestions

  1. Remove the fs permission because the current runtime implementation does not require filesystem access.
  2. If the platform requires access to packaged metadata, replace generic read permission with an explicit allowlist containing only the exact Skill files and read-only operations.
  3. Verify the runtime's interpretation of fs: ["read"]; do not rely on documentation text to impose restrictions absent from the enforceable manifest.
  4. Add a permission regression check that compares declared capabilities with filesystem, network, environment, and process operations actually used by the implementation.
  5. Keep credential-bearing .env files outside the Skill package and ensure they are not made readable through broad Skill permissions.
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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (15)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- `SKILL.md` – this documentation

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- `SKILL.md` – this documentation

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
- `SKILL.md` – this documentation

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 · SKILL.md (reported line 102)May include surrounding context.

md
- EBUSY_FIRST_COURT_NO optional: base court number in that module (e.g. 2135)

These can be provided via:
- a .env file loaded by your environment/venv,
- export in your shell profile,
- or the OpenClaw gateway/agent env configuration.

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 · SKILL.md (reported line 125)May include surrounding context.

md
- EBUSY_FIRST_COURT_NO optional: base court number in that module (e.g. 2135)

These can be provided via:
- a .env file loaded by your environment/venv,
- export in your shell profile,
- or the OpenClaw gateway/agent env configuration.

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 · ebusy_api.py (reported line 25)May include surrounding context.

python
- EBUSY_FIRST_COURT_NO optional: base court number in that module (e.g. 2135)

These can be provided via:
- a .env file loaded by your environment/venv,
- export in your shell profile,
- or the OpenClaw gateway/agent env configuration.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill documentation describes access to environment variables and outbound network use, but the skill does not declare an explicit tool scope or permissions boundary in the documented interface. In agent ecosystems, missing permission declarations can cause the runtime or reviewers to underestimate the skill's capabilities, increasing the risk of unintended secret access and arbitrary outbound requests.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The documentation asserts network access is limited to https://*.ebusy.de, but the configuration model allows an arbitrary EBUSY_BASE_URL to be supplied. If the client trusts that variable without validation, an operator or compromised agent could direct authenticated requests and possibly credentials to an attacker-controlled host.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

This markdown mixes English documentation with German-only user request examples and German-only safety guidance (for example, the invocation example at L139-L140 and the security section at L154-L170). Because the skill does not state that it is intentionally limited to German users or offer a language/locale choice, this is a natural-language locale policy concern.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
93% confidence
Finding

The safety section claims credentials are only provided by the OpenClaw agent, but earlier instructions explicitly tell users to source secrets from a local shell or .env file. This inconsistency can mislead reviewers about the actual secret-handling model and weaken operational safeguards around local credential exposure.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Low
Category
Not specified by scanner
Confidence
93% confidence
Finding

The docstring says running the module directly will 'print all reservations for the configured module/date'. In practice, main() and check() transform reservation data into aggregate availability counts, per-court status, filtered time-window results, and JSON summaries rather than simply listing reservations.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

The documented direct-script behavior explicitly promises printing reservations. However, the code at main() builds court_status, counts available/unavailable courts, separates available and unavailable lists, and prints a synthesized JSON result, which contradicts that stated behavior.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The script performs a remote login and reservation fetch using username and password from environment variables, which causes credentialed network activity and transmission of account data. Although the module docstring documents the required environment variables, the execution path in main() provides no user-facing notice or confirmation before contacting the remote service.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The check() path also authenticates with credentials from environment variables and issues HTTP requests to the eBusy service, but it does so without any user-facing warning in that command path. This is a safety-relevant operation because it uses sensitive environment variables and transmits them to an external service.

Content

No source excerpt is available for this finding.

Vague Triggers

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

The manifest description 'Query eBusy-based tennis hall bookings' describes a general capability but does not specify concrete trigger phrases, scope limits, or exclusion conditions. In a manifest file, this can be ambiguous enough to overlap with broad booking-related requests and may lead to unintended activation.

Content

No source excerpt is available for this finding.