T06 · System Persistence
- Location
SKILL.md:37- Finding
Optional Cron Configuration Creates Persistent Scheduled Execution
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This skill has a coherent Feishu chat purpose, but it bulk-syncs organization contacts into prompt-loaded memory without sanitizing names and optionally runs that sync persistently.
Review before installing. Use a least-privileged Feishu app, avoid the cron setup unless automatic sync is necessary, keep USER.md out of version control, and inspect or sanitize generated contact names before restarting the gateway. Safer designs would sync only needed users and store contact mappings as structured data rather than prompt text.
SKILL.md:37Optional Cron Configuration Creates Persistent Scheduled Execution
scripts/sync_feishu_contacts.py:70Unescaped Feishu Contact Names Are Persisted into a System-Prompt-Backed File
The skill is presented as guidance for group-chat behavior, but it also instructs users to fetch an organization contact directory, authenticate with app credentials, and persist identities into USER.md. That mismatch is security-relevant because reviewers may approve it as harmless prompt guidance while overlooking directory harvesting, credential use, and durable storage of user mappings in prompt-injected files.
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 instructs operators to run a bundled sync script that reads credentials, calls Feishu APIs, and writes contact data into USER.md, yet the skill declares no explicit tool scope or permissions boundary. This creates an unreviewed capability expansion: a seemingly behavioral skill also enables network access and persistent file modification, increasing the chance of over-privileged deployment or accidental data exposure.
The example USER.md content is explicitly written in Chinese, and later startup instructions are also mandated in Chinese. The file does not offer an opt-in language choice or explain that the skill is limited to Chinese-speaking deployments, which creates a locale-policy issue.
The guidance says to add the provided sender-identification snippet to the startup sequence, and the snippet is entirely in Chinese. Because the file gives no language-choice option or justification for enforcing Chinese, it violates the natural-language locale policy.
The script's purpose is to pull the full Feishu contact directory and persist it into a local USER.md file, which expands the skill from chat behavior assistance into bulk identity-data replication. This creates unnecessary local exposure of names and open_id values, increasing privacy and data-minimization risk if the markdown file is shared, committed, or read by other tools.
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)
The code enumerates all users from the root department and writes them into a plaintext markdown file, a bulk export behavior not clearly required for a group-chat participation helper. In this skill context, that makes the behavior more concerning because it collects organization-wide identity data beyond what is necessary to format or interpret messages.
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", "")
No suspicious patterns detected.