T05 · Unauthorized Access and Privilege Escalation
- Location
SKILL.md:88- Finding
Untrusted Environment Variable Permits DingTalk Account-Binding Bypass
- Content
View full analysis
Vulnerability Details
File Location:
SKILL.md:88-102and duplicated inSKILL.md:216-229
Vulnerability Type: Improper authorization and unsafe account selection
Risk Level: HighVulnerable Code
bash # Get current agent ID from environment (set by OpenClaw runtime) AGENT_ID="${OPENCLAW_AGENT_ID:-dingtalk-office}" # Look up accountId from bindings ACCOUNT_ID=$(jq -r ".bindings[] | select(.agentId == \"$AGENT_ID\") | .match.accountId" $OPENCLAW_CONFIG) # Fallback: if no binding found, use agent ID suffix if [ -z "$ACCOUNT_ID" ] || [ "$ACCOUNT_ID" = "null" ]; then # Extract account from agent ID (e.g., "dingtalk-office" → "office") ACCOUNT_ID=$(echo "$AGENT_ID" | sed 's/dingtalk-//') fi # Read credentials (clientId is used as appKey) APP_KEY=$(jq -r ".channels.dingtalk.accounts[\"$ACCOUNT_ID\"].clientId" $OPENCLAW_CONFIG) APP_SECRET=$(jq -r ".channels.dingtalk.accounts[\"$ACCOUNT_ID\"].clientSecret" $OPENCLAW_CONFIG) ROBOT_CODE=$(jq -r ".channels.dingtalk.accounts[\"$ACCOUNT_ID\"].robotCode" $OPENCLAW_CONFIG)The complete script repeats the same vulnerable selection logic:
bash # Get current agent ID from environment (set by OpenClaw runtime) AGENT_ID="${OPENCLAW_AGENT_ID:-dingtalk-office}" # Look up accountId from bindings ACCOUNT_ID=$(jq -r ".bindings[] | select(.agentId == \"$AGENT_ID\") | .match.accountId" $OPENCLAW_CONFIG) # Fallback: if no binding found, use agent ID suffix if [ -z "$ACCOUNT_ID" ] || [ "$ACCOUNT_ID" = "null" ]; then ACCOUNT_ID=$(echo "$AGENT_ID" | sed 's/dingtalk-//') fi # Read credentials from OpenClaw config APP_KEY=$(jq -r ".channels.dingtalk.accounts[\"$ACCOUNT_ID\"].clientId" $OPENCLAW_CONFIG) APP_SECRET=$(jq -r ".channels.dingtalk.accounts[\"$ACCOUNT_ID\"].clientSecret" $OPENCLAW_CONFIG) ROBOT_CODE=$(jq -r ".channels.dingtalk.accounts[\"$ACCOUNT_ID\"].robotCode" $OPENCLAW_CONFIG)Technical Analysis
The skill t ...[truncated 2743 chars]
- Remediation
View remediation
Remediation Suggestions
- Obtain the active agent identity exclusively from trusted runtime context. Do not use a caller-controlled environment variable as proof of session identity.
- Remove the account-name fallback. If no exact authorized binding exists, terminate the operation.
- Require the binding to use the DingTalk channel and reject missing, duplicate, empty, or malformed results.
- Pass external values to
jqwith--argrather than interpolating them into filter source code. - Quote the configuration path consistently and use
jq -eso missing configuration entries cause failure. - Validate that the resolved account belongs to the authenticated session before reading credentials or requesting a token.
- Consider maintaining an explicit runtime-provided account identifier that cannot be overridden by the skill caller.
A safer binding lookup is:
bash : "${TRUSTED_AGENT_ID:?Trusted runtime agent identity is required}" ACCOUNT_ID=$(jq -er --arg agent_id "$TRUSTED_AGENT_ID" ' [ .bindings[] | select( .agentId == $agent_id and .match.channel == "dingtalk" ) | .match.accountId ] | if length == 1 and .[0] != "" then .[0] else error("Exactly one authorized DingTalk binding is required") end ' "$OPENCLAW_CONFIG") || { echo "No unique authorized DingTalk account binding was found." exit 1 } APP_KEY=$(jq -er --arg account_id "$ACCOUNT_ID" \ '.channels.dingtalk.accounts[$account_id].clientId' \ "$OPENCLAW_CONFIG") || exit 1 APP_SECRET=$(jq -er --arg account_id "$ACCOUNT_ID" \ '.channels.dingtalk.accounts[$account_id].clientSecret' \ "$OPENCLAW_CONFIG") || exit 1 ROBOT_CODE=$(jq -er --arg account_id "$ACCOUNT_ID" \ '.channels.dingtalk.accounts[$account_id].robotCode' \ "$OPENCLAW_CONFIG") || exit 1Apply the correction to both the step-by-step commands and the duplicated complete script.
