Back to skill

Security audit

dingtalk-doc

Security checks for vulnerabilities and agentic risk

Overview

This DingTalk document skill is mostly coherent, but it exposes high-impact write/delete powers with weak boundaries that should be reviewed before installation.

Install only if you trust the DingTalk app credentials and first replace the bundled whitelist with narrow, user-approved targets. Avoid using the low-level dingtalk-client.js commands, remove or disable get-token, and require explicit confirmation for overwrites and deletions.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (4)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/index.js:509
Finding
Whitelist authorization is not bound to the actual document mutation target<![CDATA[ ## Vulnerability Details **File Location**: `scripts/index.js:509-528`; equivalent vulnerable flows also occur at `scripts/index.js:547-563`, `scripts/index.js:575-591`, and `scripts/index.js:603-619` **Vulnerability Type**: Authorization target mismatch **Risk Level**: High ### Vulnerable Code ```javascript async function updateDocContent(nodeId, markdown, config = null, senderId = null, workspaceId = null, docKey = null) { if (!config) { config = loadConfig(); } const token = await getAccessToken(); const operatorId = await getCurrentOperatorId(token, senderId); // Authorization is performed against nodeId. const { docPath, permission } = await checkFullWritePermission( nodeId, operatorId, config, workspaceId ); // The mutation can independently target a caller-supplied docKey. const actualDocKey = docKey || nodeId; const result = await overwriteContent(actualDocKey, markdown, operatorId); return { success: true, data: { ...result, path: docPath, permissionRule: permission.matchedRule } }; } ``` The same identity mismatch is present in the block operations: ```javascript await checkFullWritePermission(nodeId, operatorId, config, workspaceId); const actualDocKey = docKey || nodeId; const result = await deleteBlock(actualDocKey, blockId, operatorId); ``` ```javascript await checkFullWritePermission(nodeId, operatorId, config, workspaceId); const actualDocKey = docKey || nodeId; const result = await modifyBlock(actualDocKey, blockId, element, operatorId); ``` ```javascript await checkFullWritePermission(nodeId, operatorId, config, workspaceId); const actualDocKey = docKey || nodeId; const result = await insertBlock(actualDocKey, element, operatorId, position); ``` ### Technical Analysis The whitelist decision is made using `nodeId`. That identifier is resolved through the Wiki API to obtain its workspace and node name. However, after authorization succeeds, ...[truncated 1855 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use one canonical document identity for both authorization and mutation. 2. Resolve the actual `docKey` from the authorized `nodeId` through a trusted DingTalk API response rather than accepting it independently from the caller. 3. If the DingTalk API requires callers to provide both identifiers, retrieve authoritative metadata and reject the operation unless the supplied `docKey` is proven to belong to the supplied `nodeId`. 4. Replace logic such as: ```javascript const actualDocKey = docKey || nodeId; ``` with a verified resolution flow: ```javascript const authorizedDocument = await resolveDocumentIdentity(nodeId, operatorId); if (docKey && docKey !== authorizedDocument.docKey) { throw new Error('The supplied docKey does not belong to the authorized nodeId'); } await overwriteContent(authorizedDocument.docKey, markdown, operatorId); ``` 5. Apply the same binding requirement to overwrite, insert, modify, and delete operations. 6. Add negative tests that pair an allowed `nodeId` with an unrelated `docKey` and verify that every write operation is rejected. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/dingtalk-client.js:610
Finding
Direct low-level client commands bypass mandatory whitelist enforcement<![CDATA[ ## Vulnerability Details **File Location**: `scripts/dingtalk-client.js:610-623` and `scripts/dingtalk-client.js:644-656` **Vulnerability Type**: Alternate unguarded write interface **Risk Level**: High ### Vulnerable Code The low-level client exposes document creation directly: ```javascript case 'create-doc': const workspaceId2 = args.find(a => a.startsWith('--workspaceId='))?.split('=')[1]; const name = args.find(a => a.startsWith('--name='))?.split('=')[1]; const docType = args.find(a => a.startsWith('--docType='))?.split('=')[1]; const operatorId3 = args.find(a => a.startsWith('--operatorId='))?.split('=')[1]; const parentNodeId = args.find(a => a.startsWith('--parentNodeId='))?.split('=')[1]; if (!workspaceId2 || !name || !docType || !operatorId3) { throw new Error('缺少必填参数:--workspaceId=, --name=, --docType=, --operatorId='); } const createParams = { name: decodeCliValue(name), docType: docType, operatorId: operatorId3 }; if (parentNodeId) { createParams.parentNodeId = parentNodeId; } result = await createDoc(workspaceId2, createParams); break; ``` It also exposes document deletion directly: ```javascript case 'delete-doc': const workspaceId4 = args.find(a => a.startsWith('--workspaceId='))?.split('=')[1]; const nodeId2 = args.find(a => a.startsWith('--nodeId='))?.split('=')[1]; const operatorId6 = args.find(a => a.startsWith('--operatorId='))?.split('=')[1]; if (!workspaceId4 || !nodeId2 || !operatorId6) { throw new Error('缺少参数:--workspaceId=, --nodeId=, --operatorId='); } result = await deleteDoc(workspaceId4, nodeId2, operatorId6); break; ``` ### Technical Analysis The project states that all writes must pass the local whitelist and that there is no way to bypass the check. The primary `scripts/index.js` interface attempts to enforce that policy, but `scripts/dingtalk-client.js` is independently executable and provides raw creation and deletion commands. These ...[truncated 1667 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove all write commands from the executable low-level client. 2. Convert `dingtalk-client.js` into a non-executable transport module and expose write operations only through the policy-enforcing entry point. 3. As defense in depth, require every exported destructive function to receive a verified authorization context that cannot be constructed from raw caller input. 4. Centralize authorization immediately next to the mutation primitive so future entry points cannot omit it. 5. Do not accept a raw `operatorId` as sufficient authorization for local write policy. 6. Add tests that invoke every executable file directly and verify that writes outside the whitelist are rejected. 7. Update the documentation only after the code ensures that no alternate bundled write path bypasses the whitelist. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/dingtalk-client.js:570
Finding
Live DingTalk access token is exposed through standard output<![CDATA[ ## Vulnerability Details **File Location**: `scripts/dingtalk-client.js:570-573`; exposed as a package command at `package.json:7` **Vulnerability Type**: Plaintext bearer-token disclosure **Risk Level**: Medium ### Vulnerable Code ```javascript case 'get-token': const token = await getAccessToken(); result = { accessToken: token }; break; ``` The returned object is subsequently printed: ```javascript console.log(JSON.stringify(result, null, 2)); ``` The package explicitly exposes this operation: ```json "scripts": { "get-token": "node scripts/dingtalk-client.js get-token" } ``` ### Technical Analysis The `get-token` command writes a complete live DingTalk access token to standard output. Access tokens are bearer credentials: possession is generally sufficient to authenticate API requests within the token's lifetime and permission scope. Standard output is not an appropriate secret transport. It can be captured by Agent conversations, CI/CD logs, terminal recording, process supervisors, support bundles, centralized log systems, or copied command output. The token cache does not mitigate this disclosure because the complete reusable credential is deliberately emitted. ### Attack Path 1. A user, Agent, diagnostic script, or CI job runs: ```bash npm run get-token ``` 2. The complete access token is printed as JSON. 3. A log collector, transcript store, terminal recorder, or other user with output access captures the token. 4. The capturing party sends authenticated requests to DingTalk before the token expires. 5. Those requests execute with the permissions granted to the DingTalk application. ### Impact Assessment A disclosed token can provide temporary access to DingTalk APIs under the application's granted scopes. Based on the permissions described by the project, this can include: - Reading workspace and document metadata. - Reading document contents. - Creating or deleting documents. - Overwriting content or modifyin ...[truncated 118 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the `get-token` CLI command and the corresponding package script. 2. Keep tokens internal to the HTTP client and never return them through user-facing command output. 3. If diagnostics are necessary, print only a non-reversible masked fingerprint and expiration information, for example: ```javascript result = { authenticated: true, tokenFingerprint: maskValue(token), expiresAt: tokenExpiry }; ``` 4. Ensure errors never include request headers, token response bodies, client secrets, or complete tokens. 5. Document token rotation and revocation procedures for users who may already have exposed tokens through logs. 6. Review existing CI logs and Agent transcripts for prior token output and revoke affected credentials where appropriate. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
config/whitelist.json:4
Finding
Distributed whitelist configuration enables workspace-wide destructive access by default<![CDATA[ ## Vulnerability Details **File Location**: `config/whitelist.json:4-9` **Vulnerability Type**: Overly permissive default authorization configuration **Risk Level**: Medium ### Vulnerable Configuration ```json { "workspaceId": "eLvJDSRX3l4moO87", "workspaceName": "AI可写知识库一级目录", "allowRootWrite": true, "whitelist": ["/"] } ``` The `/` rule is interpreted as workspace-wide authorization: ```javascript function hasWorkspaceWideWhitelist(whitelist) { return Array.isArray(whitelist) && whitelist.some(rule => normalizePath(rule) === '/'); } ``` ```javascript if (hasWorkspaceWideWhitelist(wsConfig.whitelist)) { return { docPath, permission: { allowed: true, matchedRule: '/' }, wsConfig }; } ``` ### Technical Analysis The package ships with a concrete workspace identifier and a whitelist containing `/`. The code explicitly treats `/` as matching every node in the configured workspace. This conflicts with least-privilege and deny-by-default design. A user who installs or deploys the Skill without replacing the sample configuration may unknowingly enable creation, overwrite, block modification, and deletion across the complete configured workspace. Although the configuration comment says that only the user should edit the file, the distributed configuration is active and machine-readable rather than an inert example. ### Attack Path 1. Install or deploy the Skill without replacing `config/whitelist.json`. 2. The configured workspace is accessible to the DingTalk application. 3. A caller requests a write or deletion against any node in that workspace. 4. `hasWorkspaceWideWhitelist` recognizes `/` and immediately approves the operation. 5. The destructive DingTalk API call executes if upstream permissions allow it. ### Impact Assessment If the packaged workspace ID is valid in the deployment, the Skill receives local authorization to mutate every document in that workspace. Permitted actions include: - C ...[truncated 321 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Ship a deny-by-default configuration: ```json { "workspaces": [] } ``` 2. Move example configurations to documentation or a separate `whitelist.example.json` file that is never loaded automatically. 3. Require explicit user initialization before enabling any write operation. 4. Default `allowRootWrite` to `false`. 5. Reject `/` unless the user enables a separate, clearly named high-risk setting such as `allowWorkspaceWideWrite`. 6. Display a prominent warning and require confirmation when workspace-wide access is configured. 7. Prefer narrowly scoped document-name rules and periodically audit configured permissions. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (28)

Credential Access

High
Category
Privilege Escalation
Content
OpenClaw 会在启动时自动加载此文件中的环境变量。

```bash
# ~/.openclaw/.env
DINGTALK_CLIENTID=dingxxxxxx
DINGTALK_CLIENTSECRET=your_secret
```
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
- `POST /v1.0/doc/workspaces/{workspaceId}/docs`
- `GET /v2.0/wiki/nodes`
- `GET /v2.0/wiki/nodes/{nodeId}`
- `DELETE /v1.0/doc/workspaces/{workspaceId}/docs/{nodeId}`
- `GET /v1.0/doc/suites/documents/{docKey}/blocks`
- `POST /v1.0/doc/suites/documents/{docKey}/overwriteContent`
- `POST /v1.0/doc/suites/documents/{docKey}/blocks`
Confidence
80% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
- `POST /v1.0/doc/suites/documents/{docKey}/overwriteContent`
- `POST /v1.0/doc/suites/documents/{docKey}/blocks`
- `PUT /v1.0/doc/suites/documents/{docKey}/blocks/{blockId}`
- `DELETE /v1.0/doc/suites/documents/{docKey}/blocks/{blockId}`

常见所需权限:
Confidence
80% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The top-level description presents the skill as a document read/update helper, but the body documents materially broader capabilities such as token acquisition, user identity resolution, create/delete operations, CLI execution, and potentially wider workspace management. This mismatch can cause reviewers or orchestrators to authorize the skill under a narrower trust model than its actual power warrants.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The top-level description presents the skill as a document read/update helper, but the body documents materially broader capabilities such as token acquisition, user identity resolution, create/delete operations, CLI execution, and potentially wider workspace management. This mismatch can cause reviewers or orchestrators to authorize the skill under a narrower trust model than its actual power warrants.

Ae1

High
Category
analysis-evasion
Content
通过钉钉开放平台 API 管理钉钉文档与钉钉知识库内文档。`SKILL.md` 只保留 agent 执行所需规则;配置细节、示例、API 背景见 `README.md`。
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `scripts/index.js`:主入口
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `scripts/index.js`:主入口
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `scripts/index.js`:主入口
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `scripts/index.js`:主入口
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Tool Parameter Abuse

High
Category
Tool Misuse
Content
/**
 * 删除知识库文档 (doc_1.0)
 * API: DELETE /v1.0/doc/workspaces/{workspaceId}/docs/{nodeId}
 */
async function deleteDoc(workspaceId, nodeId, operatorId) {
  const path = `/v1.0/doc/workspaces/${workspaceId}/docs/${nodeId}?operatorId=${encodeURIComponent(operatorId)}`;
Confidence
96% confidence
Finding
The deleteDoc primitive accepts workspaceId/nodeId/operatorId and directly constructs a destructive API request with no local authorization, confirmation, or constraint on what resources may be targeted. This is a classic high-risk tool surface for parameter abuse in agent systems, where attacker-influenced parameters can cause deletion of arbitrary accessible documents.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
/**
 * 删除块元素
 * API: DELETE /v1.0/doc/suites/documents/{docKey}/blocks/{blockId}
 */
async function deleteBlock(docKey, blockId, operatorId) {
  const path = `/v1.0/doc/suites/documents/${docKey}/blocks/${blockId}?operatorId=${encodeURIComponent(operatorId)}`;
Confidence
95% confidence
Finding
The deleteBlock primitive similarly accepts attacker-influenceable identifiers and issues a destructive API request without confirming the block belongs to the intended document context or that the user explicitly approved its removal. In an agent environment, this increases the chance of unauthorized or accidental content removal through parameter manipulation.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
/**
 * 删除块元素(写入操作,需要白名单)
 * API: DELETE /v1.0/doc/suites/documents/{docKey}/blocks/{blockId}
 * 
 * @param {string} nodeId - 节点 ID(用于白名单检查)
 * @param {string} blockId - 块 ID
Confidence
92% confidence
Finding
The write authorization is bound to nodeId, but the actual destructive API call uses a caller-supplied docKey without verifying that the docKey belongs to the same document that was authorized. An attacker or confused caller could pass an allowed nodeId to satisfy whitelist checks and a different docKey for overwrite/delete/modify operations, potentially causing unauthorized writes or deletions against another accessible document.

Lp3

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding
The skill relies on sensitive environment variables (`DINGTALK_CLIENTID`, `DINGTALK_CLIENTSECRET`, sender identity fields) but does not declare an explicit tool scope or permissions boundary in the skill manifest. That weakens reviewability and increases the chance the agent accesses secrets or identity context more broadly than operators expect.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The skill documents destructive actions such as overwrite, block deletion, and document deletion without requiring explicit user confirmation or warning about irreversible data loss. In an agent setting, ambiguous or mistaken instructions could therefore lead to unintended destructive changes to production knowledge-base content.

Intent-Code Divergence

Medium
Confidence
93% confidence
Finding
The file presents a contradictory security posture: a comment says AI must not modify the configuration, yet the actual policy grants broad write access via allowRootWrite=true and whitelist ['/'], effectively permitting writes anywhere in the workspace root. In the context of a document-management skill that can update DingTalk docs/knowledge-base content, this makes unauthorized or overbroad AI-driven modification materially more dangerous because a prompt mistake or adversarial instruction could alter arbitrary content in the allowed workspace.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
The skill resolves sender IDs to unionId/operatorId by querying user profile/address-book APIs, adding identity lookup functionality unrelated to simple document handling. This increases access to organizational identity data and creates privacy and privilege risks if sender IDs are spoofed, mishandled, or logged, especially since operator identity is then reused to authorize downstream document actions.

External Transmission

Medium
Category
Data Exfiltration
Content
/**
 * 获取 access_token(企业内部应用)
 * POST https://api.dingtalk.com/v1.0/oauth2/accessToken
 *
 * @returns {Promise<string>} 用于请求头 x-acs-dingtalk-access-token 的 accessToken
 */
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The client exposes destructive and administrative capabilities such as deleting documents, deleting blocks, copying nodes, and creating workspaces/docs, which go beyond the skill’s stated document read/summarize/update purpose. Scope expansion increases the blast radius if the skill is invoked unexpectedly or manipulated by prompt/tool misuse, because the process can perform irreversible changes rather than only read or summarize content.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The overwriteContent operation replaces entire document contents without any user-facing warning, preview, or confirmation. Because it is a full overwrite rather than a bounded edit, misuse can cause large-scale data loss or tampering from a single call.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The block deletion primitive removes content with no confirmation or guardrails. In a document-management agent, low-friction destructive primitives are dangerous because they can be triggered by malformed instructions, parameter confusion, or prompt injection and may silently remove important content.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The CLI exposes document deletion directly with no confirmation, dry-run, or secondary approval step. In an agent skill context, this makes accidental, coerced, or prompt-injected destructive execution much more likely, and the action is potentially irreversible.

Missing User Warnings

Medium
Confidence
83% confidence
Finding
The script performs document deletion via `deleteDoc(...)` after permission checks, but there is no confirmation prompt, user-facing warning, or log immediately before the irreversible action. Although the file comments describe delete capability, they do not clearly disclose at runtime that the command will permanently remove content.

Missing User Warnings

Medium
Confidence
80% confidence
Finding
The function deletes a document block through `deleteBlock(...)`, which is an irreversible content modification. The code includes permission checks but no confirmation prompt, no warning print/log, and no nearby user-facing disclosure that the targeted block content will be removed.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
This code file contains natural-language comments and CLI output in Chinese, and the help/error interface shown to users is also fixed to Chinese later in the file. Under the policy, forcing a specific language without user opt-in is a locale/language policy violation unless the tool is clearly documented as region-specific, which is not evident here.

Static analysis

Detected: suspicious.exposed_secret_literal

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
scripts/dingtalk-client.js:235