T02 · Agent Memory Poisoning
- Location
scripts/sync_feishu_contacts.py:62- Finding
Untrusted Feishu Directory Data Is Persisted in Agent Prompt Context
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill is disclosed as a Feishu chat helper, but it asks admins to persist the whole Feishu contact directory into agent prompt context, which needs careful review before installation.
Install only with administrator approval for Feishu contact access. Treat names and open_ids as personal data, keep USER.md private, avoid the weekly cron until scoping and retention are decided, and prefer a restricted lookup or allowlisted sync with escaping/validation before storing any directory data in prompt context.
scripts/sync_feishu_contacts.py:62Untrusted Feishu Directory Data Is Persisted in Agent Prompt Context
scripts/sync_feishu_contacts.py:52Organization-Wide Contact Collection Exceeds the Data Needed for Sender Identification
The published purpose is a behavioral/group-chat skill, but the content also instructs operators to fetch an org contact directory from a remote API, use app credentials, and write synchronized identity data into USER.md. This mismatch is dangerous because reviewers or users may enable the skill expecting only conversational behavior changes, while it actually introduces data collection, credential use, and persistent file modification affecting privacy and trust boundaries.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
print(f"Available accounts: {list(cfg.get('channels', {}).get('feishu', {}).get('accounts', {}).keys())}")
sys.exit(1)
# 2. Get tenant access token
req = urllib.request.Request(
"https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
data=json.dumps({
The skill describes and depends on capabilities equivalent to network access and file reads/writes, but it does not declare an explicit tool scope or permissions boundary. That makes the skill harder to review and govern, and in a real agent environment can lead to over-privileged execution or unintended access if the runtime grants broad default capabilities.
The document instructs users to populate USER.md with a Chinese-language heading and explanatory text, and later provides a Chinese-only startup snippet. This imposes a specific language on the skill configuration without offering a language choice or documenting that the skill is intentionally limited to Chinese-speaking deployments.
The AGENTS.md startup sequence is specified entirely in Chinese, which can require a specific locale/language for operation or maintenance. The file does not offer an alternative language version or indicate that this language constraint is optional or justified.
The script exports the full Feishu contact directory, including names and open_id values, into USER.md, which may be broadly accessible in a workspace or repository. This creates an unnecessary bulk copy of directory data and increases the risk of privacy leakage, internal user enumeration, and secondary misuse beyond the narrow need of identifying DM senders.
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
}).encode(),
headers={"Content-Type": "application/json"}
)
resp = json.loads(urllib.request.urlopen(req).read())
if resp.get("code", 0) != 0:
print(f"Error getting token: {resp.get('msg', 'unknown error')}")
sys.exit(1)
Bulk synchronization of all users is not clearly necessary for a skill whose stated purpose is group-chat behavior and sender recognition. In this context, collecting and persisting the entire directory broadens exposure of personal data and makes the skill more dangerous because it turns a chat-integration helper into a directory replication mechanism.
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
})
data = json.loads(urllib.request.urlopen(req).read()).get("data", {})
for u in data.get("items", []):
name = u.get("name", "")
open_id = u.get("open_id", "")