Back to skill

Security audit

modora

Security checks for vulnerabilities and agentic risk

Overview

The skill is coherent for remote PDF analysis, but it forwards user model credentials and documents to a remote service with weak boundary controls.

Install only if you trust the MoDora server operator with both your PDF contents and the upstream model API key supplied in MODORA_USER_API_KEY. Prefer a local or tightly controlled MoDora endpoint, use a limited-scope/low-quota key, avoid sensitive PDFs unless approved for third-party processing, and verify settings so every pipeline uses explicit user-owned model instances.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/common.py:188
Finding
<![CDATA[HTTPS and credential-boundary validation is not enforced across redirects]]><![CDATA[ ## Vulnerability Details **File Location**: `scripts/common.py:40-49, 188-201` **Vulnerability Type**: Improper redirect handling for credential-bearing HTTP requests **Risk Level**: Medium ### Vulnerable Code ```python def get_base_url() -> str: url = os.environ.get("MODORA_BASE_URL", DEFAULT_BASE_URL).rstrip("/") parsed = urllib.parse.urlparse(url) hostname = (parsed.hostname or "").lower() is_local = hostname in {"127.0.0.1", "localhost", "::1"} if not is_local and parsed.scheme != "https": raise SystemExit( f"Security error: remote MoDora endpoints must use HTTPS. Current value: {url}" ) return url ``` ```python def request_json( method: str, url: str, data: bytes | None = None, headers: dict[str, str] | None = None, timeout: int = 60, ) -> object: req = urllib.request.Request(url, data=data, method=method) for key, value in (headers or {}).items(): req.add_header(key, value) try: with urllib.request.urlopen(req, timeout=timeout) as resp: return parse_json_bytes(resp.read()) ``` Upload and chat requests pass credentials through this function: ```python headers={ "Content-Type": f"multipart/form-data; boundary={boundary}", **get_credential_headers(), **SKILL_HEADERS, }, ``` ### Technical Analysis `get_base_url()` validates only the initially configured URL. It requires HTTPS for a non-local hostname, but `urllib.request.urlopen()` follows redirects automatically without invoking `get_base_url()` for each redirect destination. Consequently, an initially valid HTTPS endpoint can redirect a request to: - A plaintext HTTP URL, bypassing the documented transport requirement. - A different origin that has not been approved by the user. - An attacker-controlled host. The affected upload and chat requests contain sensitive headers, including `Authorization: Bearer <API key>`, `X-Modora-Endpoint`, `X-Modora-Model`, and `X- ...[truncated 1409 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic redirects for upload and chat requests that contain credentials. 2. If redirects are operationally required, implement a custom `HTTPRedirectHandler` that: - Rejects every HTTPS-to-HTTP redirect. - Rejects redirects to a different hostname or port. - Revalidates the scheme and destination at every redirect. - Enforces a small maximum redirect count. 3. Remove `Authorization` and all credential-bearing `X-Modora-*` headers before any redirect unless the destination is proven to be the exact same trusted origin. 4. Prefer failing closed and requiring the user to configure the final service URL directly. 5. Add automated tests for same-origin redirects, cross-origin redirects, redirect loops, and HTTPS-to-HTTP downgrade attempts. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/common.py:96
Finding
<![CDATA[Settings validation does not enforce required pipeline modules or prohibit server-default model instances]]><![CDATA[ ## Vulnerability Details **File Location**: `scripts/common.py:96-125` **Vulnerability Type**: Incomplete security configuration validation **Risk Level**: Medium ### Vulnerable Code ```python def normalize_settings_payload(data: dict) -> dict: normalized: dict[str, object] = {} schema_version = data.get("schemaVersion") if isinstance(schema_version, int): normalized["schemaVersion"] = schema_version ocr = data.get("ocr") if isinstance(ocr, dict): provider = ocr.get("provider") if isinstance(provider, str) and provider.strip(): normalized["ocr"] = {"provider": provider.strip()} pipelines = data.get("pipelines") if isinstance(pipelines, dict): clean_pipelines: dict[str, dict[str, str]] = {} for module in MODULE_KEYS: item = pipelines.get(module) if not isinstance(item, dict): continue model_instance = item.get("modelInstance") if isinstance(model_instance, str) and model_instance.strip(): clean_pipelines[module] = {"modelInstance": model_instance.strip()} if clean_pipelines: normalized["pipelines"] = clean_pipelines if "pipelines" not in normalized: raise SystemExit("Settings file must define pipeline modelInstance values.") return normalized ``` The intended restrictions are stated in `SKILL.md`: ```text - skill requests cannot use server default model instance IDs such as `local-default` or `remote-default` - include `pipelines.<module>.modelInstance` for each pipeline module - Never fall back to server-side defaults when the settings file is missing. - Do not use server default model instance IDs such as `local-default` or `remote-default`. ``` ### Technical Analysis The validator only checks whether at least one recognized pipeline module has a non-empty `modelInstance`. Missing modules are skipped with `continue`, and the payload is accepted ...[truncated 1971 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require every module listed in `MODULE_KEYS` to exist in `pipelines`. 2. Require each module to contain a non-empty string `modelInstance`. 3. Explicitly reject prohibited identifiers, including `local-default` and `remote-default`, using normalized case and whitespace handling. 4. Reject unknown pipeline module names rather than silently discarding them, so typographical errors cannot cause unintended omissions. 5. If model-instance metadata is available, verify that every selected instance is user-owned and multimodal-capable. 6. Return an error that identifies every missing or prohibited module before making any network request. 7. Add tests covering: - Every missing required module. - Empty and non-string identifiers. - Prohibited default identifiers. - Case and whitespace variants. - Unknown module names. - A fully valid settings template. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (7)

Tainted flow: 'req' from os.environ.get (line 195, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
req.add_header(key, value)

    try:
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            return parse_json_bytes(resp.read())
    except urllib.error.HTTPError as exc:
        body = exc.read().decode("utf-8", errors="replace")
Confidence
98% confidence
Finding
Environment-derived values influence both the destination URL and authentication headers for outbound requests, and the code intentionally forwards a Bearer API key plus upstream endpoint/model metadata to a remote MoDora service. While HTTPS is enforced for non-local hosts and there is an acknowledgement gate, a compromised or user-controlled MODORA_BASE_URL can still cause sensitive credentials and document contents to be sent to an untrusted server, making this a real exfiltration risk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared purpose centers on PDF analysis through a remote HTTP service with credential handling via environment variables. The actual code shown only emits a JSON health result from a shared helper, indicating a health/status endpoint or diagnostic script. That is a materially different primary purpose from the declared PDF-analysis functionality, and the expected service interaction and credential behavior are not present in this code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding
The declared description centers on PDF analysis through a remote HTTP service and mentions credential management via environment variables. This code chunk does not show PDF parsing/analysis, HTTP communication, or credential handling. Instead, it is a small CLI wrapper for waiting on completion of an operation for a given filename, with timeout and polling controls. That is a materially different primary behavior from the declared purpose, so this chunk does not accurately represent the description.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill advertises network, environment-variable, and file-read driven behavior but does not declare any explicit tool scope such as permissions or allowed-tools. That increases the chance an agent can invoke broader capabilities than a reviewer expects, especially for a skill that uploads local PDFs and uses secrets from the environment to contact a remote service.

External Transmission

Medium
Category
Data Exfiltration
Content
```bash
export MODORA_BASE_URL="https://api.modora.pro"
export MODORA_USER_API_KEY="sk-..."
export MODORA_USER_ENDPOINT="https://api.openai.com/v1"
export MODORA_USER_MODEL="gpt-4o"
```
Confidence
87% confidence
Finding
The skill is explicitly designed to send user documents and questions to external services, including a remote MoDora endpoint and a model endpoint example at api.openai.com. This creates a real data-exfiltration and third-party processing risk: sensitive PDF contents, prompts, and possibly metadata leave the local environment and are handled by external operators.

Intent-Code Divergence

Medium
Confidence
92% confidence
Finding
The acknowledgement text notes use of 'environment-managed credentials for upstream model access' but soft-pedals the key security fact that those credentials are actually transmitted to the remote server along with the PDF and prompt. In a skill whose purpose is remote PDF analysis, incomplete disclosure is more dangerous because users are likely to process sensitive documents and may not realize they are delegating both data and credentials to another party.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The skill description emphasizes that credentials are not stored in the settings file, but the implementation sends environment-managed credentials to the remote MoDora service in HTTP headers for upstream use. This can mislead users into thinking secrets remain local when in fact they are disclosed to a third-party service, increasing the risk of unintended credential sharing and trust confusion.

Static analysis

No suspicious patterns detected.