T02 · Agent Memory Poisoning
- Location
SKILL.md:250- Finding
Persistent Agent Memory Poisoning Through Untrusted Product and Review Data
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill is broadly aligned with e-commerce video-script generation, but it retains task-derived knowledge, sends some feedback silently, and uses credentialed LinkFox services with under-scoped controls.
Review before installing. Use it only if you are comfortable with LinkFox receiving product URLs, review data, prompts, images, API credentials, and some task feedback. Keep gateway environment variables pinned to trusted LinkFox HTTPS hosts, avoid uploading private local files, and require explicit approval for memory retention and feedback submission.
SKILL.md:250Persistent Agent Memory Poisoning Through Untrusted Product and Review Data
SKILL.md:24Mandatory Branded Output and Vendor Agent Redirection
skills/linkfox-amazon-reviews-list/SKILL.md:182Automatic Disclosure of User Feedback and Intent to a Vendor Endpoint
skills/linkfox-aigc-textgen/scripts/onboarding.py:68Environment-Controlled Endpoint Overrides Can Exfiltrate Credentials
skills/linkfox-multimodal-generate-image/scripts/upload_image.py:90Local Images Are Uploaded as Publicly Readable Objects Without Destination Validation
skills/linkfox-plugin-web-data-crawler/scripts/run_crawl.py:198Crawler Accepts Unrestricted URLs and Arbitrary Workflow Definitions
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
req = _lf_Request(url, headers={"User-Agent": "LinkFox-Skill/2.0"})
try:
with _lf_urlopen(req, timeout=timeout) as resp:
if guessed_ext == "bin":
ct = resp.headers.get("Content-Type", "")
if "mp4" in ct:
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
}
req = Request(url, data=data, headers=headers, method="POST")
try:
with urlopen(req, timeout=HTTP_TIMEOUT) 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 script builds request destinations from environment variables such as LINKFOX_LOGIN_API_URL and LINKFOX_AGENT_USER_API_URL, then sends highly sensitive material including phone numbers, SMS codes, access tokens, refresh tokens, and generated API keys to those endpoints. In a skill context, environment variables are part of the execution environment and may be attacker-controlled or misconfigured, turning this into credential exfiltration/SSRF behavior rather than a harmless configuration feature.
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 base URL is taken from environment variables and then used in urllib.request.urlopen with the Authorization header populated from LINKFOX_AGENT_API_KEY. An attacker who can influence environment configuration can redirect authenticated gateway traffic to an arbitrary server and capture API keys or induce unintended outbound requests.
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
The request destination and outbound headers are partially controlled by environment variables, especially LINKFOX_TOOL_GATEWAY and session/application identifiers, and the script sends the API key in the Authorization header to whatever gateway is selected. If an attacker can influence the runtime environment, they can redirect requests to an arbitrary host and exfiltrate credentials and product-detail query data through this network call.
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-controlled base URLs and then sends sensitive data such as phone numbers, SMS codes, access tokens, refresh tokens, and generated API keys to those endpoints. In a skill environment, environment variables may be influenced by a host, wrapper, or attacker with deployment access, creating an SSRF/exfiltration path that can silently redirect credentialed traffic to attacker-controlled infrastructure.
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 client uses an environment-derived base URL and attaches the LinkFox API key in the Authorization header before calling urlopen. If the base URL is overridden, the skill can leak persistent API credentials and send authenticated purchase/order actions to an attacker-controlled or unintended server.
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
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
method="POST",
)
try:
with urlopen(req, timeout=timeout) as response:
result = _parse_api_response(response.read(), path)
cost_token = response.headers.get("X-Cost-Token")
if isinstance(result, dict) and cost_token is not None:
The script builds request destinations from environment variables such as LINKFOX_LOGIN_API_URL and LINKFOX_AGENT_USER_API_URL, then sends sensitive authentication material including phone numbers, SMS codes, access tokens, refresh tokens, and generated API keys to those URLs. If an attacker can influence the environment, they can redirect these requests to an attacker-controlled server and exfiltrate credentials and tokens; in this skill, that risk is amplified because the code handles account login and token provisioning rather than ordinary content generation.
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 then used in urllib.request.urlopen with the Authorization header populated from LINKFOX_AGENT_API_KEY. An attacker who can set the environment can redirect API traffic to a rogue endpoint and capture the API key and associated account/order data; because this script also performs billing and order operations, that exposure can lead to account abuse and financial actions.
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
The script forwards multiple environment-controlled values into outbound HTTP headers and also allows the gateway base URL to be overridden by the LINKFOX_TOOL_GATEWAY environment variable. In an agent/runtime context where environment variables may be influenced by untrusted callers or plugins, this can redirect authenticated requests to an attacker-controlled endpoint and exfiltrate the API key and session metadata.
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 POST target URL is derived from environment-controlled base URLs, and the request may include sensitive data such as phone numbers, SMS codes, access tokens, refresh tokens, generated API keys, and authorization headers. If an attacker can influence environment variables, they can redirect these requests to an attacker-controlled endpoint and exfiltrate credentials or capture authentication flows.
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 request built for urlopen uses an environment-derived base URL and attaches the API key in the Authorization header. A manipulated environment can therefore redirect billing, account, or package-management API calls to an attacker-controlled server, causing direct credential leakage and possible abuse of the victim's account or purchases.
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
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
)
try:
with urlopen(req, timeout=150) as response:
result = json.loads(response.read().decode("utf-8"))
except HTTPError as e:
body = e.read().decode("utf-8") if e.fp else ""
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
)
try:
with urlopen(req, timeout=120) as response:
if response.status not in (200, 201):
print(f"Upload failed with status: {response.status}", file=sys.stderr)
sys.exit(1)
download_media accepts an arbitrary http/https URL and fetches it with urlopen, then stores the response on local disk. This creates an SSRF-style primitive that can be used to reach attacker-chosen endpoints from the agent environment, potentially accessing internal services or downloading untrusted content for later processing.
req = Request(url, headers={"User-Agent": "LinkFox-Skill/2.0"})
try:
with urlopen(req, timeout=timeout) as resp:
# 从 Content-Type 进一步修正扩展名
if guessed_ext == "bin":
ct = resp.headers.get("Content-Type", "")
The script forwards multiple environment-derived values, including an API key and session identifiers, into an outbound HTTP request. Because the destination base URL is itself overrideable via the LINKFOX_TOOL_GATEWAY environment variable, a caller who can influence the environment can redirect requests and exfiltrate credentials and runtime metadata to an attacker-controlled host.
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 sends authentication material and account data to URLs derived from environment-controlled base endpoints. If an attacker can influence environment variables such as LINKFOX_LOGIN_API_URL or LINKFOX_AGENT_USER_API_URL, SMS codes, access tokens, refresh tokens, and generated API keys could be exfiltrated to attacker-controlled infrastructure. The danger is amplified because this skill already performs login and token issuance outside its declared video-script purpose.
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 request path uses a base URL sourced from environment variables and then transmits Authorization credentials via urlopen. An attacker who can set LINKFOX_AGENT_API_URL or fallback variables can redirect authenticated requests, capture API keys, enumerate account/package data, or manipulate order-related responses. Because this file includes account and payment actions, redirectable network destinations materially increase risk.
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
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
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 POST destination is derived from environment-controlled base URLs, and the request can carry sensitive materials including SMS-login credentials, access tokens, refresh tokens, and generated API keys in headers or body. In an agent/skill environment, a malicious wrapper or user-controlled environment can redirect these requests to an attacker-controlled host, causing credential exfiltration and account takeover.
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 request target is also built from environment variables and is used together with the Authorization API key header for account, package, and order operations. If an attacker can influence the environment, they can redirect authenticated traffic to an arbitrary server and capture the API key or manipulate billing-related flows.
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
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
)
try:
with urlopen(req, timeout=150) as response:
result = json.loads(response.read().decode("utf-8"))
except HTTPError as e:
body = e.read().decode("utf-8") if e.fp else ""
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
)
try:
with urlopen(req, timeout=150) as response:
result = json.loads(response.read().decode("utf-8"))
except HTTPError as e:
body = e.read().decode("utf-8") if e.fp else ""
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
)
try:
with urlopen(req, timeout=120) as response:
if response.status not in (200, 201):
print(f"Upload failed with status: {response.status}", file=sys.stderr)
sys.exit(1)