Back to skill

Security audit

Moodle Connector

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real Moodle connector, but it has serious under-disclosed security risks around credential handling, downloads, and local persistence.

Review before installing. Do not use this skill with real Moodle credentials until the maintainer restricts downloads to the configured Moodle origin, contains output paths to an approved directory, removes the 'test-pass' default, and accurately documents credential storage, caching, and data transmission. If already used with untrusted download URLs or the default password, rotate the Moodle token and any stored password.

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

T09 · Insecure Skill Coding Practices

Error
Location
moodle_connector.py:514
Finding

Moodle Authentication Token Exfiltration Through Unrestricted Download URLs

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 exposed through MCP: ```python Tool( name="download", description="Download a file from Moodle", inputSchema={ "type": "object", "properties": { "url": { "type": "string", "description": "File URL to download" }, "output": { "type": "string", "description": "Optional: output file path (defaults to cache)" } }, "required": ["url"] } ) ``` ```python elif name == "download": url = arguments.get("url") output = arguments.get("output") result = connector.download(url, output) ``` ### Technical Analysis The downloader accepts an unrestricted URL and uncondit ...[truncated 2025 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
moodle_connector.py:817
Finding

Arbitrary Filesystem Write Through Unvalidated Download Paths

Content
View full analysis
str: dest = Path(output) if output else None path = self.api.download_file(url, dest) return f"# ✅ Downloaded\n\n- **File:** `{path}`\n- **Size:** {path.stat().st_size // 1024} KB\n" ``` The sink creates parent directories and writes remote content without enforcing an approved root: ```python 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 log.info("Downloading: %s → %s", file_url, dest.name) try: 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) ``` Batch configuration fields are similarly used as path components without containment checks: ```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', []): total += 1 filename = file_info.get('name', 'file') url = file_info.get('url') if not url: print(f" ⚠️ Skipped: {filename} (no URL)") continue try: target_path = module_dir / filename print(f" Downloading: {filename}...", end=' ') sys.stdout.flush() connector.api.download_file(url, target_path) ``` ### Technical Analysis The MC ...[truncated 2309 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
mcp_server.py:36
Finding

Hardcoded Password Used to Encrypt Moodle Credentials

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 uses the same password as its command-line default: ```python parser.add_argument( '--password', default='test-pass', help='Encryption password for credentials (default: test-pass)' ) ``` ### Technical Analysis `credentials.enc` is encrypted with Fernet using a PBKDF2-derived key. PBKDF2 and Fernet are appropriate primitives, but their protection depends on the secrecy and strength of the password. The fixed value `test-pass` is present in source code and therefore known to every attacker. The MCP implementation does not read the documented `MOODLE_CRED_PASSWORD` environment variable at all. The batch downloader's nonempty default also prevents its environment-variable fallback from being reached under normal invocation. This contradicts the documentation's claims that `MOODLE_CRED_PASSWORD` is required and that no hardcoded default exists. ### Attack Path 1. A user runs the MCP server or batch downloader with the default password. 2. The connector stores a Moodle token and potentially a Moodle username and password in `credentials.enc`. 3. An attacker obtains read access to that file through a backup, artifact, shared directory, local compromise, or accidental disclosure. 4. The attacker uses the publicly known password `test-pass`. 5. The attacker derives the Fernet key using the salt stored in the first 16 byte ...[truncated 671 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding

Unpinned Dependencies and Mutable Browser Installation

Content
View full analysis
=2.31.0 cryptography>=41.0.0 playwright>=1.40.0 mcp>=0.1.0 ``` The package installation script also downloads dependencies and a browser binary: ```json "scripts": { "install": "pip install -r requirements.txt && playwright install chromium" } ``` ### Technical Analysis Every Python requirement uses an open-ended lower bound. A fresh installation can therefore select versions that did not exist when the Skill was reviewed. No lock file or package hashes are provided to verify the exact artifacts installed. The install script additionally invokes Playwright's browser installer, which retrieves a mutable external browser component. The reviewed files do not show a suspicious custom package registry or known typosquatted dependency, but the installation is not reproducible and implicitly trusts future upstream releases and their transitive dependencies. ### Attack Path 1. A user installs the Skill at a later date. 2. `pip` resolves the newest dependency versions satisfying the lower-bound constraints. 3. Playwright downloads a browser binary through its installer. 4. If an allowed dependency, transitive dependency, package-index account, or browser distribution channel is compromised, the installer retrieves the affected artifact. 5. Package installation code or the compromised component executes with the privileges of the user performing installation. ### Impact Assessment A compromised dependency can execute arbitrary code during installation or when imported by the Skill. This would grant access to the running user's files, environment variables, Moodle credentials, and network privileges. Even without malicious compromise, incompatible future releases c ...[truncated 123 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 (24)

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
94% confidence
Finding

The skill description materially overstates implemented security-sensitive functionality, including MCP integration, broad SSO support, and batch downloading. Description-behavior mismatches are dangerous because operators may trust the documented controls and deployment model, then expose credentials, tokens, or automation workflows under false assumptions about what the code actually does.

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 262)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
95% confidence
Finding

The skill documentation advertises capabilities that read environment variables, access local files, write configuration/token material, and make network requests, but it does not declare any explicit tool scope such as permissions or allowed-tools. That omission weakens user visibility and policy enforcement, making it easier for a high-privilege skill to be installed or run without informed consent.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The headless/CI instructions state that tokens are saved automatically to config.json, but they do not prominently warn that this creates durable credential material on disk. In CI/CD and shared runners, silent persistence of tokens increases the risk of credential leakage through artifacts, workspace reuse, backups, or accidental commits.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The security notes claim 'No external data transmission or logging' even though the tool is explicitly a network client that sends requests to Moodle and identity providers and downloads remote content. This is a security-relevant false assurance that can mislead users about data exposure, audit requirements, and acceptable deployment environments.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

The listed tools fetch Moodle courses, grades, assignments, materials, deadlines, announcements, summaries, and downloads, which inherently involve network access and handling user educational data. This code exposes those operations without any user-facing warning, privacy notice, or comment explaining that requests to the Moodle service will be made and account data will be accessed.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The download tool explicitly exposes a file download capability with an optional output path, which can write files to disk. In this file there is no confirmation prompt, visible user-facing notice, or warning comment/docstring describing that invoking this tool will create or overwrite local files.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

API responses are cached to disk as plaintext JSON and can include sensitive academic and personal data such as grades, assignments, announcements, course membership, and calendar items. On a shared machine or compromised user account, these cache files could be read long after the session ends, expanding the exposure window beyond the live API session.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

Downloaded course files are persisted locally by default under the cache directory, which may store sensitive educational materials or user-specific documents without prominent disclosure. In the context of an AI-integrated connector, this increases the chance that private files remain on disk unintentionally and may later be accessed by other local users, processes, or tooling.

Content

No source excerpt is available for this finding.

Ssd 3

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The summary() function aggregates courses, grades, assignments, deadlines, and announcements into one AI-oriented markdown dump, concentrating a large amount of sensitive student data into a single exportable artifact. In a Claude/MCP integration context, that materially increases the risk of oversharing to downstream LLMs, logs, prompts, or transcripts beyond the user’s intended scope.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

This code creates output directories and then downloads files from remote URLs into the local filesystem. While progress is printed during execution, there is no user-facing warning in the docstring or argument help that the script will write multiple remote files to disk under the chosen output path.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
76% confidence
Finding

The manifest and module documentation describe a Moodle REST client and batch downloader, so downloading files is expected. However, this file also accepts an encryption password via CLI, reads a credential password from an environment variable, and prompts interactively for it, which is a credential-handling capability not described in this file's stated downloading role.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
76% confidence
Finding

The script reads an encryption password from a command-line argument or environment variable and may prompt interactively, but it does not explain this sensitive credential handling in a visible warning or usage note. For code-file review, access to credentials should have some disclosure through comments, docstrings, or user-facing documentation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
83% confidence
Finding

The template hard-codes the Moodle endpoint for a specific institution (mytimes.taylors.edu.my) in both the comment and base_url. This creates a natural-language locale/scope constraint without indicating that the skill is institution-specific or offering the user a choice of endpoint.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
94% confidence
Finding

The dependency is specified with a lower bound only, which allows future installs to resolve to different versions over time. This weakens build reproducibility and can unintentionally introduce vulnerable or breaking releases into a security-sensitive connector that handles authentication and external API access.

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
88% confidence
Finding

Requests has multiple published advisories, but the manifest does not pin a version, so there is no way to verify whether deployed environments avoid affected releases. In a connector that makes outbound authenticated HTTP calls, this uncertainty can expose credentials or transport security behavior to known flaws if an unsafe version is resolved.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
94% confidence
Finding

Using an unpinned cryptography dependency permits uncontrolled version drift at install time. For a skill that supports SSO and likely processes tokens or TLS-protected communications, inconsistent crypto library versions increase supply-chain and patch-management risk.

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
89% confidence
Finding

Cryptography has known advisories, and the lack of version pinning makes it impossible to determine if installations are using a patched release. Because this skill supports SSO and likely handles sensitive authentication material, vulnerable crypto components can have elevated consequences compared with ordinary utility code.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
91% confidence
Finding

The playwright package is not pinned to an exact version, so deployments may pull different releases over time. Because browser automation often interacts with login flows and authenticated sessions, unexpected upstream changes can create security and reliability risk.

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
95% confidence
Finding

The mcp dependency is unpinned, allowing arbitrary newer releases that may include vulnerable server behavior or breaking protocol changes. This is more concerning here because the skill exposes MCP server functionality, so dependency drift could directly affect network-exposed components.

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
92% confidence
Finding

The mcp package has published advisories, and without exact version pinning the deployed release cannot be verified as patched. Since this skill includes MCP server integration, unresolved vulnerabilities in that dependency could affect a service-facing attack surface rather than only local functionality.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.