T09 · Insecure Skill Coding Practices
- Location
- SKILL.md:299
- Finding
- GitHub PAT Disclosure Through Unquoted Curl URL Expansion<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 299–305; equivalent unsafe expansions also occur at lines 330, 347, 363, and 411 **Vulnerability Type**: Shell word splitting leading to curl argument injection and credential disclosure **Risk Level**: High ### Vulnerable Code ```bash OWNER="octocat" REPO="hello-world" URL="${BASE_URL}/repos/${OWNER}/${REPO}/issues" curl -s -X POST ${URL} \ -H "Authorization: Bearer ${GH_TOKEN}" \ ``` The same unsafe pattern is used in other examples: ```bash curl -s ${URL} \ -H "Authorization: Bearer ${GH_TOKEN}" ``` ### Technical Analysis The URL is constructed from repository identifiers that would ordinarily be derived from user requests. Although the assignment to `URL` is quoted, `${URL}` is expanded without quotes when passed to `curl`. In a POSIX-compatible shell, an unquoted variable expansion is subject to word splitting and pathname expansion. If an attacker-controlled owner or repository value contains whitespace followed by another URL or curl option, the resulting value can be interpreted as multiple command-line arguments rather than one URL. This does not require shell metacharacters such as semicolons to be re-evaluated. Word splitting alone can introduce an additional URL into the existing curl invocation. Curl applies the explicitly configured `Authorization: Bearer ${GH_TOKEN}` header to requests made by that invocation, creating a credible path for the GitHub PAT to be transmitted to an attacker-controlled server. Authenticated communication with `https://api.github.com` is necessary for the Skill’s declared functionality. Allowing the same credential-bearing invocation to contact an unvalidated destination exceeds the minimum privileges required. ### Attack Path 1. An attacker provides a crafted repository owner or repository name containing whitespace and an attacker-controlled HTTPS URL. 2. The agent places that value into `OWNER` or `REPO` and constructs `URL`. 3. T ...[truncated 1347 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Quote every URL expansion passed to curl: ```bash curl -s -X POST "$URL" \ -H "Authorization: Bearer ${GH_TOKEN}" ``` Apply this correction to all affected commands, including lines 304, 330, 347, 363, and 411. 2. Validate user-derived GitHub identifiers before constructing a URL. Reject whitespace, control characters, URL delimiters, and values beginning with `-`. For example: ```bash validate_repo_component() { case "$1" in ""|*[!A-Za-z0-9_.-]*|-*) echo "Invalid GitHub repository identifier" >&2 return 1 ;; esac } validate_repo_component "$OWNER" || exit 1 validate_repo_component "$REPO" || exit 1 ``` 3. Pass the destination explicitly and terminate curl option parsing: ```bash curl -s -X POST --url "$URL" \ -H "Authorization: Bearer ${GH_TOKEN}" ``` Where compatible with the command structure, also use `--` before positional URL arguments. 4. Enforce an endpoint allowlist before attaching credentials. Confirm that the parsed scheme is HTTPS and the exact hostname is `api.github.com`; do not rely only on a string prefix check. 5. Use fine-grained, repository-specific, short-lived PATs. Separate read-only and write-capable credentials where practical, and avoid classic tokens with the broad `repo` scope. 6. Add negative tests using repository values containing spaces, leading dashes, additional URLs, tabs, and control characters. Verify that malformed values are rejected before curl executes. 7. Avoid printing the credential during setup verification. Replace the documented `cat ~/.config/openclaw/github_token` check with an existence and permissions check such as: ```bash test -r ~/.config/openclaw/github_token && stat ~/.config/openclaw/github_token ``` ]]>
