T01 · Skill Instruction Hijacking
- Location
SKILL.md:208- Finding
Skill instructions override the agent's runtime identity and onboarding behavior
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This Amazon Ads skill is mostly purpose-aligned, but it handles advertising credentials and business data with several under-scoped exposure paths users should review first.
Install only if you trust LinkFox with Amazon Ads account access and advertising-spend changes. Before use, avoid custom gateway/login environment variables, disable report HTTP serving unless needed, do not bind report serving to non-loopback addresses, store API keys in a real secret manager rather than shell startup files, and require explicit confirmation before any create/update/budget/bid operation.
SKILL.md:208Skill instructions override the agent's runtime identity and onboarding behavior
scripts/report/get_report.py:286User-controlled bind address can expose sensitive advertising reports without authentication
scripts/manager/_common.py:40Unvalidated gateway overrides can receive LinkFox API keys and Amazon access tokens
references/linkfox-amazon-ads-auth.md:135Onboarding Skill installation uses a mutable archive without integrity verification
references/onboarding.md:11Onboarding guidance persists API keys in plaintext shell configuration
The request destination is derived from environment variables, allowing a caller or surrounding runtime to redirect this script to an arbitrary host. Because the script automatically includes the authorization API key in the Authorization header, a malicious override can exfiltrate credentials and receive sensitive request data intended for the trusted LinkFox gateway.
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 ""
The request destination is derived from environment variables (LINKFOX_TOOL_GATEWAY or AMAZON_ADS_BASE_URL) and is used directly in urlopen without validation or allowlisting. An attacker who can influence the runtime environment can redirect this authenticated request, including the Authorization API key, to an attacker-controlled endpoint, causing credential exfiltration and potentially unauthorized access to downstream services.
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 ""
The request destination is derived from environment variables (LINKFOX_TOOL_GATEWAY or AMAZON_ADS_BASE_URL) and then used directly in urlopen without allowlisting or validation. In a hostile or misconfigured runtime, an attacker who can influence environment variables can redirect this authenticated request, including the Authorization API key, to an attacker-controlled endpoint, causing credential exfiltration and unintended outbound access.
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 ""
The request destination is derived from environment-controlled base URL settings and is then used for a credentialed outbound POST. If an attacker can influence LINKFOX_TOOL_GATEWAY or AMAZON_ADS_BASE_URL, the script will send the Authorization API key and token-refresh payload to an arbitrary host, creating a server-side credential exfiltration path.
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 ""
The request target is derived from environment variables (LINKFOX_TOOL_GATEWAY or AMAZON_ADS_BASE_URL) and then used directly in urlopen while sending the Authorization API key header. If an attacker can influence the environment, they can redirect the request to an attacker-controlled endpoint and exfiltrate the API key and requested token data; the skill context is especially sensitive because this script retrieves advertising access and refresh tokens.
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 ""
The code builds request destinations from environment variables via _login_base() and _agent_user_base(), then sends sensitive authentication material such as SMS login data, access tokens, refresh tokens, generated API tokens, and user identifiers to those URLs. In a skill/agent environment, environment variables are often externally configurable, so an attacker who can influence them can redirect these requests to a malicious host and exfiltrate credentials.
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 = ""
The gateway URL is derived from environment variables and used in urlopen with an Authorization header containing the LinkFox API key. If an attacker can control LINKFOX_AGENT_API_URL or LINKFOX_TOOL_GATEWAY, they can redirect gateway calls to an arbitrary server and capture the API key and account/order data.
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
This skill explicitly supports refreshing and retrieving Amazon Ads access credentials, making it a credential-handling component with real token exposure risk if downstream skills, logs, or stdout leak the returned values. The danger is increased by the documented behavior of writing full responses to disk and, for small responses or with --inline, printing full JSON to stdout, which may include accessToken and refreshToken.
### 步骤 4:刷新过期令牌
- 【输入】`profileId` 或 `authRecordId`
- 【动作】下游返回 HTTP 401 或错误体含 `expired`/`unauthorized`/`access token` 时,调 `refresh_token.py`
- 【输出】新的 `accessToken`(有效期 1 小时)
## 核心概念
The refreshToken and storeTokens interfaces are documented as returning live accessToken and refreshToken values. In the context of this skill, that is security-sensitive because bearer tokens grant API access; if exposed through logs, saved JSON artifacts, chat context, or unauthorized skill chaining, they can be reused to access or manipulate the connected Amazon Ads account.
### 4. 刷新令牌 — `/amazonAds/refreshToken`
`accessToken` 1 小时有效;过期后下游返回 HTTP 401 或错误体含 `expired` / `unauthorized` / `access token`。
**请求**(`authRecordId` 和 `profileId` 二选一):
| 参数 | 类型 | 说明 |
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).
��后:`python scripts/onboarding.py login <phone> <code>`(workbuddy 宿主加 `--channel workbuddy`)
- 拿到 `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>` → 展示优先�
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
#!/usr/bin/env python3
"""
Amazon Ads Token Refresh - LinkFox Skill
Calls /amazonAds/refreshToken to refresh access token
Usage:
python refresh_token.py '{"authRecordId": 123}'
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
#!/usr/bin/env python3
"""
Amazon Ads Token Refresh - LinkFox Skill
Calls /amazonAds/refreshToken to refresh access token
Usage:
python refresh_token.py '{"authRecordId": 123}'
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
#!/usr/bin/env python3
"""
Amazon Ads Token Refresh - LinkFox Skill
Calls /amazonAds/refreshToken to refresh access token
Usage:
python refresh_token.py '{"authRecordId": 123}'
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
#!/usr/bin/env python3
"""
Amazon Ads Token Refresh - LinkFox Skill
Calls /amazonAds/refreshToken to refresh access token
Usage:
python refresh_token.py '{"authRecordId": 123}'
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
#!/usr/bin/env python3
"""
Amazon Ads Token Refresh - LinkFox Skill
Calls /amazonAds/refreshToken to refresh access token
Usage:
python refresh_token.py '{"authRecordId": 123}'
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
#!/usr/bin/env python3
"""
Amazon Ads Token Refresh - LinkFox Skill
Calls /amazonAds/refreshToken to refresh access token
Usage:
python refresh_token.py '{"authRecordId": 123}'
On token fetch failure, the code prints the entire result object to stderr. If the gateway includes sensitive diagnostic data, token material, account identifiers, or backend error payloads, this can leak secrets into logs, consoles, CI output, or telemetry systems where stderr is captured.
def get_access_token(profile_id: int) -> str:
"""Fetch access token for the given profileId via /amazonAds/storeTokens."""
print(f"🔑 Fetching access token for profileId={profile_id}…", file=sys.stderr)
result = call_gateway(STORE_TOKENS_ENDPOINT, {"profileId": int(profile_id)})
if "error" in result or "accessToken" not in result:
print(f"❌ Failed to get access token: {result}", file=sys.stderr)
The script validates and constructs an ad-type-specific path in entity_path, but the actual API mutation call never uses that path and instead calls mutate_entity(...) without an endpoint argument. This creates a dangerous mismatch between documented behavior and actual behavior: callers may believe they are creating one SB ad subtype while the request is sent to a different default endpoint or malformed route, potentially causing unintended ad creation, failed operations, or writes to the wrong API resource.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
print(f"❌ Failed to get access token: {tokens_result}", file=sys.stderr)
sys.exit(1)
access_token = tokens_result["accessToken"]
print("✓ Access token retrieved", file=sys.stderr)
# Step 2: 获取待轮询的 reportId —— 两种来源
# (a) 调用方直接传 reportId(例如上次超时但报告仍在跑)
The skill description is broad enough to trigger on generic Amazon advertising requests, including high-impact write operations such as bid, budget, and status changes. In an agentic environment, overly broad invocation criteria increase the chance the skill is selected when the user intended only discussion or analysis, which can lead to unnecessary account access or unintended ad mutations.
The skill states that report retrieval exposes extracted JSON through a temporary local HTTP server on 127.0.0.1, but the top-level description does not clearly warn users that sensitive advertising data will be served over a reachable local endpoint. This creates unnecessary local data exposure risk because other local processes, browser extensions, malware, or co-tenant software on the host may access the report during the exposure window.
The skill persistently writes full API responses to local storage, including authorization and advertising-management data, without giving a prominent up-front warning in the descriptive sections. Persistent storage of sensitive business data and possibly token-bearing responses expands the attack surface through disk disclosure, backups, shared workspaces, and later unintended reuse.
This markdown file presents all operational instructions and examples in Chinese, which effectively forces a specific language for users. The policy allows locale constraints only when the skill offers user choice or clearly documents a justified region-specific limitation, neither of which is stated here.
The primary description is written as a Chinese-only instruction set and does not indicate that the skill can respond in other languages or let the user choose a locale. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
export KEY=$LINKFOXAGENT_API_KEY