Back to skill

Security audit

极鲸云Coupang类目搜索

Security checks for vulnerabilities and agentic risk

Overview

The skill matches its Coupang research purpose, but it under-discloses sensitive authentication storage and trusted display of server-provided login links.

Install only if you are comfortable with this skill contacting GeekBI, requiring login, and storing GeekBI bearer-token state locally. Prefer running it from a private workspace, avoid publishing or syncing .geekbi directories, use the clear command when done, and verify any login link belongs to the expected GeekBI domain before opening it.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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/geekbi_auth.py:69
Finding
Bearer Tokens Are Replicated Across Multiple Filesystem Locations<![CDATA[ ## Vulnerability Details **File Location**: `scripts/geekbi_auth.py:69-90`, `scripts/geekbi_auth.py:371-380`, `scripts/geekbi_auth.py:631-648` **Vulnerability Type**: Excessive replication of plaintext authentication tokens **Risk Level**: Medium ### Complete Code Snippet ```python def _user_config_state_path(): return _absolute_path( user_config_path("GeekBI", appauthor=False, ensure_exists=True) / "temu-research-skill" / AUTH_FILE_NAME ) def _skill_state_path(): return _absolute_path(Path(__file__).parent.parent / AUTH_STATE_DIR / AUTH_FILE_NAME) def _workspace_state_path(): return _absolute_path(Path(os.getcwd()) / AUTH_STATE_DIR / AUTH_FILE_NAME) def _resolve_stores(): candidates = ( ResolvedStore(_user_config_state_path(), "user-config-directory"), ResolvedStore(_skill_state_path(), "skill-directory"), ResolvedStore(_workspace_state_path(), "working-directory"), ) ``` ```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)}") ``` ```python 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(latest_server) latest_server["accessToken"] = access_token latest_server["accessTokenExpiresAt"] = now + max(0, expires_in - 30) latest_server.pop("pending", None) return True, True return _update_state(save_token) ``` ### Technical Analysis ...[truncated 2558 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store authentication state only in the platform-specific user configuration directory. 2. Prefer an operating-system credential manager, such as Keychain, Credential Manager, or Secret Service, for the bearer token. 3. Remove `_skill_state_path()` and `_workspace_state_path()` from credential-store candidates. 4. Do not fall back to a less protected workspace when the secure store is unavailable. Fail closed and provide a clear setup error instead. 5. Retain atomic writes and restrictive permissions for non-secret metadata. 6. On POSIX systems, verify the final file owner and mode after replacement rather than silently ignoring permission-setting failures. 7. Provide migration logic that deletes legacy mirrored token files after moving authentication state into the protected store. 8. Document token lifetime, revocation behavior, and a reliable command for clearing all legacy copies. ]]>

T01 · Skill Instruction Hijacking

Warning
Location
scripts/geekbi_auth.py:500
Finding
Unvalidated Server-Controlled Messages and Links Are Propagated into Agent Output<![CDATA[ ## Vulnerability Details **File Location**: `scripts/geekbi_auth.py:500-514`, `scripts/geekbi_auth.py:669-695`, `references/查询暂停与恢复流程.md:5-7` **Vulnerability Type**: Remote instruction and phishing-link injection through trusted Skill output **Risk Level**: Medium ### Complete Code Snippet ```python def _raise_action_if_needed(payload): data = payload.get("data", {}) if isinstance(payload, dict) else {} jump_url = data.get("jumpUrl") if isinstance(data, dict) else None if not isinstance(jump_url, str) or not jump_url: return raise ActionRequired( response_message(payload, "请完成页面操作后继续"), jump_url, action=data.get("error") or "ACTION_REQUIRED", expires_in=int(data.get("expiresIn", 0)), ) ``` ```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("登录响应缺少 deviceCode") if not isinstance(jump_url, str) or not jump_url: raise ValueError("登录响应缺少 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", "需要登录后继续"), jump_url, action="AUTH_REQUIRED", expires_in=expires_in, ) ``` The workflow documentation additionally instructs the Agent to use the server-provided message verbatim and display the server-provided URL as a clickable Markdown link without rewriting it. ### Technical Analysis Both `msg` and `data.jumpUrl` cross a remote trust boundary b ...[truncated 2648 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse `jumpUrl` with a strict URL parser before returning it. 2. Permit only `https` URLs. 3. Enforce an explicit allowlist of GeekBI authentication hostnames. 4. Reject embedded usernames or passwords, nonstandard ports unless required, malformed hostnames, and non-web URI schemes. 5. Resolve and compare normalized hostnames carefully to prevent suffix tricks such as `trusted.example.attacker.test`. 6. Replace server-provided instructional text with a fixed local message, such as: “Authentication is required. Open the verified GeekBI login page.” 7. If remote details must be shown, quote and label them as untrusted service-provided text rather than Agent instructions. 8. Escape Markdown control characters before presenting remote text. 9. Do not instruct the Agent to reproduce arbitrary server messages verbatim. 10. Add tests covering `javascript:`, `data:`, `file:`, credential-bearing URLs, lookalike domains, malicious Markdown, and instruction-injection payloads. 11. Keep the existing rule that access tokens, device codes, request headers, and internal authentication objects must never be displayed. ]]>
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
89% confidence
Finding
The skill instructs the agent to run local scripts and read reference files, which implies file-read, shell, and likely network access, but no permissions are declared. This creates a capability-transparency gap: a reviewer or runtime may not understand that the skill can execute code and access external data sources, increasing the risk of over-privileged or unsafely approved deployment.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The auth helper stores bearer-token state in a user-config subdirectory named for a different skill ('temu-research-skill'). That namespace collision can cause unintended credential sharing, overwrite another skill's auth state, or let this skill read stale tokens belonging to a different workflow if both use the same backend, which is a real security boundary/identity-mixup problem rather than a cosmetic bug.

Static analysis

No suspicious patterns detected.