Back to skill

Security audit

Mercado Público ChileCompra

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly coherent for ChileCompra work, but it needs review because optional API caching can save the API ticket to disk in plaintext.

Install only if you are comfortable granting this skill access to ChileCompra workflows and an API ticket. Keep caching disabled unless the cache code is fixed to redact the ticket, use a private cache directory, and rotate any ticket that may have been cached in a shared or backed-up location. Require explicit user approval before any offer, quotation, purchase-order, cancellation, complaint, or other state-changing portal action.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mercado_publico_api.py:84
Finding
API Ticket Persisted in Plaintext Cache Metadata## Vulnerability Details **File Location**: `scripts/mercado_publico_api.py`, lines 84–88 and 118–126 **Vulnerability Type**: Plaintext persistence of sensitive authentication material **Risk Level**: Medium ### Vulnerable Code ```python def build_url(path: str, ticket: str, params: dict[str, Any]) -> str: clean = {k: str(v) for k, v in params.items() if v is not None and str(v) != ""} clean["ticket"] = ticket query = urllib.parse.urlencode(clean) return f"{API_BASE}{path}?{query}" ``` ```python def _write_cache(cache_path: Path, url: str, payload: Any) -> None: cache_path.parent.mkdir(parents=True, exist_ok=True) data = { "url": _normalize_url_for_cache(url), "fetched_at": int(time.time()), "payload": payload, } tmp_path = cache_path.with_suffix(".tmp") tmp_path.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8") tmp_path.replace(cache_path) ``` The vulnerable cache write is reached at lines 155–156: ```python payload = fetch_with_retry(url, timeout, max_retries, backoff_seconds) _write_cache(cache_path, url, payload) ``` ### Technical Analysis The script correctly obtains `MERCADO_PUBLICO_API_TICKET` from an environment variable and sends it to the declared official Mercado Público HTTPS API. That network transmission is necessary for the Skill’s read-only API functionality and does not, by itself, indicate unauthorized exfiltration. However, `build_url()` places the ticket in the URL query string. When the optional cache is enabled through `--cache-ttl`, `_write_cache()` stores the complete normalized URL in the cache document. URL normalization sorts and re-encodes the query parameters but does not remove or redact the `ticket` parameter. Consequently, the API ticket is written to disk in recoverable plaintext. The cache destination is also configurable through `--cache-dir`. The implementation creates directories and files without explicitly enforcing owner-only permiss ...[truncated 2194 chars]
Remediation
## Remediation Suggestions 1. **Never store the ticket in cache metadata.** Remove the `ticket` parameter before serializing the URL: ```python def _redact_url(url: str) -> str: parsed = urllib.parse.urlparse(url) pairs = urllib.parse.parse_qsl(parsed.query, keep_blank_values=True) safe_pairs = [ (key, "[REDACTED]" if key.lower() == "ticket" else value) for key, value in pairs ] return urllib.parse.urlunparse( ( parsed.scheme, parsed.netloc, parsed.path, "", urllib.parse.urlencode(safe_pairs), "", ) ) ``` Store `_redact_url(url)` rather than `_normalize_url_for_cache(url)`. 2. **Exclude the credential from cache-key material.** Construct cache keys from the endpoint and non-secret filters only. This avoids unnecessarily processing authentication material as persistent-cache identity. 3. **Separate request authentication from cache identity.** Prefer passing a structured endpoint and parameter dictionary into the cache layer, with the ticket added only at the network-request boundary. 4. **Apply restrictive filesystem permissions.** Create the cache directory with mode `0700` and cache files with mode `0600`, while accounting for cross-platform behavior and existing directories. 5. **Use a safe default cache location.** Prefer a user-private cache directory rather than a project-relative directory that may be shared, archived, or accidentally committed. 6. **Warn about custom cache locations.** Document that `--cache-dir` must not point to shared, synchronized, publicly served, or version-controlled directories. 7. **Remove existing exposed cache entries.** Delete cache files created by affected versions and rotate or revoke any API ticket that may have been stored in a location accessible to untrusted parties. 8. **Add regression tests.** Verify that neither cache file contents nor cache filenames contain the literal ticket and th ...[truncated 61 chars]
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (15)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The skill claims broad authenticated portal capabilities, including login/session handling, OTP-related flows, and portal actions, but the described implementation appears limited to reference-reading plus a read-only public API helper. This mismatch is dangerous because operators or higher-level agents may trust the skill to safely handle sensitive authenticated workflows it cannot actually enforce, leading to unsafe manual workarounds, accidental credential exposure, or incorrect state-changing guidance.

Lp3

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding
The skill advertises access to environment variables, file operations, and networked API use, but does not declare explicit tool scope such as permissions or allowed-tools. In an agent setting, missing scope boundaries can allow the runtime or downstream orchestration to grant broader capabilities than intended, increasing the blast radius if the skill is misused or prompt-injected.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
The entire skill document is written as operational guidance in Spanish and does not indicate that users may choose another language or that the skill is intentionally limited to Spanish-speaking contexts. Under the stated policy, forcing a specific language without user opt-in is a natural-language policy concern.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
The file’s natural-language instructions, guardrails, and operational guidance are all presented in Spanish, and there is no indication that the user can opt into another language or that the skill is intentionally restricted to a Spanish-speaking context. Under the policy, forcing a specific language without opt-in is a natural-language policy concern.

Natural-Language Policy Violations

Medium
Confidence
88% confidence
Finding
The file's natural-language instructions are entirely in Spanish, including operational guidance and safety-related steps. Under the language/locale policy, forcing a specific language without user opt-in or a documented justification can be a policy violation.

External Transmission

Medium
Category
Data Exfiltration
Content
from pathlib import Path
from typing import Any

API_BASE = "https://api.mercadopublico.cl/servicios/v1"
DEFAULT_TIMEOUT = 15
DEFAULT_MAX_RETRIES = 2
DEFAULT_BACKOFF_SECONDS = 1.0
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
from pathlib import Path
from typing import Any

API_BASE = "https://api.mercadopublico.cl/servicios/v1"
DEFAULT_TIMEOUT = 15
DEFAULT_MAX_RETRIES = 2
DEFAULT_BACKOFF_SECONDS = 1.0
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
from pathlib import Path
from typing import Any

API_BASE = "https://api.mercadopublico.cl/servicios/v1"
DEFAULT_TIMEOUT = 15
DEFAULT_MAX_RETRIES = 2
DEFAULT_BACKOFF_SECONDS = 1.0
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
from pathlib import Path
from typing import Any

API_BASE = "https://api.mercadopublico.cl/servicios/v1"
DEFAULT_TIMEOUT = 15
DEFAULT_MAX_RETRIES = 2
DEFAULT_BACKOFF_SECONDS = 1.0
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
from pathlib import Path
from typing import Any

API_BASE = "https://api.mercadopublico.cl/servicios/v1"
DEFAULT_TIMEOUT = 15
DEFAULT_MAX_RETRIES = 2
DEFAULT_BACKOFF_SECONDS = 1.0
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The helper writes full API responses to local disk when caching is enabled, and the payload may contain procurement, supplier, buyer, or order data that can be sensitive in the operator's environment. Because the cache is silent and there are no permission hardening, redaction, or user-facing warnings, another local user, backup system, or diagnostic bundle could access data that the operator did not realize was being persisted.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
The file includes imperative operational instructions in Spanish such as 'siempre, para decidir estado/transición e intervención humana' and 'Leer ... para ejecutar flujo operativo mínimo' without indicating that language selection is optional. This can violate a language/locale policy when users or operators are not given a choice of language for core instructions.

Natural-Language Policy Violations

Low
Confidence
82% confidence
Finding
Todo el contenido operativo de la skill está redactado en español y no indica que el usuario pueda elegir otro idioma ni que exista una limitación de locale documentada. Según la política indicada, forzar un idioma sin opt-in puede constituir una violación de política lingüística.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
The file’s instructions and labels are entirely in Spanish, but the document does not state that the skill is Spanish-only, region-specific, or that users may choose their preferred language. Under the policy, forcing a specific language without user opt-in can be a natural-language policy violation.

Natural-Language Policy Violations

Low
Confidence
89% confidence
Finding
This file contains user-facing natural-language instructions entirely in Spanish, and there is no indication that the skill is region-specific or that users can opt into this locale. Under the policy rule for language/locale, forcing a specific language without user choice can be a natural-language policy violation.

Static analysis

No suspicious patterns detected.