Back to skill

Security audit

极鲸云Ozon店铺搜索

Security checks for vulnerabilities and agentic risk

Overview

This skill is mostly a coherent Ozon shop research integration, but it stores login tokens too broadly and allows API destinations that are not clearly constrained.

Review before installing if you will authenticate to GeekBI. Prefer using only the default GeekBI endpoint, do not supply custom base URLs, and avoid running the skill from shared, synced, or repository directories until token storage is narrowed to one protected user-specific location.

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/ozon_mall_search.py:70
Finding
Unrestricted API Base URL Enables Phishing and Insecure Transmission<![CDATA[ ## Vulnerability Details **File Location**: - `scripts/ozon_mall_search.py:70-78` - `scripts/ozon_mall_info.py:35-43` - `scripts/ozon_site_list.py:47-53` - `scripts/ozon_search_common.py:104-107` - `scripts/geekbi_auth.py:578-580, 695-713` **Vulnerability Type**: User-controlled network destination without scheme or hostname validation **Risk Level**: Medium ### Vulnerable Code ```python # scripts/ozon_mall_search.py:70-78 parser.add_argument("--base-url", default=DEFAULT_BASE_URL) parser.add_argument("--param", action="append", default=[], help="查询条件,格式为 名称=值") parser.add_argument("--timeout", type=float, default=45) 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/ozon_mall_info.py:35-43 parser.add_argument("--mall-id", required=True) parser.add_argument("--site-id", type=int, default=1) parser.add_argument("--base-url", default=DEFAULT_BASE_URL) parser.add_argument("--timeout", type=float, default=45) args = parser.parse_args() try: params = build_params(args.mall_id, args.site_id) payload = authenticated_json_request( build_url(args.base_url, ENDPOINT, params), args.base_url, args.timeout ) ``` ```python # scripts/ozon_site_list.py:47-53 parser.add_argument("--country", help="国家、站点 UID 或域名;不传则返回全部") parser.add_argument("--base-url", default=DEFAULT_BASE_URL) parser.add_argument("--timeout", type=float, default=30) args = parser.parse_args() try: payload = authenticated_json_request( f"{args.base_url.rstrip('/')}{ENDPOINT}", args.base_url, args.timeout ) ``` ```python # scripts/ozon_search_common.py:104-107 def build_url(base_url, endpoint, params): url = f"{base_url.rstrip('/')}{endpoint}" query = urlencode(params) return f"{url}?{query}" if query else url ``` ```python # scripts/geekbi_auth.py:578-580 endpoint = f"{base_url.r ...[truncated 3238 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `--base-url` from production-facing commands and use the fixed `https://openapi.geekbi.com` endpoint. 2. If custom endpoints are required for development, place them behind an explicit development-only option that is disabled by default. 3. Parse endpoints with `urllib.parse.urlsplit` and enforce: - scheme exactly equal to `https`; - hostname exactly matching an approved allowlist; - no username or password component; - only approved ports; - no fragments or ambiguous URL forms. 4. Independently validate every returned `jumpUrl` against an authentication-domain allowlist before storing or displaying it. 5. Reject redirects to unapproved origins. If redirects are required, verify the origin after every redirect. 6. Bind stored authentication state to a canonical, validated origin rather than an arbitrary input string. 7. Add tests covering HTTP URLs, lookalike domains, user-info URLs, alternate ports, malformed URLs, and malicious challenge links. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/geekbi_auth.py:65
Finding
Bearer Authentication State Is Unnecessarily Replicated Across Multiple Directories<![CDATA[ ## Vulnerability Details **File Location**: - `scripts/geekbi_auth.py:65-88` - `scripts/geekbi_auth.py:248-261` - `scripts/geekbi_auth.py:354-374` - `scripts/geekbi_auth.py:637-649` **Vulnerability Type**: Excessive plaintext credential storage **Risk Level**: Medium ### Vulnerable Code ```python # scripts/geekbi_auth.py:65-88 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"), ) 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 # scripts/geekbi_auth.py:248-261 def _write_json_file(path, payload, prefix): path.parent.mkdir(parents=True, exist_ok=True) _restrict_permissions(path.parent, 0o700) temp_path = None try: with tempfile.NamedTemporaryFile( mode="w", encoding="utf-8", dir=path.parent, prefix=prefix, delete=False, ) as handle: temp_path = Path(handle.name) json.dump(payload, handle, ensure_ascii=False) ``` ```python # scripts/geekbi_auth.py:354-374 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)}") ...[truncated 3906 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store authentication state only in a single user-specific configuration or credential location. 2. Remove the Skill-directory and working-directory stores from `_resolve_stores`. 3. Store bearer tokens in an operating-system credential facility such as: - macOS Keychain; - Windows Credential Manager; - Linux Secret Service or an equivalent protected keyring. 4. If file storage remains necessary: - verify directory ownership; - create the directory with restrictive permissions atomically; - verify the resulting mode and ownership; - fail closed if restrictions cannot be established; - avoid writing tokens to network shares or repository workspaces. 5. Store non-sensitive metadata separately from access tokens. 6. Correct the configuration namespace from `temu-research-skill` to a unique Ozon Skill identifier to prevent cross-Skill collisions. 7. On upgrade, securely remove legacy copies from the Skill and workspace directories after migrating valid state to the protected store. 8. Add automated tests proving that login creates only one protected credential record and never writes authentication state beneath the current working directory. ]]>
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
88% confidence
Finding
The skill references executable components (`ozon_site_list.py`, `ozon_mall_search.py`, `ozon_mall_info.py`) and external reference files, which implies shell/network/file-read capabilities, but it declares no permissions or trust boundaries. This mismatch is dangerous because users and the platform cannot accurately assess what the skill may access or execute, increasing the risk of unintended network calls, local file access, or command execution without explicit review.

Intent-Code Divergence

Medium
Confidence
79% confidence
Finding
The auth state is stored under a directory named for a different skill ("temu-research-skill"), which can cause cross-skill state confusion and accidental token sharing if multiple skills use the same local store. In a multi-skill environment, one skill could read, overwrite, or clear another skill's login state, resulting in unauthorized account linkage, session mix-up, or denial of service.

Static analysis

No suspicious patterns detected.