Back to skill

Security audit

Feishu Contacts Sync

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it says, but it stores Feishu contact data in a prompt-loaded file and can optionally keep rewriting it on a schedule, which needs careful review.

Install only if you trust the Feishu tenant data source and are comfortable placing a name-to-open_id directory into USER.md. Review the generated table before restarting the gateway, avoid the cron option unless you need it, and prefer adding validation or escaping for contact names and open_ids before using this in a shared or high-privilege agent environment.

Vulnerability Patterns
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • System PersistenceInstalls backdoors, hooks, services, or scheduled tasks that survive the run
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T06 · System Persistence

Warning
Location
SKILL.md:67
Finding
Optional Crontab Creates Persistent Unattended Execution<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:67-73` **Vulnerability Type**: `T06: System Persistence` **Risk Level**: Medium ### Vulnerable Code ```markdown ### 5. Set up periodic sync (optional) Add a system crontab to keep contacts fresh (e.g., weekly Monday 7am): ```bash 0 7 * * 1 python3 /path/to/scripts/sync_feishu_contacts.py ~/.openclaw/openclaw.json my_app ~/workspace/USER.md ``` ``` ### Technical Analysis The documentation recommends registering the synchronization script in the system crontab. Although this action is optional, transparent, and not performed automatically by the Skill, it establishes recurring execution that survives the initial Skill run. Every scheduled invocation reads Feishu application credentials from the OpenClaw configuration, authenticates to Feishu, retrieves the accessible contact directory, and rewrites `USER.md`. Persistent scheduling is not required for the core on-demand contact synchronization function and therefore exceeds the minimum execution lifetime necessary to perform a single synchronization. The scheduled command also references a script by filesystem path without an integrity check. If that script is subsequently replaced or modified, cron will execute the changed content automatically under the account that owns the crontab. ### Attack Path 1. The user follows the optional setup instructions and installs the provided cron entry. 2. The cron job runs weekly under the user's account without interactive confirmation. 3. An attacker who later obtains write access to the referenced script path replaces or modifies the synchronization script. 4. At the next scheduled time, cron executes the modified script automatically. 5. The malicious replacement inherits the cron user's filesystem and network access, including potential access to the OpenClaw configuration and `USER.md`. This path requires the attacker to obtain write access to the scheduled script or its containing directory. The c ...[truncated 899 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Prefer explicit, user-initiated synchronization because the core functionality does not require persistent execution. - If periodic synchronization is necessary, use an application-scoped scheduler instead of a general system crontab. - Run the task under a dedicated least-privileged account that can read only the required configuration and modify only the intended contact data file. - Place the script in a directory that unprivileged or unrelated processes cannot modify. - Verify the script's integrity before each scheduled execution, such as by checking a pinned cryptographic hash or using a signed deployment artifact. - Add bounded execution time, failure logging, and notifications for unexpected changes. - Document how to list, disable, and remove the scheduled task. - Avoid scheduling the task as `root` or another privileged service account. ]]>

T02 · Agent Memory Poisoning

Error
Location
scripts/sync_feishu_contacts.py:62
Finding
Unescaped Feishu Contact Data Can Poison Persistent Agent Context<![CDATA[ ## Vulnerability Details **File Location**: `scripts/sync_feishu_contacts.py:62-67, 74-76, 99-100` **Vulnerability Type**: `T02: Agent Memory Poisoning` **Risk Level**: High ### Vulnerable Code ```python for u in data.get("items", []): name = u.get("name", "") open_id = u.get("open_id", "") if name and open_id: users.append({"name": name, "open_id": open_id}) if not data.get("has_more"): break ``` ```python TABLE_HEADER = "| 姓名 | open_id |\n|------|---------|" table_rows = "\n".join(f"| {u['name']} | {u['open_id']} |" for u in users) ``` ```python else: with open(user_md_path, "w") as f: f.write(new_content) ``` The associated Skill documentation explains that this file becomes Agent context: ```markdown Embed the full contacts table directly in USER.md. Since workspace files are injected into the system prompt at gateway startup, the agent can match `open_id` from inbound metadata against the table — zero tool calls needed. ``` ### Technical Analysis The script treats Feishu API values as trusted Markdown. It extracts `name` and `open_id`, interpolates both directly into table rows, and writes the result into `USER.md` without validating or escaping: - Newline and carriage-return characters. - Markdown table delimiters such as `|`. - Markdown headings or other structural syntax. - Control characters. - Text crafted to resemble Agent instructions. A maliciously constructed contact name containing newline characters can terminate its intended table row and append arbitrary Markdown or instruction-like content. This is security-sensitive because the declared design injects `USER.md` into the Agent's system context when the gateway starts. Consequently, external directory data crosses from an untrusted API response into a persistent prompt-bearing file. This is not remote code execution at the operating-system level. It is persistent ...[truncated 1922 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not place externally controlled directory data directly into files that are loaded as system-level Agent context. - Store the contact mapping in a structured data file, such as JSON, and expose it through a narrowly scoped lookup tool whose results are explicitly treated as untrusted data. - If Markdown storage must be retained, reject `\r`, `\n`, NUL bytes, and other control characters from all API-derived fields. - Escape Markdown table delimiters, especially `|` and backslashes, before constructing rows. - Apply a strict allowlist to `open_id`, matching only the expected Feishu identifier format and length. - Normalize contact names and enforce a reasonable maximum length. - Clearly delimit generated data as non-instructional content and ensure the Agent runtime does not grant it the same authority as system or developer instructions. - Write updates atomically through a securely created temporary file, validate the complete generated document, and replace `USER.md` only after validation succeeds. - Add tests using names containing newlines, pipes, headings, code fences, control characters, and instruction-like text. - Require review or confirmation when synchronized data would introduce unexpected multiline content or structural changes. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (6)

Credential Access

High
Category
Privilege Escalation
Content
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({
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill instructs the user to run a script that reads credentials from a config file, makes network calls to Feishu, and writes data into USER.md, yet it declares no explicit tool scope or permission boundaries. This is dangerous because it hides the operational and trust surface of the skill, making it easier for an agent or user to invoke file and network actions without clear review or least-privilege controls.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The skill directs users to embed a full open_id-to-name contact table into USER.md, but it does not clearly warn that this modifies a prompt-injected file and stores sensitive identity mapping data there. This creates privacy and integrity risk because USER.md may be broadly exposed to the agent context, may persist stale or unauthorized personnel data, and may be overwritten or expanded without the user's informed consent.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The required USER.md format and the AGENTS.md startup instruction are provided as Chinese text and presented as content the user should add, with no opt-in or alternative language option. This creates a language-policy issue because the skill implicitly mandates a specific language rather than allowing the user's preferred locale.

Tainted flow: 'req' from urllib.request.urlopen (line 58, network input) → urllib.request.urlopen (network output)

Medium
Category
Data Flow
Content
}).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)
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Tainted flow: 'req' from urllib.request.urlopen (line 58, network input) → urllib.request.urlopen (network output)

Medium
Category
Data Flow
Content
"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", "")
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Static analysis

No suspicious patterns detected.