Back to skill

Security audit

Dingtalk Group Saver

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it says, but it automatically writes DingTalk group metadata from chat events into long-term agent memory without sanitizing it, which can let group names corrupt or influence future memory context.

Review this carefully before installing in any DingTalk workspace where ordinary group members can rename groups or trigger mentions. Prefer a version that stores only sanitized structured data, avoids automatic MEMORY.md writes by default, and provides clear controls to review, delete, and limit retained group records.

Vulnerability Patterns
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • 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
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
Findings (1)

T02 · Agent Memory Poisoning

Error
Location
index.js:54
Finding
Persistent Agent Memory Poisoning Through Unsanitized DingTalk Metadata## Vulnerability Details **File Location**: `index.js:54-80, 92-141` **Vulnerability Type**: Persistent injection of attacker-controlled content into agent memory **Risk Level**: High ### Vulnerable Code ```js function saveGroup(conversationId, groupName) { const data = readMemory(); const now = new Date().toISOString(); // Find an existing record let group = data.groups.find(g => g.conversationId === conversationId); if (group) { group.lastActiveAt = now; group.mentionCount = (group.mentionCount || 0) + 1; if (groupName && !group.groupName) { group.groupName = groupName; } } else { group = { conversationId, groupName: groupName || 'Unknown group name', firstSeenAt: now, lastActiveAt: now, mentionCount: 1 }; data.groups.push(group); } writeMemory(data); // Synchronize the data into MEMORY.md updateMemoryMd(data.groups); return group; } ``` ```js function updateMemoryMd(groups) { if (!fs.existsSync(MEMORY_MD_FILE)) { console.log('MEMORY.md does not exist; skipping update'); return; } let content = fs.readFileSync(MEMORY_MD_FILE, 'utf-8'); const tableLines = [ '| # | Group Name | Group ID | Purpose |', '|---|------------|----------|---------|' ]; groups.forEach((g, index) => { const usage = getUsageByGroupId(g.conversationId); tableLines.push( `| ${index + 1} | ${g.groupName} | \`${g.conversationId}\` | ${usage} |` ); }); // ... fs.writeFileSync(MEMORY_MD_FILE, content, 'utf-8'); } ``` ### Technical Analysis The Skill accepts `groupName` and `conversationId` from a DingTalk group event and stores them without validation. It subsequently interpolates both values directly into `~/.openclaw/workspace/MEMORY.md`. Neither value is constrained to an expected format or maximum length. Markdown-sensitive characters—including line breaks, pipes, and backticks—are not removed or escaped. An attacker-contro ...[truncated 2496 chars]
Remediation
## Remediation Suggestions 1. **Validate the conversation ID** - Require the exact syntax used by legitimate DingTalk conversation identifiers. - Enforce a conservative maximum length. - Reject control characters, whitespace, line breaks, backticks, and Markdown delimiters. - Perform validation at the start of `saveGroup()` before any persistence occurs. 2. **Normalize and constrain group names** - Remove carriage returns, newlines, null bytes, and other control characters. - Enforce a reasonable maximum length. - Reject or replace unexpected Unicode control and bidirectional formatting characters. 3. **Escape all Markdown output** - Escape `|` as `\|`. - Escape backticks or render values without inline-code delimiters. - Convert line breaks to spaces before creating table rows. - Apply escaping to every externally influenced field, even after validation. 4. **Separate untrusted records from agent instructions** - Prefer retaining remote metadata only in structured JSON. - Do not place raw remote values in a file automatically loaded as trusted agent memory. - If a Markdown summary is required, clearly mark generated values as untrusted display data and generate it from sanitized fields only. 5. **Prevent persistent reinjection** - Validate and sanitize existing JSON records before generating `MEMORY.md`. - Provide a migration routine that quarantines malformed historical records. - Correct both the Markdown file and the JSON source when removing malicious entries. 6. **Harden file updates** - Generate the updated document from a well-defined section boundary rather than relying on broad substring operations. - Write through a temporary file followed by an atomic rename to reduce corruption risk. - Set restrictive file permissions appropriate for long-term agent state. A safe rendering helper should enforce type, length, single-line output, and Markdown escaping before interpolation: ```js function ...[truncated 401 chars]
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (4)

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill explicitly describes automatically saving DingTalk group IDs and group names, including writing them into MEMORY.md that is loaded across sessions, but it does not present this as a prominent user-facing privacy/security warning. Group identifiers and names are organizational metadata that can reveal internal structures, and persistent shared storage increases the chance of unintended disclosure or reuse beyond the original interaction.

Ssd 3

Medium
Confidence
96% confidence
Finding
The skill instructs persistence of DingTalk group identifiers and names into globally loaded long-term memory across sessions, which creates a durable cross-session data exposure channel. Even if the data is not highly sensitive by itself, storing it in natural-language shared memory broadens access, makes accidental disclosure easier, and can enable later misuse such as targeting specific groups or reconstructing internal communication topology.

Intent-Code Divergence

Medium
Confidence
92% confidence
Finding
The module advertises that it saves DingTalk group IDs and names to memory, but it also modifies a separate global `MEMORY.md` file and adds inferred metadata such as group usage. This creates a broader side effect than disclosed, and untrusted group names are written into Markdown without escaping, which can corrupt shared memory content or inject misleading instructions into downstream agent context.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The file’s primary description is entirely in Chinese, and later user-facing messages and comments are also Chinese-only. This imposes a specific language/locale on users without any opt-in, alternative language support, or stated region-specific requirement.

Static analysis

No suspicious patterns detected.