Back to skill

Security audit

Shopee-店铺健康

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its Shopee account-health purpose, but it handles API keys, SMS login, billing orders, and plaintext saved account data with weak scoping and disclosure.

Install only if you trust LinkFox with Shopee account-health data and are comfortable with the skill handling API keys, phone/SMS login, and unpaid order/payment flows. Avoid custom gateway environment variables unless you control the endpoint, do not paste OTPs or API keys into shared logs, rotate any exposed key, and review saved linkfox response files because they may contain sensitive store data.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/_shopee_account_health_common.py:18
Finding
Credential Exfiltration Through an Environment-Controlled Gateway<![CDATA[ ## Vulnerability Details **File Location**: `scripts/_shopee_account_health_common.py:18-20, 70-88` **Vulnerability Type**: Unrestricted credential-bearing network destination **Risk Level**: High ### Vulnerable Code ```python API_BASE_URL = os.environ.get("LINKFOX_TOOL_GATEWAY") or os.environ.get("SHOPEE_API_BASE_URL") or "https://tool-gateway.linkfox.com" STORE_TOKENS_ENDPOINT = f"{API_BASE_URL.rstrip('/')}/shopee/storeTokens" DEVELOPER_PROXY_ENDPOINT = f"{API_BASE_URL.rstrip('/')}/shopee/developerProxy" ``` ```python def call_api(endpoint: str, params: dict) -> dict: api_key = get_api_key() data = json.dumps(params).encode("utf-8") req = Request( endpoint, data=data, headers={ "Authorization": api_key, "Content-Type": "application/json", "User-Agent": "LinkFox-Skill/1.0", "SESSION_ID": os.environ.get("SESSION_ID", ""), "MESSAGE_ID": os.environ.get("MESSAGE_ID", ""), "MODE_ID": os.environ.get("MODE_ID", ""), "APP_NAME": os.environ.get("APP_NAME", ""), }, method="POST", ) try: with urlopen(req, timeout=150) as response: return json.loads(response.read().decode("utf-8")) ``` ### Technical Analysis Every Account Health request places the LinkFox API key in the `Authorization` header. The destination is derived from `LINKFOX_TOOL_GATEWAY` or `SHOPEE_API_BASE_URL`, both of which can completely replace the trusted default host. No validation requires HTTPS, checks the destination against an approved hostname, or asks the user to confirm that credentials will be disclosed to a non-default service. Consequently, an environment modification can redirect the API key, session metadata, shop or merchant identifier, and API request parameters to an arbitrary endpoint. The Account Health functionality legitimately requires network access and authentication, but allowing unrestricted redirection o ...[truncated 1291 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Allowlist the exact approved gateway hostname, such as `tool-gateway.linkfox.com`. 2. Require an `https` scheme and reject HTTP, embedded credentials, unexpected ports, fragments, and non-approved hosts. 3. If custom gateways are a necessary enterprise feature, require an explicit command-line option and informed confirmation before sending credentials. 4. Use separate, narrowly scoped credentials for custom endpoints rather than forwarding the primary LinkFox API key. 5. Do not transmit `SESSION_ID`, `MESSAGE_ID`, `MODE_ID`, or `APP_NAME` unless each field is necessary for the requested API operation. 6. Add automated tests confirming that malicious, malformed, HTTP, and look-alike destinations are rejected. 7. Document the exact recipient, data fields, and credential scope before the first network request. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/_shopee_account_health_common.py:183
Finding
Plaintext Response Persistence With Session-ID Path Traversal and Undocumented Temporary-Directory Fallback<![CDATA[ ## Vulnerability Details **File Location**: `scripts/_shopee_account_health_common.py:183-208, 216-224, 315-333` **Vulnerability Type**: Unsafe path construction and plaintext storage of sensitive responses **Risk Level**: High ### Vulnerable Code ```python def _lf_root() -> str: cached = _LF_SESSION_CACHE.get("_root") if cached: return cached candidates = [] acpx = (os.environ.get("ACPX_WORKSPACES") or "").strip() if acpx: acpx = acpx.split(os.pathsep)[0].strip() if acpx: candidates.append(os.path.join(acpx, "linkfox")) candidates.append(os.path.join(os.getcwd(), "linkfox")) candidates.append(os.path.join(os.path.expanduser("~"), "linkfox")) candidates.append(os.path.join(_lf_tempfile.gettempdir(), "linkfox")) for root in candidates: try: os.makedirs(root, exist_ok=True) probe = os.path.join(root, ".write_probe") with open(probe, "w", encoding="utf-8") as f: f.write("") os.remove(probe) except OSError: continue root = os.path.abspath(root) _LF_SESSION_CACHE["_root"] = root return root ``` ```python def _lf_session_id(ts: float) -> str: env = os.environ.get("SESSION_ID") if env: return env.strip() if "_auto" not in _LF_SESSION_CACHE: _LF_SESSION_CACHE["_auto"] = ( _lf_time.strftime("%H%M%S", _lf_time.localtime(ts)) + "-" + _lf_secrets.token_hex(3) ) return _LF_SESSION_CACHE["_auto"] ``` ```python def emit_result(result, slug=SLUG, inline=False): """落盘完整响应到 linkfox/<date>/<session>/data/<slug>-<ts>.json;大响应只打印摘要。无缓存。""" serialized = json.dumps(result, ensure_ascii=False, indent=2) ts = _lf_time.time() date_str = _lf_time.strftime("%Y-%m-%d", _lf_time.localtime(ts)) sid = _lf_session_id(ts) root = _lf_root() session_dir = os.path.join(root, date_str, sid) os.makedirs(session_dir, exi ...[truncated 2711 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate `SESSION_ID` with a strict allowlist, such as letters, digits, underscores, and hyphens with a conservative maximum length. 2. Reject absolute paths, path separators, `.` and `..` components, drive prefixes, and platform-specific alternate separators. 3. Resolve the final path and verify with `os.path.commonpath()` that it remains beneath the approved root before creating directories or files. 4. Remove the home-directory and temporary-directory fallbacks if they are inconsistent with the declared storage policy. Fail closed when the approved project directory is unavailable. 5. Make complete-response persistence opt-in, especially for order and disciplinary data. 6. Create directories and files with owner-only permissions, such as mode `0700` for directories and `0600` for files. 7. Use authenticated encryption or an operating-system protected data store where responses must survive the session. 8. Define and enforce a retention period and provide a secure deletion mechanism. 9. Avoid printing the full storage path if logs or transcripts may be accessible to unrelated users. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/onboarding.py:478
Finding
Newly Issued API Key Is Printed to Standard Output and Recommended for Plaintext Shell-Profile Storage<![CDATA[ ## Vulnerability Details **File Location**: `scripts/onboarding.py:478-516`; `references/onboarding.md:11-15` **Vulnerability Type**: Plaintext credential disclosure and insecure credential persistence **Risk Level**: Medium ### Vulnerable Code ```python def login_and_get_key(phone: str, code: str, channel: str) -> dict: masked = _mask_phone(phone) if not re.fullmatch(r"\d{11}", phone): return {"error": f"login: 手机号格式不正确: {phone}", "phone": masked} if not re.fullmatch(r"\d{4,8}", code): return {"error": f"login: 验证码格式不正确: {code}", "phone": masked} lg = _login_v3(phone, code, channel) if "error" in lg: return {"error": lg["error"], "phone": masked} if lg.get("is_new_user"): lbt = _login_by_token(lg["access_token"], lg["refresh_token"]) if "error" in lbt: print(f"{TAG} {lbt['error']}(不影响拿 key)", file=sys.stderr) info = _fetch_user_info_v3(lg["access_token"], lg["user_id"]) if "error" in info: return {"error": info["error"], "phone": masked} tok = _get_or_generate_api_token(lg["access_token"], lg["user_id"], info["group_id"]) if "error" in tok: return {"error": tok["error"], "phone": masked} return { "api_key": tok["api_key"], "phone": masked, "group_id": info["group_id"], "member_id": info["member_id"], "source": tok["source"], "nick_name": lg.get("nick_name", ""), "team_name": info.get("team_name", ""), "is_new_user": lg.get("is_new_user", False), } ``` ```python def _emit(obj: dict) -> None: print(json.dumps(obj, ensure_ascii=False, indent=2)) def _cmd_login(args) -> int: r = login_and_get_key(args.phone.strip(), args.code.strip(), args.channel) _emit(r) if "api_key" in r: print(f"{TAG} 成功获取 API key(来源: {r['source']})", file=sys.stderr) return 0 return 1 ``` The onboarding documentation further recommends persistent plaintext shell configuration: ```text Wi ...[truncated 2168 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Never print a complete API key to ordinary stdout or stderr. 2. Store the issued key directly in an operating-system credential manager, agent-managed secret store, or similarly protected facility. 3. If a one-time display is unavoidable, require an explicit interactive confirmation, prevent transcript capture where supported, and display the secret only once. 4. Redact credentials in all structured output, logs, exceptions, and diagnostic responses. 5. Replace shell-profile instructions with platform-specific secret-storage guidance. 6. If environment variables remain supported, place them in an owner-readable configuration file with restrictive permissions rather than general shell startup files. 7. Issue narrowly scoped, revocable, short-lived API tokens and provide immediate rotation and revocation procedures. 8. Review existing transcripts and logs for leaked keys and rotate any credential that may already have been exposed. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (35)

Tainted flow: 'req' from os.environ.get (line 73, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
method="POST",
    )
    try:
        with urlopen(req, timeout=150) as response:
            return json.loads(response.read().decode("utf-8"))
    except HTTPError as e:
        body = e.read().decode("utf-8") if e.fp else ""
Confidence
97% confidence
Finding
The request destination and multiple outbound headers are derived from environment variables, including the base URL and session/application metadata, and the Authorization API key is sent to whatever endpoint API_BASE_URL resolves to. If an attacker can influence environment variables or runtime configuration, they can redirect requests to an attacker-controlled server and exfiltrate credentials and identifiers.

Tainted flow: 'url' from os.environ.get (line 235, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
except RuntimeError as e:
        return {"_error": str(e)}
    try:
        r = requests.post(url, json=body or {}, headers=headers, timeout=timeout)
        return r.json()
    except Exception as e:
        body_text = ""
Confidence
94% confidence
Finding
The code builds destination URLs from environment-controlled base URLs and then sends sensitive data such as phone numbers, SMS codes, access tokens, refresh tokens, and generated API keys via requests.post. If an attacker can influence these environment variables, the CLI can be redirected to an attacker-controlled endpoint, causing credential exfiltration and account compromise.

Tainted flow: 'req' from os.environ.get (line 244, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
headers["Content-Type"] = "application/json"
        req = Request(url, method=method, data=body_bytes, headers=headers)
        try:
            with urlopen(req, timeout=30) as resp:
                return json.loads(resp.read().decode())
        except urllib.error.HTTPError as e:
            status = e.code
Confidence
92% confidence
Finding
The gateway request path uses a URL derived from environment variables and attaches the LinkFox API key in the Authorization header before calling urlopen. An attacker who controls the environment can redirect these authenticated requests to a malicious server and capture the API key and account data.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
If the underlying behavior includes SMS login, token retrieval, API key generation, subscription ordering, and payment flows while claiming only account-health queries, the skill crosses into credential handling and financial operations without transparent disclosure. That creates significant risk of secret exposure, account misuse, unintended purchases, and user deception in a context where only read-oriented shop health data was expected.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
If the underlying behavior includes SMS login, token retrieval, API key generation, subscription ordering, and payment flows while claiming only account-health queries, the skill crosses into credential handling and financial operations without transparent disclosure. That creates significant risk of secret exposure, account misuse, unintended purchases, and user deception in a context where only read-oriented shop health data was expected.

YARA rule 'backdoor_persistence': Backdoor persistence with malicious payloads (shell commands, SSH key injection, hidden root users) [malware]

High
Category
YARA Match
Content
示 JSON 里的 phone/agreements
   - 收到验证码后:`python scripts/onboarding.py login <phone> <code>`
   - 拿到 `api_key` 后把下面三平台配置转发给用户,提示重启会话生效:
     - Windows PowerShell(永久):`setx LINKFOX_AGENT_API_KEY "<key>"`
     - macOS zsh:`echo 'export LINKFOX_AGENT_API_KEY="<key>"' >> ~/.zshrc && source ~/.zshrc`
     - Linux bash:`echo 'export LINKFOX_AGENT_API_KEY="<key>"' >> ~/.bashrc && source ~/.bashrc`
     - 变量名 `LINKFOX_AGENT_API_KEY`(主推)或 `LINKFOXAGENT_API_KEY`(老规范)任一即可

**billing 场景**:`errcode=402` 或消息含 `算力/余额/quota/insufficient/充值/套餐到期`。
- `python scripts/onboarding.py list-plans` → 有 AskUserQuestion 就弹菜单,否则输出编号清单让用户选
- 校验 `plan_id` ∈ 清单、支付方式 ∈ 该套餐 `available_methods`(通常 `wechat/alipay`)
- `python scripts/onboarding.py order <plan_id> <method>` → 展示优先级 PNG
Confidence
75% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
This file implements LinkFox onboarding, login, token generation, subscription purchase, and payment operations, which are unrelated to the declared Shopee Account Health read/query scope. Such scope mismatch is dangerous because users or orchestrators may grant trust and permissions appropriate for a read-only Shopee skill while the code performs unrelated sensitive account and billing actions.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The order creation and payment QR flow introduces real billing capability into a skill advertised as a Shopee account-health query tool. In context, this is especially dangerous because it expands from passive data retrieval to financial transactions, creating risk of unauthorized charges or deceptive purchase flows.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The SMS login, access-token handling, team discovery, and API-token generation functionality is unjustified for a Shopee account-health query skill and materially increases the skill's power. This can expose highly sensitive credentials and enable account takeover or unauthorized API access if misused, especially because users may not expect such onboarding behavior from the stated skill purpose.

Lp3

Medium
Category
MCP Least Privilege
Confidence
85% confidence
Finding
The skill advertises shell, network, environment-variable access, and file writes, but does not declare any explicit tool scope or allowed-tools boundary. In an agent environment, this weakens least-privilege controls and can allow broader execution than reviewers or users would expect, especially because the skill also instructs writing responses to disk and depends on environment secrets.

Vague Triggers

Medium
Confidence
93% confidence
Finding
An overly broad trigger can cause the skill to activate for loosely related Shopee topics, increasing the chance that shell/network/file-writing behavior runs when the user did not intend to invoke this integration. In an agent setting, unnecessary invocation expands attack surface and may expose store identifiers, local files, or API-backed data more often than necessary.

External Transmission

Medium
Category
Data Exfiltration
Content
---

## curl 示例

```bash
curl -X POST https://tool-gateway.linkfox.com/shopee/developerProxy \
Confidence
86% confidence
Finding
The example explicitly directs authenticated requests and account-health data to an external endpoint, which establishes a clear external transmission path for sensitive commercial and account-status information. In this skill context, the transmitted data can include store performance, enforcement history, and order issues, so misuse, interception in downstream systems, or over-collection by the proxy could expose sensitive business information.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The document provides a ready-to-use authenticated curl example that sends an API key, access token, and shop identifier to an external gateway, but it does not warn about the sensitivity of those credentials or the account data being transmitted. In a skill that handles store health, penalty, punishment, listing issue, and late-order data, this increases the chance that operators will copy real secrets into requests without understanding the exposure and trust boundary.

Natural-Language Policy Violations

Medium
Confidence
89% confidence
Finding
The file’s instructional content is written entirely in Chinese, including the title, parameter descriptions, and usage notes, with no indication that language selection is optional or that the skill is intended only for a Chinese-speaking or region-specific audience. Under the stated policy, forcing a specific language without user opt-in is a natural-language policy concern.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The instructions explicitly tell the agent to ask for a user's phone number and use it to drive a registration/login flow, but they provide no requirement to obtain informed consent, minimize retention, or warn about handling sensitive personal data and one-time codes. In an agent setting, this can normalize unsafe collection of PII and authentication material, increasing the risk of privacy violations, account takeover abuse, or accidental disclosure through logs/transcripts.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The billing workflow instructs the agent to create payment orders and display payment artifacts such as PNGs, pay URLs, or ASCII QR codes without requiring a clear warning, confirmation step, or provenance check. In practice this can lead users to follow payment links or scan codes without understanding they are leaving the trusted flow, making phishing, misbilling, or payment redirection more likely.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
This code performs a network call through developerProxy and may transmit identifiers, query parameters, and POST bodies including account-health-related data. While the module docstring states the purpose at a high level, there is no confirmation prompt, user-facing log/print, or explicit warning in this file that request data will be sent to an external service.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
print(f"DEPENDENCY_MISSING: {json.dumps(payload, ensure_ascii=False)}", file=sys.stderr)
        sys.exit(DEPENDENCY_EXIT_CODE)
    try:
        result = subprocess.run(
            [sys.executable, str(checker)],
            capture_output=True,
            text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The code forwards SESSION_ID, MESSAGE_ID, MODE_ID, and APP_NAME from the environment on every API request without any minimization or disclosure in this file. These identifiers may enable tracking, correlation, or unintended sharing of internal context with upstream services, especially when combined with the configurable outbound endpoint.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The module persistently writes full API responses to local disk under predictable directories, even though the stated purpose is API querying rather than local retention. Account-health responses may contain sensitive operational, penalty, and shop-identifying data, so unnecessary persistence increases exposure through local compromise, accidental disclosure, or cross-session data leakage.

External Transmission

Medium
Category
Data Exfiltration
Content
except RuntimeError as e:
        return {"_error": str(e)}
    try:
        r = requests.post(url, json=body or {}, headers=headers, timeout=timeout)
        return r.json()
    except Exception as e:
        body_text = ""
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Natural-Language Policy Violations

Medium
Confidence
88% confidence
Finding
The natural-language messages and request payload hard-code domestic 11-digit phone numbers and area code +86, effectively constraining the skill to Chinese phone numbers. There is no visible opt-in or justification in the user-facing text that this is a China-specific flow, which can violate language/locale policy when a fixed locale is imposed silently.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
This code performs multiple network calls involving a phone number, SMS code, access token, refresh token, and generated API key, but the only disclosure is a generic module docstring saying environment-variable instructions are shown elsewhere. Within this code path there is no confirmation prompt or explicit user-facing warning that sensitive account data and credentials will be sent to remote LinkFox services and that an API key will be generated/retrieved.

Natural-Language Policy Violations

Low
Confidence
77% confidence
Finding
The file presents core invocation and usage instructions in a fixed Chinese/English mixed form, but does not state whether the user can choose language or locale. Under the language/locale policy rule, forcing a specific presentation language without opt-in can be a policy concern.

Intent-Code Divergence

Low
Confidence
98% confidence
Finding
The documentation states at L014 that using the tool '会消耗算力' and requires the user to be warned before additional retrieval attempts, but later states at L098 '不消耗算力'. These statements cannot both be true and create intent ambiguity about resource usage and when the user should be cautioned.

Static analysis

No suspicious patterns detected.