Back to skill

Security audit

Moodle Connector Skill

Security checks for vulnerabilities and agentic risk

Overview

This Moodle connector mostly matches its stated purpose, but it has serious credential and filesystem safety issues users should review before installing.

Install only in an isolated, non-admin environment. Do not use real Moodle credentials until the download tool is restricted to trusted Moodle URLs, output paths are constrained to a dedicated download directory, default test-pass is removed, and tokens/credentials are stored outside plaintext config with restrictive permissions.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (5)

T09 · Insecure Skill Coding Practices

Error
Location
moodle_connector.py:513
Finding

Moodle authentication token disclosed to caller-controlled network destinations

Content
View full analysis
Path: """ Download a Moodle file (PDF, doc, etc.) with local caching. Returns the local path. """ # Build a stable filename from the URL url_hash = hashlib.sha256(file_url.encode()).hexdigest()[:16] # Try to get a meaningful name from the URL url_name = file_url.split("/")[-1].split("?")[0] or "file" filename = f"{url_hash}_{url_name}" if dest is None: dest = CACHE_DIR / "files" / filename dest.parent.mkdir(parents=True, exist_ok=True) if dest.exists(): log.debug("File already cached: %s", dest) return dest # Append token if not already present sep = "&" if "?" in file_url else "?" url_with_token = f"{file_url}{sep}token={self.token}" log.info("Downloading: %s → %s", file_url, dest.name) try: with self.session.get(url_with_token, stream=True, timeout=60) as r: ``` The unrestricted URL is reachable through the public connector method: ```python def download(self, url: str, output: Optional[str] = None) -> str: dest = Path(output) if output else None path = self.api.download_file(url, dest) ``` The MCP interface permits its client to supply that URL: ```python elif name == "download": url = arguments.get("url") output = arguments.get("output") result = connector.download(url, output) ``` ### Technical Analysis The downloader accepts a caller-controlled URL without parsing its scheme, hostname, port, or path. It unconditionally adds the active Moodle web-service token to the query string and sends the request. Consequently, a caller can direct ...[truncated 1715 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
moodle_connector.py:513
Finding

Caller-controlled output paths permit arbitrary file creation

Content
View full analysis
Path: """ Download a Moodle file (PDF, doc, etc.) with local caching. Returns the local path. """ # Build a stable filename from the URL url_hash = hashlib.sha256(file_url.encode()).hexdigest()[:16] # Try to get a meaningful name from the URL url_name = file_url.split("/")[-1].split("?")[0] or "file" filename = f"{url_hash}_{url_name}" if dest is None: dest = CACHE_DIR / "files" / filename dest.parent.mkdir(parents=True, exist_ok=True) ``` ```python with self.session.get(url_with_token, stream=True, timeout=60) as r: r.raise_for_status() with dest.open("wb") as f: for chunk in r.iter_content(chunk_size=65536): f.write(chunk) ``` The public method converts the caller's output string directly into a filesystem path: ```python def download(self, url: str, output: Optional[str] = None) -> str: dest = Path(output) if output else None path = self.api.download_file(url, dest) ``` The batch downloader has a related traversal condition: ```python for module in config.get('downloads', []): module_name = module.get('module', 'Unknown') module_dir = output_dir / module_name.replace(' ', '_') module_dir.mkdir(parents=True, exist_ok=True) for file_info in module.get('files', []): filename = file_info.get('name', 'file') url = file_info.get('url') target_path = module_dir / filename ``` ### Technical Analysis The MCP `output` argument is interpreted as an unrestricted `Path`. Absolute paths are accepted, and relative paths containing `..` are not rejected. Parent directories are created au ...[truncated 1938 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
mcp_server.py:34
Finding

Known default password undermines encrypted credential storage

Content
View full analysis
MoodleConnector: """Get or create the connector instance""" global _connector if _connector is None: _connector = MoodleConnector( config_path=Path('config.json'), password='test-pass' ) return _connector ``` The batch downloader exposes the same known value as its default: ```python parser.add_argument( '--password', default='test-pass', help='Encryption password for credentials (default: test-pass)' ) ``` ### Technical Analysis Credential records are encrypted with a key derived from the supplied password and a salt. PBKDF2 and Fernet are reasonable primitives, but their security depends on the password remaining secret and sufficiently strong. The MCP server always supplies the publicly known string `test-pass`. It does not read the documented `MOODLE_CRED_PASSWORD` environment variable. The batch downloader also selects `test-pass` by default, making its later environment-variable fallback ineffective under ordinary invocation because `args.password` is already nonempty. A random salt prevents precomputation across files but does not protect data encrypted under a known password. Anyone who obtains `credentials.enc` can derive the same key and decrypt it. ### Attack Path 1. The MCP server or batch downloader creates or updates `credentials.enc` using the default password. 2. An attacker obtains a copy through local filesystem access, a backup, an archive, or accidental publication. 3. The attacker reads the salt from the first 16 bytes, as defined by the credential format. 4. The attacker runs the same PBKDF2 derivation using the known value `test-pass`. 5. The resulting Fernet key decrypts ...[truncated 609 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
moodle_connector.py:176
Finding

Normal setup stores the Moodle API token in plaintext configuration

Content
View full analysis
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding

Install process executes unpinned third-party dependencies and browser artifacts

Content
View full analysis
=2.31.0 cryptography>=41.0.0 playwright>=1.40.0 mcp>=0.1.0 ``` ```json "scripts": { "install": "pip install -r requirements.txt && playwright install chromium" } ``` ### Technical Analysis Every Python dependency uses an open-ended minimum version and lacks an integrity hash. A future installation can therefore resolve to package releases that were not part of this audit. The package installation hook automatically invokes `pip` and then downloads a Chromium build through Playwright. Installing Python packages and a browser is broadly related to the declared functionality, but executing mutable third-party installation paths automatically is not reproducible and increases the install-time trust boundary. Python packages may execute build backends or setup logic, while the Playwright command installs a substantial externally retrieved binary artifact. The reviewed dependency names are consistent with imports in the source, and no typosquatted or attacker-specific package was identified. The risk arises from unbounded version selection, missing hashes, and automatic execution rather than evidence that a currently named dependency is malicious. ### Attack Path 1. A user installs the package through an environment that runs the declared install script. 2. The script invokes `pip install -r requirements.txt`. 3. The resolver selects currently available versions satisfying the open-ended constraints. 4. Package build or installation logic executes with the installing user's privileges. 5. The script then invokes Playwright to retrieve and install Chromium. 6. If an upstream account, release, index response, build artifact, or delivery channel is compromised, malicious code can execute or be ins ...[truncated 576 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
Findings (23)

Tainted flow: 'password' from os.environ.get (line 890, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · moodle_connector.py (reported line 233)May include surrounding context.

python
"""Try Moodle's /login/token.php endpoint (works for local accounts)."""
        url = f"{base_url.rstrip('/')}/login/token.php"
        try:
            resp = requests.post(url, data={
                "username": username,
                "password": password,
                "service": "moodle_mobile_app",

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding

The code clearly matches much of the declared Moodle client behavior: it authenticates, calls Moodle REST endpoints, fetches courses/grades/assignments/announcements/materials/deadlines, caches responses, and downloads files. However, the declared description also says it is an MCP server for Claude Code integration, and there is no MCP server implementation, server loop, protocol handling, or Claude-specific integration in this chunk. Additionally, while it includes a downloader, the visible functionality is primarily a CLI client with a single-file download command rather than an explicit batch downloader service. Therefore the description materially overstates capabilities not present in the supplied code.

Content

No source excerpt is available for this finding.

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
85% 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 204)May include surrounding context.

md
Run: `python moodle_connector.py login` for manual token retrieval.

### File download stuck
Check network. Increase timeout in code or clear cache: `rm -rf cache/`

## License

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
85% 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 232)May include surrounding context.

md
Run: `python moodle_connector.py login` for manual token retrieval.

### File download stuck
Check network. Increase timeout in code or clear cache: `rm -rf cache/`

## License

MCP Config Access

High
Category
Agent Snooping
Confidence
80% confidence
Finding

Skill accesses MCP server configuration files (mcp.json). MCP configs contain server URLs, authentication tokens, and tool definitions — reading them allows the skill to discover and potentially abuse other tool integrations.

Content

Scanner excerpt · mcp_server.py (reported line 48)May include surrounding context.

python
@server.list_tools()
async def list_tools() -> list[Tool]:
    """List available MCP tools"""
    return [
        Tool(
            name="courses",

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

python
Strategy:
    1. Try MSAL device-code flow (headless-friendly).
    2. If a Playwright browser is available, use it for interactive MFA.
    3. Cache the access token and refresh it automatically.
    """

    AUTH_BASE = "https://login.microsoftonline.com"

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
79% confidence
Finding

The skill documents capabilities that require environment access, filesystem reads/writes, and network access, but it does not declare any explicit tool scope or permission boundaries. In an agent ecosystem, this weakens least-privilege controls and makes it easier for the skill to access sensitive local files, secrets, or external endpoints without clear user review.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The documentation encourages downloading files and running a batch downloader without a clear warning that these actions will create directories and write content to disk. In an agent-assisted context, insufficient disclosure can lead users to authorize operations that consume storage, overwrite expected locations, or persist untrusted files unintentionally.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The MCP setup instructs users to place a credential password directly into configuration without prominently warning that this is sensitive secret material. Secrets embedded in config files are often exposed through version control, logs, screenshots, backups, or local compromise, and this is especially risky in agent tooling where configs may be shared or inspected.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

This code creates output directories and later writes downloaded files to them, which can affect user data on disk. Although progress is printed during download, there is no explicit warning or confirmation that running the script will create directories and save files under the specified output path.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
81% confidence
Finding

This file presents itself as a generic batch downloader that reads a download list and fetches files, but it also handles credential-secret acquisition via a password argument, environment-variable lookup, and interactive prompt before initializing the connector. Secret-management behavior is not described in this file's stated purpose and goes beyond a simple downloader's documented role.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The MCP server exposes a download tool that can write arbitrary Moodle-fetched content to a caller-supplied local path, but the tool description and handler provide no warning, confirmation, or path restrictions. In an MCP context, this increases the risk that a client or prompt-influenced agent causes unintended filesystem writes, potentially overwriting local files or planting data in sensitive locations.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The tool persists sensitive material to disk, including encrypted credentials and cached Moodle/API data, but does not enforce restrictive filesystem permissions or clearly warn users at the point of use. On multi-user systems or shared workspaces, other local users or processes may access these files, exposing academic records, tokens, or stored credentials.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
96% confidence
Finding

The file first states the project is MIT licensed and then says contributors must agree to license work under GPLv3 (L216), but later repeats the same contribution section with 'Agree to license your work under MIT' (L244). These are mutually inconsistent documentation statements about developer intent and licensing obligations.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

The code unconditionally rewraps stdout and stderr with UTF-8 encoding on Windows. This imposes a locale/output-encoding choice without offering the user an option or documenting the constraint, which matches the language/locale policy concern.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

The download operation saves a Moodle file to a local path, potentially under a user-supplied destination, but the function only reports success after the write completes. While downloading is part of the command purpose, there is no explicit warning or pre-action disclosure that a local file will be created or overwritten at the chosen path.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
97% confidence
Finding

The dependency is specified with a lower-bound constraint only, which makes builds non-reproducible and can pull in different versions over time. This increases supply-chain risk and makes it impossible to determine from the manifest alone whether a safe or vulnerable release of requests will be installed.

Content

Scanner excerpt · requirements.txt (reported line 1)May include surrounding context.

text
requests>=2.31.0
cryptography>=41.0.0
playwright>=1.40.0
mcp>=0.1.0

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
89% confidence
Finding

requests has multiple known advisories, and because the manifest does not pin a version there is no way to verify whether the deployed version is affected. In a connector that likely makes authenticated outbound HTTP requests, installing a vulnerable requests release could expose credentials, request integrity, or TLS/session security depending on the resolved version.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
97% confidence
Finding

Using cryptography with only a minimum version allows future and environment-dependent resolution, reducing reproducibility and weakening assurance about which OpenSSL/packaged wheel contents are actually deployed. For security-sensitive libraries, this uncertainty is especially undesirable because downstream behavior can change with dependency resolution.

Content

Scanner excerpt · requirements.txt (reported line 2)May include surrounding context.

text
requests>=2.31.0
cryptography>=41.0.0
playwright>=1.40.0
mcp>=0.1.0

Unverifiable Dependency: cryptography has 16 known advisory(ies) (GHSA-39hc-v87j-747x (Vulnerable OpenSSL included in cryptography wheels); CVE-2023-50782 (Python Cryptography package vulnerable to Bleichenbacher timing oracle attack); GHSA-537c-gmf6-5ccf (Vulnerable OpenSSL included in cryptography wheels) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
91% confidence
Finding

cryptography is a high-sensitivity library, and the lack of version pinning means the project could resolve to a release affected by known cryptographic or bundled OpenSSL issues. In software handling API auth, secure transport, or encrypted material, that uncertainty has elevated risk because failures in this layer can undermine core security guarantees.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
94% confidence
Finding

playwright is also unpinned, so installs may vary across environments and time. While this is primarily a reliability and supply-chain hygiene issue rather than an immediate exploitable flaw by itself, it can still introduce unexpected vulnerable or incompatible versions.

Content

Scanner excerpt · requirements.txt (reported line 3)May include surrounding context.

text
requests>=2.31.0
cryptography>=41.0.0
playwright>=1.40.0
mcp>=0.1.0

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
98% confidence
Finding

The unpinned mcp dependency is more concerning in this skill context because the package underpins an MCP server exposed for Claude Code integration, so version drift could directly affect remotely reachable functionality. Without an exact version, the project may install a release with known security issues or breaking security defaults.

Content

Scanner excerpt · requirements.txt (reported line 4)May include surrounding context.

text
requests>=2.31.0
cryptography>=41.0.0
playwright>=1.40.0
mcp>=0.1.0

Unverifiable Dependency: mcp has 12 known advisory(ies) (CVE-2025-53366 (MCP Python SDK vulnerability in the FastMCP Server causes validation error, lead); CVE-2025-66416 (Model Context Protocol (MCP) Python SDK does not enable DNS rebinding protection); CVE-2026-52870 (MCP Python SDK: Experimental task handlers allow any client to access and cancel) +9 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
96% confidence
Finding

mcp has known advisories and is not pinned, so the skill may resolve to a vulnerable MCP SDK release without any visibility from this manifest. Because this project explicitly provides an MCP server for integration, flaws in the SDK could be directly exposed to connected clients and materially increase the risk of remote abuse, access control bypass, or unsafe server behavior.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.exposed_secret_literal

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
SKILL.md:72