Back to skill

Security audit

极鲸云美客多类目搜索

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to do the advertised Mercado Libre research, but it handles login tokens, custom API origins, and authentication links too broadly for automatic approval.

Review before installing. Use only the default trusted GeekBI HTTPS endpoint, do not pass custom --base-url values, verify any login link before opening it, and be aware that bearer tokens may be written into .geekbi auth files in the skill, user config, and current working directories. Clear the auth state when you stop using the skill.

Vulnerability Patterns
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/geekbi_auth.py:698
Finding
Arbitrary API Origin Can Receive Authentication Material and Bearer Tokens## Vulnerability Details **File Location**: `scripts/geekbi_auth.py:698-721`; caller-controlled input originates at `scripts/mercadolibre_goods_search.py:63-70` and equivalent `--base-url` arguments in the other query scripts. **Vulnerability Type**: Unrestricted authentication destination and insecure transport **Risk Level**: High ### Vulnerable Code ```python # scripts/mercadolibre_goods_search.py:63-70 parser.add_argument("--base-url", default=DEFAULT_BASE_URL) parser.add_argument("--param", action="append", default=[], help="Query parameters") parser.add_argument("--timeout", type=float, default=30) args = parser.parse_args() try: params = parse_params(args.param) payload = authenticated_json_request( build_url(args.base_url, ENDPOINT, params), args.base_url, args.timeout ) ``` ```python # scripts/geekbi_auth.py:698-721 def authenticated_json_request( url, base_url, timeout, *, method="GET", body=None, headers=None, ): complete_pending_login(base_url, timeout) request_headers = _api_headers() if headers: request_headers.update(headers) authorization = _authorization_header(base_url) if authorization: request_headers["token"] = authorization request = Request( url, data=body, headers=request_headers, method=method, ) try: with urlopen(request, timeout=timeout) as response: response_payload = _read_json_response(response) ``` ```python # scripts/geekbi_auth.py:564-578 def complete_pending_login(base_url, timeout): payload = _load_state() server_key = _server_key(base_url) server = payload["servers"].get(server_key) if not isinstance(server, dict): return False now = int(time.time()) changed = _clear_expired(server, now) pending = server.get("pending") if not is ...[truncated 2304 chars]
Remediation
## Remediation Suggestions - Remove `--base-url` from production-facing commands unless custom endpoints are an explicit requirement. - Parse URLs with `urllib.parse.urlsplit` and require an exact allowlisted HTTPS origin, such as `https://openapi.geekbi.com`. - Reject non-HTTPS schemes, user information, fragments, unexpected ports, IP literals, and unapproved hostnames. - Normalize hosts before using them as token-store keys. - Before attaching authentication headers, verify that the request URL and authentication base URL have exactly the same scheme, hostname, and effective port. - Disable redirects for authenticated requests or validate every redirect target before resending any sensitive request. - Never forward the `token` header across an origin change. - If development endpoints are needed, place them behind an explicit opt-in configuration with a separate token store and clear security warnings.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/geekbi_auth.py:669
Finding
Unvalidated Server-Controlled Action URLs Are Presented to Users## Vulnerability Details **File Location**: `scripts/geekbi_auth.py:669-693`; the presentation behavior is mandated by `references/查询暂停与恢复流程.md:3-8`. **Vulnerability Type**: Untrusted URL forwarding and phishing exposure **Risk Level**: Medium ### Vulnerable Code ```python def _save_challenge(base_url, response_payload): data = response_payload.get("data", {}) device_code = data.get("deviceCode") jump_url = data.get("jumpUrl") if not isinstance(device_code, str) or not device_code: raise ValueError("Login response is missing deviceCode") if not isinstance(jump_url, str) or not jump_url: raise ValueError("Login response is missing jumpUrl") expires_in = int(data.get("expiresIn", 0)) def save_challenge(payload): server = _server_state(payload, base_url) _remove_access_token(server) server["pending"] = { "deviceCode": device_code, "jumpUrl": jump_url, "expiresAt": int(time.time()) + expires_in, } return True, None _update_state(save_challenge) raise ActionRequired( response_payload.get("msg", "Login is required to continue"), jump_url, action="AUTH_REQUIRED", expires_in=expires_in, ) ``` The documented workflow instructs the agent to display the server-provided `jumpUrl` as a clickable Markdown link without rewriting it. ### Technical Analysis Any non-empty string is accepted as an action URL, persisted in authentication state, and returned through `ActionRequired`. No validation restricts the value to HTTPS or to an approved GeekBI authentication hostname. Unsafe URI schemes, misleading hostnames, embedded credentials, attacker-controlled ports, and IP-literal destinations are not rejected. This issue is especially relevant because the API origin itself can be caller-controlled. However, it also represents an independent trust ...[truncated 1162 chars]
Remediation
## Remediation Suggestions - Parse `jumpUrl` before storing or returning it. - Require HTTPS and an explicit allowlist of authentication hostnames. - Reject URL user information, fragments where unnecessary, nonstandard ports, IP literals, and schemes such as `javascript:`, `data:`, and `file:`. - Validate redirect destinations used by the browser-facing authentication service. - Display the normalized destination hostname alongside the link so users can verify it. - Replace the instruction to reproduce arbitrary server URLs verbatim with an instruction to reject and report URLs that fail validation. - Avoid persisting an invalid URL even if the associated device code appears valid.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/geekbi_auth.py:66
Finding
Plaintext Bearer Tokens Are Mirrored into Skill and Working Directories## Vulnerability Details **File Location**: `scripts/geekbi_auth.py:66-92`, `367-383`, and `631-648` **Vulnerability Type**: Excessive plaintext credential storage **Risk Level**: Medium ### Vulnerable Code ```python def _resolve_stores(): candidates = ( ResolvedStore(_user_config_state_path(), "user-config-directory"), ResolvedStore(_skill_state_path(), "skill-directory"), ResolvedStore(_workspace_state_path(), "working-directory"), ) stores = [] seen_paths = set() for store in candidates: path_key = os.path.normcase(os.fspath(store.path)) if path_key in seen_paths: continue seen_paths.add(path_key) stores.append(store) return tuple(stores) ``` ```python def _write_state_files(stores, payload): normalized = _normalize_state(payload) errors = [] written = 0 for store in stores: try: _write_state_file(store, normalized) written += 1 except OSError as error: errors.append(f"{store.kind}: {_storage_probe_reason(error)}") if written == 0: reason = ";".join(errors) or "Authentication-state directory is unavailable" raise OSError(reason) ``` ```python token_data = response["data"] access_token = token_data.get("accessToken") if not isinstance(access_token, str) or not access_token: raise ValueError("Login token response is missing accessToken") expires_in = int(token_data.get("expiresIn", 0)) def save_token(latest): latest_server = latest["servers"].get(server_key) if not isinstance(latest_server, dict): return False, False latest_pending = latest_server.get("pending") if not isinstance(latest_pending, dict): return False, False if latest_pending.get("deviceCode") != pending.get("deviceCode"): return False, False _remove_access_token ...[truncated 2249 chars]
Remediation
## Remediation Suggestions - Store only one credential copy in the operating system's credential manager, keychain, or secret service. - If file storage is unavoidable, use only a dedicated user configuration directory with restrictive permissions. - Remove the Skill-directory and working-directory stores from `_resolve_stores`. - Do not mirror access tokens merely to improve state availability. - Separate non-sensitive state from access tokens so synchronization cannot copy credentials accidentally. - Encrypt stored credentials using platform-protected keys where supported. - Keep token lifetime and server-side scope as short and narrow as practical. - Document storage location, retention, revocation, and cleanup behavior. - On upgrade, securely remove legacy token copies from Skill and workspace directories where feasible.

T08 · Insecure Dependencies

Note
Location
requirements.txt:1
Finding
Third-Party Dependency Is Not Reproducibly Pinned## Vulnerability Details **File Location**: `requirements.txt:1` **Vulnerability Type**: Unpinned dependency and missing integrity verification **Risk Level**: Low ### Vulnerable Code ```text platformdirs>=4.0,<5.0 ``` ### Technical Analysis The dependency specification accepts any `platformdirs` release from version 4.0 up to, but excluding, version 5.0. Future installations can therefore resolve to code that was not part of the audited artifact. No lock file or package hash constrains the selected distribution. The package name is not an apparent typo or dependency-confusion target, and no unsafe custom package index was found. The issue is reproducibility and integrity rather than evidence that the current dependency is malicious. ### Attack Path 1. A new matching dependency release becomes available through the configured package source. 2. A later installation resolves that release instead of the version used during review. 3. If the package source, maintainer account, or release artifact is compromised, malicious package code is installed. 4. The package is imported by `scripts/geekbi_auth.py`, causing its Python module initialization code to run with the Skill process's privileges. ### Impact Assessment A compromised dependency executes with the same operating-system permissions as the Skill process. It could access local files available to that process, including stored authentication state, and make network connections. This is a supply-chain hardening weakness rather than a demonstrated compromise.
Remediation
## Remediation Suggestions - Pin `platformdirs` to an exact version that has been reviewed and tested. - Generate a lock file or hash-checked requirements file. - Install with hash enforcement, such as `pip install --require-hashes`, where operationally practical. - Review dependency updates before changing the lock file. - Use a trusted package index and prevent unapproved fallback indexes. - Add automated vulnerability and provenance checks to the release process.
Vulnerability Patterns
  • 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
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (2)

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill instructs the agent to run local scripts and read multiple reference files, which implies shell execution, file access, and likely network access, but it does not declare any permissions or capability boundaries. This creates a trust and containment gap: an agent may execute code or access resources beyond what reviewers and operators expect, increasing the chance of unintended data access or outbound requests.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The auth state is stored under a different namespace path ("temu-research-skill") than the declared Mercado Libre skill. This can cause credential/state confusion across skills, leading to unintended sharing, overwrite, or reuse of authentication material between unrelated skills running under the same user account. In a multi-skill environment, that weakens isolation assumptions around persisted tokens.

Static analysis

No suspicious patterns detected.