T09 · Insecure Skill Coding Practices
- Location
scripts/execute_operation.py:21- Finding
Undisclosed Persistent Cross-Skill Tracking Identifier
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill matches its TikTok commerce research purpose, but it quietly creates and sends a persistent install identifier and can forward credentials to a configurable API endpoint without clear safeguards.
Review before installing. Expect requests to the scdata gateway and avoid placing unrelated secrets in local .env files. Only use a trusted HTTPS SCRUMBALL_BASE_URL, and be aware that the current runner creates and sends a persistent install ID unless you manually remove or patch that behavior.
scripts/execute_operation.py:21Undisclosed Persistent Cross-Skill Tracking Identifier
scripts/execute_operation.py:150Authorization Credentials Can Be Sent Over Plaintext HTTP
The code allows environment-controlled configuration, especially SCRUMBALL_BASE_URL and SCRUMBALL_AUTH_PREFIX/API key handling, to influence outbound requests made via urllib.request.urlopen. This can redirect requests, authorization headers, and the persistent install identifier to an attacker-controlled endpoint, creating SSRF-style egress and credential/privacy leakage risk.
req = urllib.request.Request(url=url, method=op["method"], headers=req_headers, data=data)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
raw = resp.read().decode("utf-8")
try:
payload = json.loads(raw) if raw else None
The skill explicitly tells the agent to load a local .env file when executing commands. In an agent environment, that can encourage access to locally stored secrets and normalize secret retrieval as part of routine operation, increasing the risk of credential exposure or misuse beyond the intended API key.
## Tooling
Execution commands:
- List operations: `python3 scripts/execute_operation.py --env-file .env list`
- Sale snapshot: `python3 scripts/execute_operation.py --env-file .env call --operation tiktok_user_shop_sale --query '{"unique_id":"nike"}'`
- Goods list: `python3 scripts/execute_operation.py --env-file .env call --operation tiktok_user_shop_goods_list --query '{"unique_id":"nike","page":1,"page_size":20}'`
The surrounding tooling guidance continues the pattern of relying on .env-based secret loading without constraining what may be read. In a skill with network capabilities, this is more dangerous because secrets sourced from local files can be combined with outbound requests to unauthorized destinations.
Execution commands:
- List operations: `python3 scripts/execute_operation.py --env-file .env list`
- Sale snapshot: `python3 scripts/execute_operation.py --env-file .env call --operation tiktok_user_shop_sale --query '{"unique_id":"nike"}'`
- Goods list: `python3 scripts/execute_operation.py --env-file .env call --operation tiktok_user_shop_goods_list --query '{"unique_id":"nike","page":1,"page_size":20}'`
Required environment variables:
The skill lists required environment variables including the API gateway base URL and API key, alongside instructions that imply automated loading from local secret files. This materially raises the risk of credential access and misuse, especially since the skill also supports outbound network calls to a remote gateway.
Execution commands:
- List operations: `python3 scripts/execute_operation.py --env-file .env list`
- Sale snapshot: `python3 scripts/execute_operation.py --env-file .env call --operation tiktok_user_shop_sale --query '{"unique_id":"nike"}'`
- Goods list: `python3 scripts/execute_operation.py --env-file .env call --operation tiktok_user_shop_goods_list --query '{"unique_id":"nike","page":1,"page_size":20}'`
Required environment variables:
- `SCRUMBALL_BASE_URL`: API gateway base URL
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
elif os.getenv("SCRUMBALL_ENV_FILE"):
candidates.append(Path(os.getenv("SCRUMBALL_ENV_FILE", "")).expanduser())
else:
candidates.append(Path.cwd() / ".env")
candidates.append(SKILL_ROOT / ".env")
for p in candidates:
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
elif os.getenv("SCRUMBALL_ENV_FILE"):
candidates.append(Path(os.getenv("SCRUMBALL_ENV_FILE", "")).expanduser())
else:
candidates.append(Path.cwd() / ".env")
candidates.append(SKILL_ROOT / ".env")
for p in candidates:
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
elif os.getenv("SCRUMBALL_ENV_FILE"):
candidates.append(Path(os.getenv("SCRUMBALL_ENV_FILE", "")).expanduser())
else:
candidates.append(Path.cwd() / ".env")
candidates.append(SKILL_ROOT / ".env")
for p in candidates:
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
def main() -> int:
parser = argparse.ArgumentParser(description="Run operations for this skill")
parser.add_argument("--env-file", help="Optional .env file path; if omitted, auto-tries ./.env then <skill-root>/.env")
sub = parser.add_subparsers(dest="cmd", required=True)
The skill documents execution paths that rely on environment variables, local files, and networked API calls, but it declares no explicit tool scope or permission boundaries. This increases the chance an agent runtime grants broader access than necessary, enabling unintended reads of local .env files or outbound requests without clear user consent.
The skill instructs the assistant to direct users to an external site and to set an API key in their environment after quota exhaustion. That creates an external transmission and credential-handling workflow outside the host platform's trust boundary, which can facilitate phishing, credential misuse, or data exfiltration if the link or gateway is not tightly controlled.
- **Free tier**: a limited number of calls per day, zero-config (no key required — the skill defaults to a hosted gateway that injects the key server-side).
- **When any operation returns HTTP 429, or the response body has `code=429` / contains `quota_exceeded`, you (the assistant) MUST immediately and clearly tell the user** (do not silently retry, do not treat it as a generic error):
> Free quota exhausted. Register and get your own API key at **https://data.scdata.cc/pricing**, then set the environment variable `SCRUMBALL_API_KEY` to your key (in your host/agent environment or a local `.env`). You can then continue with your own quota.
- The 429 response body usually includes `apply_url` and `env_var` fields you can quote directly.
The trigger phrases are broad and generic enough to match common user requests about shopping, sales, product details, or live shopping, which can cause the skill to activate outside its intended scope. In a research-oriented commerce skill, this increases the chance of unintended invocation, misleading responses, or unnecessary exposure of the skill’s capabilities in unrelated conversations.
The script generates a stable cross-run identifier and stores it in the user home directory or skill directory, then transmits it on every request as x-install-id. For a TikTok commerce intelligence skill, this persistence is not necessary to fulfill the stated function and enables tracking, quota enforcement, and user correlation across runs without meaningful notice or consent.
The script writes a stable installation ID to disk without user notice, making tracking persistent across runs and potentially across multiple skills when the home-directory file is used. This creates a privacy vulnerability because it establishes durable correlation for usage analytics or quotas unrelated to the user-visible task.
The code automatically sends authorization data and a stable x-install-id with outbound requests, but there is no user-facing disclosure or consent mechanism. In this skill context, the combination of analytics-style tracking and optional credential forwarding increases privacy and data-handling risk beyond what a user may reasonably expect from a product research tool.
The capability names for TikTok operations are presented only in Chinese, while the rest of the skill is written in English. This creates a language-policy inconsistency and may force a specific language presentation on users without opt-in or explanation.
The markdown lists all endpoint summaries in Chinese only (for example, '实时采集-商品详情' and '用户带货商品列表') with no indication that the skill is region-specific or that users can choose another language. This can violate a language/locale policy when a skill implicitly enforces a single language without opt-in or documented justification.
This markdown file includes user-facing natural language such as the summary '实时采集-商品详情' and example message '请求成功' in Chinese while the surrounding documentation is otherwise in English. Under the policy rule for language/locale, forcing one language without user opt-in or justification is a natural-language policy concern.
The docstring text is written only in Chinese for behavior that affects installation tracking, with no indication that language can be selected or localized. This can violate language or locale policy when a skill imposes a specific language without user opt-in.
The inline comment says the default managed gateway means callers need zero configuration and can leave SCRUMBALL_API_KEY empty. However, run_doctor marks the environment as not OK unless both base_url and api_key are set, which contradicts that documented intent and may mislead operators about actual required configuration.