Back to skill

Security audit

Dingtalk Todo

Security checks for vulnerabilities and agentic risk

Overview

This DingTalk todo skill is purpose-aligned, but it stores sensitive DingTalk credentials and tokens in a plaintext local file and can change or delete todo data without clear safeguards.

Review before installing. Use this only if you are comfortable giving the skill DingTalk app credentials capable of managing todos. Prefer a dedicated low-permission DingTalk app, restrict its scopes, protect or avoid the local config file, rotate secrets if exposed, and manually confirm any update, completion, or deletion request before allowing it to run.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:72
Finding
Plaintext Persistent Storage of DingTalk Credentials and Access Tokens<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 72-90 **Vulnerability Type**: Plaintext sensitive-data storage without file permission or ownership safeguards **Risk Level**: Medium ### Vulnerable Code ```bash #!/bin/bash set -e CONFIG=~/.dingtalk-skills/config APP_KEY=$(grep '^DINGTALK_APP_KEY=' "$CONFIG" | cut -d= -f2-) APP_SECRET=$(grep '^DINGTALK_APP_SECRET=' "$CONFIG" | cut -d= -f2-) USER_ID=$(grep '^DINGTALK_USER_ID=' "$CONFIG" | cut -d= -f2-) # Cached modern token used by the Todo API CACHED_TOKEN=$(grep '^DINGTALK_ACCESS_TOKEN=' "$CONFIG" 2>/dev/null | cut -d= -f2-) TOKEN_EXPIRY=$(grep '^DINGTALK_TOKEN_EXPIRY=' "$CONFIG" 2>/dev/null | cut -d= -f2-) NOW=$(date +%s) if [ -n "$CACHED_TOKEN" ] && [ -n "$TOKEN_EXPIRY" ] && [ "$NOW" -lt "$TOKEN_EXPIRY" ]; then TOKEN=$CACHED_TOKEN else RESP=$(curl -s -X POST https://api.dingtalk.com/v1.0/oauth2/accessToken \ -H 'Content-Type: application/json' \ -d "{\"appKey\":\"$APP_KEY\",\"appSecret\":\"$APP_SECRET\"}") TOKEN=$(echo "$RESP" | grep -o '"accessToken":"[^"]*"' | cut -d'"' -f4) sed -i '/^DINGTALK_ACCESS_TOKEN=/d;/^DINGTALK_TOKEN_EXPIRY=/d' "$CONFIG" echo "DINGTALK_ACCESS_TOKEN=$TOKEN" >> "$CONFIG" echo "DINGTALK_TOKEN_EXPIRY=$((NOW + 7000))" >> "$CONFIG" fi ``` ### Technical Analysis The workflow stores the DingTalk application secret and cached bearer token in `~/.dingtalk-skills/config` as plaintext. It neither creates the directory and file with restrictive permissions nor validates the file's owner, permissions, or type before reading and modifying it. The configuration update also follows symbolic links because ordinary shell redirection and `sed -i` are used without checking the destination. Consequently, the security of the credentials depends on the caller's existing `umask`, directory permissions, and filesystem state. Masking credentials in user-visible output does not protect the values at rest. A process or local account capable of reading the config ...[truncated 1653 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store the AppSecret in an operating-system credential manager or another dedicated secret store rather than a plaintext configuration file. 2. Avoid persisting bearer tokens unless necessary. Prefer short-lived in-memory caching for the duration of the skill invocation. 3. If file storage is unavoidable, initialize it securely: ```bash umask 077 CONFIG_DIR="$HOME/.dingtalk-skills" CONFIG="$CONFIG_DIR/config" mkdir -p -- "$CONFIG_DIR" chmod 700 -- "$CONFIG_DIR" touch -- "$CONFIG" chmod 600 -- "$CONFIG" ``` 4. Before every read or update, reject symbolic links, confirm the file is a regular file, and verify that it is owned by the current user. 5. Use atomic updates through a securely created temporary file in the same protected directory, then rename it over the configuration file. 6. Never print secrets, tokens, complete request bodies containing credentials, or verbose HTTP traces. 7. Rotate the AppSecret and revoke cached tokens if insecure storage may already have exposed them. 8. Document the minimum DingTalk scopes required and instruct users not to grant unrelated application permissions. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:18
Finding
Predictable Shared Temporary Script Path Used for Shell Execution<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, line 18 **Vulnerability Type**: Unsafe predictable temporary-file creation and execution **Risk Level**: Medium ### Vulnerable Instruction The skill directs generated multiline commands through the following predictable shared path pattern: ```bash /tmp/<task>.sh bash /tmp/<task>.sh ``` No secure random creation, restrictive permission initialization, ownership validation, or cleanup procedure is required. ### Technical Analysis The documented workflow instructs the agent to write shell logic to a predictable filename under the shared `/tmp` directory and then execute that file with Bash. Shared temporary directories are normally writable by other local users. A predictable path is therefore exposed to pre-creation, symbolic-link, replacement, and time-of-check/time-of-use attacks. The risk is heightened because the generated scripts are expected to process the DingTalk AppSecret and access tokens. Depending on how an implementing agent materializes the template, those values may be embedded in the generated file, exposed through insecure permissions, or consumed by attacker-modified commands. The instruction does not use `mktemp`, set `umask 077`, verify ownership, open the file safely, or install a cleanup trap. ### Attack Path 1. An attacker predicts the task-derived filename under `/tmp`. 2. Before the skill creates or executes the script, the attacker pre-creates that path, substitutes a symbolic link, or monitors it for creation. 3. The skill writes command content to the attacker-controlled path or creates a script with permissions that permit local access. 4. The attacker modifies the file between creation and `bash` execution, or reads credential-bearing content from it. 5. The skill invokes `bash` on the compromised path. 6. Attacker-controlled commands execute with the operating-system privileges of the user running the agent, or DingTalk credentials processed by the script ar ...[truncated 794 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace predictable filenames with a securely created temporary file: ```bash umask 077 SCRIPT_PATH=$(mktemp "${TMPDIR:-/tmp}/dingtalk-task.XXXXXXXXXX") trap 'rm -f -- "$SCRIPT_PATH"' EXIT HUP INT TERM chmod 600 -- "$SCRIPT_PATH" ``` 2. Quote the generated pathname whenever it is written, checked, or executed: ```bash bash -- "$SCRIPT_PATH" ``` 3. Confirm that the resulting path is a regular file owned by the current user before execution. 4. Do not reopen a predictable pathname after validating it. Where practical, retain an open file descriptor to reduce replacement races. 5. Keep secrets out of generated script text. Obtain them at runtime from a protected credential store or pass them through a controlled file descriptor. 6. Remove the temporary file immediately after execution through a cleanup trap, including on errors and interruption. 7. Prefer implementing the operation in a maintained script within a protected project directory rather than dynamically generating shell programs in a shared temporary directory. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (10)

Missing User Warnings

High
Confidence
97% confidence
Finding
The workflow explicitly instructs the agent to persist AppKey, AppSecret, user identifiers, cached access tokens, and token expiry values in a plaintext file under ~/.dingtalk-skills/config across sessions. Storing long-lived credentials and reusable bearer tokens without encryption, permission hardening, rotation guidance, or user risk notice creates a clear credential exposure risk if the host, account, logs, backups, or other local processes are compromised.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The skill declares very broad trigger phrases such as “待办任务”, “task management”, and similar generic language. This can cause the skill to activate in contexts where the user did not intend DingTalk operations, increasing the chance of unintended reads, writes, or destructive actions against the user's Todo data.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
The skill advertises deletion of Todo items but does not require an explicit confirmation or warn that deletion is irreversible from the UI and must be performed through the API. In this context, accidental triggering or user misunderstanding could result in unintended data loss that may be difficult to recover.

External Transmission

Medium
Category
Data Exfiltration
Content
OLD_TOKEN=$(curl -s "https://oapi.dingtalk.com/gettoken?appkey=${APP_KEY}&appsecret=${APP_SECRET}" | grep -o '"access_token":"[^"]*"' | cut -d'"' -f4)

# 2. userId → unionId
UNION_ID=$(curl -s -X POST "https://oapi.dingtalk.com/topapi/v2/user/get?access_token=${OLD_TOKEN}" \
  -H 'Content-Type: application/json' \
  -d "{\"userid\":\"${USER_ID}\"}" | grep -o '"unionid":"[^"]*"' | cut -d'"' -f4)
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
OLD_TOKEN=$(curl -s "https://oapi.dingtalk.com/gettoken?appkey=${APP_KEY}&appsecret=${APP_SECRET}" | grep -o '"access_token":"[^"]*"' | cut -d'"' -f4)

# 2. userId → unionId
UNION_ID=$(curl -s -X POST "https://oapi.dingtalk.com/topapi/v2/user/get?access_token=${OLD_TOKEN}" \
  -H 'Content-Type: application/json' \
  -d "{\"userid\":\"${USER_ID}\"}" | grep -o '"unionid":"[^"]*"' | cut -d'"' -f4)
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
if [ -n "$CACHED_TOKEN" ] && [ -n "$TOKEN_EXPIRY" ] && [ "$NOW" -lt "$TOKEN_EXPIRY" ]; then
  TOKEN=$CACHED_TOKEN
else
  RESP=$(curl -s -X POST https://api.dingtalk.com/v1.0/oauth2/accessToken \
    -H 'Content-Type: application/json' \
    -d "{\"appKey\":\"$APP_KEY\",\"appSecret\":\"$APP_SECRET\"}")
  TOKEN=$(echo "$RESP" | grep -o '"accessToken":"[^"]*"' | cut -d'"' -f4)
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
if [ -n "$CACHED_TOKEN" ] && [ -n "$TOKEN_EXPIRY" ] && [ "$NOW" -lt "$TOKEN_EXPIRY" ]; then
  TOKEN=$CACHED_TOKEN
else
  RESP=$(curl -s -X POST https://api.dingtalk.com/v1.0/oauth2/accessToken \
    -H 'Content-Type: application/json' \
    -d "{\"appKey\":\"$APP_KEY\",\"appSecret\":\"$APP_SECRET\"}")
  TOKEN=$(echo "$RESP" | grep -o '"accessToken":"[^"]*"' | cut -d'"' -f4)
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
if [ -n "$CACHED_TOKEN" ] && [ -n "$TOKEN_EXPIRY" ] && [ "$NOW" -lt "$TOKEN_EXPIRY" ]; then
  TOKEN=$CACHED_TOKEN
else
  RESP=$(curl -s -X POST https://api.dingtalk.com/v1.0/oauth2/accessToken \
    -H 'Content-Type: application/json' \
    -d "{\"appKey\":\"$APP_KEY\",\"appSecret\":\"$APP_SECRET\"}")
  TOKEN=$(echo "$RESP" | grep -o '"accessToken":"[^"]*"' | cut -d'"' -f4)
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
fi

# 在此追加具体 API 调用,例如创建待办:
RESULT=$(curl -s -X POST \
  "https://api.dingtalk.com/v1.0/todo/users/${UNION_ID}/tasks?operatorId=${UNION_ID}" \
  -H "x-acs-dingtalk-access-token: $TOKEN" \
  -H 'Content-Type: application/json' \
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
This is a markdown file, so SQP-2 applies to omitted warnings about behaviors affecting user data. The document describes updating tasks, marking them done, and especially deleting tasks, but it does not warn users that these actions modify or remove existing todo data and may not be reversible.

Static analysis

No suspicious patterns detected.