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]]>

Content
View full analysis
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 `, `X-Modora-Endpoint`, `X-Modora-Model`, and `X- ...[truncated 1409 chars]
Remediation
View remediation

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]]>

Content
View full analysis
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..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
View remediation
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
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.

Content

Scanner excerpt · scripts/common.py (reported line 200)May include surrounding context.

python
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")

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.

Content

No source excerpt is available for this finding.

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.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

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.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
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.

Content

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

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"

Intent-Code Divergence

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.