T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/feishu-calendar.sh:24
- Finding
- Tenant access token stored in an insecure predictable temporary file<![CDATA[ ## Vulnerability Details **File Location**: `scripts/feishu-calendar.sh`, lines 24-51 **Vulnerability Type**: Predictable temporary file and plaintext bearer-token storage **Risk Level**: High ### Vulnerable Code ```bash TOKEN_CACHE="/tmp/feishu_token_cache" get_token() { if [[ -f "$TOKEN_CACHE" ]]; then cached=$(cat "$TOKEN_CACHE") expire=$(echo "$cached" | jq -r '.expire // 0') now=$(date +%s) if (( now < expire )); then echo "$cached" | jq -r '.token' return fi fi local resp resp=$(curl -s -X POST "$BASE_URL/auth/v3/tenant_access_token/internal" \ -H "Content-Type: application/json" \ -d "{\"app_id\":\"${FEISHU_APP_ID}\",\"app_secret\":\"${FEISHU_APP_SECRET}\"}") local token expire_at token=$(echo "$resp" | jq -r '.tenant_access_token // empty') if [[ -z "$token" ]]; then echo "ERROR: Failed to get token: $resp" >&2 exit 1 fi expire_at=$(( $(date +%s) + 7000 )) echo "{\"token\":\"$token\",\"expire\":$expire_at}" > "$TOKEN_CACHE" echo "$token" } ``` ### Technical Analysis The script stores a Feishu tenant bearer token in the fixed path `/tmp/feishu_token_cache`. The shared temporary directory is accessible to multiple local users, and the script does not: - Set a restrictive `umask`. - Explicitly create the cache with mode `0600`. - Verify that the cache is owned by the current user. - Reject symbolic links or other unexpected file types. - Separate cache files by user, application, or tenant. - Create or replace the cache atomically. With a common `umask` of `022`, a newly created cache can be readable by other local users. Because the path is predictable, an attacker can monitor it and retrieve the bearer token while it remains valid. On systems without effective temporary-directory symlink protections, pre-creating the path as a symbolic link may also redirect the write to another file writable by the victim process. The script also trusts any unexpired JSON object al ...[truncated 1777 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Store the cache in a private runtime directory rather than directly under `/tmp`, for example: - `${XDG_RUNTIME_DIR}/feishu-calendar/`, after validating its ownership and permissions. - A directory created with `mktemp -d` and mode `0700`. 2. Set `umask 077` before creating any credential-bearing file. 3. Create the token cache atomically with mode `0600`, then rename it into place. 4. Verify that the cache: - Is a regular file. - Is not a symbolic link. - Is owned by the effective user. - Is not readable or writable by group or other users. 5. Use separate cache names keyed by the current user and a non-secret application or tenant identifier. 6. Delete expired cache files and clear the cache when authentication fails. 7. Prefer an operating-system credential store or in-memory caching when available. 8. Avoid printing the token through the public `token` command unless token disclosure is an explicitly required administrative operation. A hardened implementation should use a private directory and atomic creation, for example: ```bash umask 077 CACHE_DIR="${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/feishu-calendar-${UID}" mkdir -p "$CACHE_DIR" chmod 700 "$CACHE_DIR" TOKEN_CACHE="$CACHE_DIR/token-cache" tmp_cache=$(mktemp "$CACHE_DIR/token.XXXXXX") chmod 600 "$tmp_cache" jq -n --arg token "$token" --argjson expire "$expire_at" \ '{token: $token, expire: $expire}' > "$tmp_cache" mv -f "$tmp_cache" "$TOKEN_CACHE" ``` Ownership and file-type checks should still be performed before reading an existing cache. ]]>
