Back to skill

Security audit

TikTok官方-店铺ERP授权

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its TikTok Shop ERP authorization purpose, but it handles credentials and tokens with under-scoped network and local-storage behavior that users should review before installing.

Install only if you trust LinkFox and your execution environment. Protect LINKFOX_AGENT_API_KEY/LINKFOXAGENT_API_KEY, do not set LINKFOX_TOOL_GATEWAY or TIKTOK_SHOP_API_BASE_URL unless you control the destination, and treat the generated linkfox output directory as sensitive because it can retain authorization URLs, seller identifiers, token-related responses, and error details.

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

Error
Location
scripts/_lf_output.py:143
Finding
Sensitive API responses are persisted with unsafe filesystem defaults<![CDATA[ ## Vulnerability Details **File Location**: `scripts/_lf_output.py:28-54, 143-169` **Vulnerability Type**: Sensitive data exposure through insecure local storage **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 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, exist_ok=True) _lf_ensure_meta(root, session_dir, date_str, sid, ts) data_dir = os.path.join(session_dir, "data") os.makedirs(data_dir, exist_ok=True) out = os.path.join(data_dir, f"{slug}-{int(ts * 1_000_000)}.json") try: with open(out, "w", encoding="utf-8") as f: f.write(serialized) print(f"Saved full response: {out} ({len(serialized)} bytes)") ex ...[truncated 3123 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable response persistence by default, especially for authorization and token-management endpoints. 2. Require an explicit command-line option or trusted configuration setting before writing responses to disk. 3. Recursively redact fields whose names or values indicate credentials, including: - `accessToken` - `refreshToken` - `Authorization` - OAuth `state` - Cookies and nested token objects 4. Avoid persisting raw HTTP error bodies unless they have been sanitized. 5. Create storage directories with mode `0700` and files with mode `0600`. 6. Use exclusive, symlink-resistant creation, such as `os.open` with `O_CREAT | O_EXCL | O_NOFOLLOW` where supported. 7. Validate that all resolved paths remain under the intended storage root. 8. Implement a documented retention period and secure cleanup for authentication-related records. 9. Print only a sanitized summary to stdout and avoid printing complete small responses automatically. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/authorize_url.py:19
Finding
Gateway overrides can redirect API credentials to an arbitrary network destination<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authorize_url.py:19-24, 37-55`; equivalent logic in `scripts/authorized_stores.py:19-24, 35-53`, `scripts/store_tokens.py:23-28, 43-61`, and `scripts/refresh_token.py:23-28, 43-61` **Vulnerability Type**: Unrestricted credential-bearing endpoint configuration **Risk Level**: Medium ### Vulnerable Code ```python API_BASE_URL = ( os.environ.get("LINKFOX_TOOL_GATEWAY") or os.environ.get("TIKTOK_SHOP_API_BASE_URL") or "https://tool-gateway.linkfox.com" ).rstrip("/") API_ENDPOINT = f"{API_BASE_URL}/tiktokShop/authorizeUrl" ``` ```python def call_api(params: dict) -> dict: """Call the authorization URL API.""" api_key = get_api_key() data = json.dumps(params).encode("utf-8") req = Request( API_ENDPOINT, data=data, headers={ "Authorization": api_key, "Content-Type": "application/json", "User-Agent": "LinkFox-Skill/1.0", }, method="POST", ) try: with urlopen(req, timeout=150) as response: return json.loads(response.read().decode("utf-8")) ``` The same endpoint-selection and authorization-header pattern is used by all four network-facing entry points. ### Technical Analysis The scripts intentionally send a LinkFox API key to the configured gateway because remote authentication and token management are part of the declared Skill functionality. The default endpoint, `https://tool-gateway.linkfox.com`, is consistent with the project documentation. However, `LINKFOX_TOOL_GATEWAY` and `TIKTOK_SHOP_API_BASE_URL` are accepted without validation. The code does not enforce: - HTTPS transport. - A trusted hostname allowlist. - An expected port. - The absence of embedded URL credentials. - An explicit opt-in for non-production gateways. Consequently, a modified process environment can redirect the `Authorization` header and request payload to an attacker-controlled host. A plaint ...[truncated 1647 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require the `https` scheme for every credential-bearing endpoint. 2. Allow only explicitly trusted gateway hostnames, with `tool-gateway.linkfox.com` as the default production host. 3. If development gateways are necessary, require a separate explicit opt-in and use separate, narrowly scoped development credentials. 4. Reject URLs containing user information, fragments, unexpected ports, or malformed hostnames. 5. Resolve and validate the final destination before attaching the `Authorization` header. 6. Prevent credentials from being forwarded when a redirect changes the scheme, hostname, or port. 7. Use least-privilege API keys restricted to the required TikTok Shop ERP authorization endpoints. 8. Document the security consequences of environment-based gateway overrides and protect launcher and CI environment configuration from untrusted modification. 9. Centralize HTTP transport and destination validation in one shared module so all four scripts enforce identical controls. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (17)

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

Critical
Category
Data Flow
Content
)

    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
90% confidence
Finding
The request destination is derived from environment-controlled configuration via API_BASE_URL and then sent through urlopen without validating the host or scheme. If an attacker can influence environment variables in the execution context, they can redirect requests containing the Authorization API key to a malicious server, causing secret exfiltration and potentially SSRF-style outbound access.

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

Critical
Category
Data Flow
Content
)

    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
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
)

    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
95% confidence
Finding
The request destination is derived from environment variables via API_BASE_URL, so anyone who can influence the runtime environment can redirect this script to an attacker-controlled endpoint. That would exfiltrate the Authorization API key and the seller openId, and could also return attacker-controlled JSON that the skill treats as a valid response. In an auth/token-management skill, this is especially sensitive because it handles credentials and token refresh operations.

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

Critical
Category
Data Flow
Content
)

    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
90% confidence
Finding
The request target is derived from environment variables (`LINKFOX_TOOL_GATEWAY` / `TIKTOK_SHOP_API_BASE_URL`) and then used directly in `urlopen`, so anyone who can influence the runtime environment can redirect this code to an attacker-controlled endpoint. Because the request includes the `Authorization` API key header and sensitive token-query parameters, this becomes an SSRF/credential-exfiltration risk rather than a harmless configurability feature.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The documented purpose is TikTok Shop ERP authorization and token management, but the described/observed behavior includes generic response capture, local storage management, and arbitrary result persistence rather than narrowly scoped OAuth actions. This mismatch is dangerous because reviewers and downstream agents may trust the declared purpose while the implementation can store sensitive material locally and perform broader actions than expected.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
1. `authorized_stores` 选店 → 得到 `openId`
2. 切换到对应 `linkfox-tiktok-shop-*` 业务 skill,脚本只传 `openId`(token 由网关解析)

## Display Rules

1. 只呈现授权/店铺/令牌数据,不做业务建议。
2. 勿明文输出完整 token,仅掩码。
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill advertises network access, local file writing, and environment interaction without declaring an explicit tool scope or permission boundary. In an agent setting, undocumented capabilities increase the chance of unintended data access or persistence, especially because the large-response workflow explicitly writes potentially auth-sensitive responses to disk.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
This documentation explicitly exposes token-query and token-refresh endpoints and includes example responses containing full accessToken and refreshToken values. Even though framed as optional/debug functionality, documenting raw token retrieval materially increases the chance that downstream agents, operators, or logs will surface bearer credentials that can be reused to access seller accounts.

External Transmission

Medium
Category
Data Exfiltration
Content
---

## curl Examples

```bash
# ERP 授权链接
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The helper serializes and writes the full API response to disk under a predictable local directory tree. In the context of a TikTok Shop ERP OAuth and token-management skill, responses can reasonably contain authorization codes, access tokens, refresh tokens, shop identifiers, or other sensitive account data, so unconditional persistence materially increases the risk of credential leakage and unintended retention.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The module writes response data to disk transparently and only prints that a file was saved after the fact, without any explicit consent or warning before persistence. For an auth/token-management skill, silent local storage is especially risky because users and downstream operators may assume ephemeral handling while sensitive OAuth artifacts and shop data are being retained on disk.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
This code performs an HTTP POST to a remote service using the user's openId and an Authorization header derived from environment variables. Although the module docstring describes the API purpose, the runtime path does not provide a visible disclosure or confirmation before transmitting data and credentials off-box.

Intent-Code Divergence

Low
Confidence
96% confidence
Finding
The header comment says '无缓存' and the emit_result docstring repeats that claim, yet the module maintains _LF_SESSION_CACHE and reuses cached values for the root path and generated session ID. This is a direct contradiction between documentation and implemented behavior.

Context-Inappropriate Capability

Low
Confidence
77% confidence
Finding
The helper inspects ACPX_WORKSPACES and SESSION_ID, probes multiple filesystem locations for write access, and creates cross-session metadata such as index.jsonl. For a skill whose stated purpose is TikTok Shop ERP authorization and authorized-shop lookup, this local environment/session bookkeeping is not directly justified by the manifest.

Natural-Language Policy Violations

Low
Confidence
91% confidence
Finding
This code includes user-facing text and explanatory comments in Chinese, such as the API key error message, without any opt-in or alternative locale. Under the language/locale policy rule, forcing a specific language in user-visible output can be a natural-language policy violation unless the locale constraint is explicitly justified.

Natural-Language Policy Violations

Low
Confidence
94% confidence
Finding
The docstring and stderr message are written in Chinese, including the missing-API-key error shown to users. This imposes a specific language on users without opt-in or justification, which matches the language/locale policy-violation category.

Natural-Language Policy Violations

Low
Confidence
94% confidence
Finding
The docstring and stderr message use Chinese-language text for authentication guidance and error output, while the file does not offer any locale selection or explain that the skill is intentionally Chinese-only. This creates a natural-language locale policy issue because the skill imposes a specific language on users without opt-in.

Static analysis

No suspicious patterns detected.