T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/linear.sh:13
- Finding
- GraphQL and JSON Injection Through Untrusted Command Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/linear.sh:13-18, 75-80, 126-133, 140-151, 161-176, 182-190, 207-218, 246-263` **Vulnerability Type**: GraphQL injection and unsafe JSON construction **Risk Level**: High ### Vulnerable Code ```bash gql() { local query="$1" curl -s -X POST "$API" \ -H "Content-Type: application/json" \ -H "Authorization: $LINEAR_API_KEY" \ -d "{\"query\": \"$query\"}" } ``` Representative vulnerable call sites include: ```bash gql "{ workflowStates(filter: { team: { id: { eq: \\\"$team_id\\\" } }, name: { eq: \\\"$state_name\\\" } }) { nodes { id } } }" ``` ```bash project_name="${1:-}" if [[ -z "$project_name" ]]; then echo "Usage: linear.sh project <name>" >&2 exit 1 fi gql "{ projects(filter: { name: { containsIgnoreCase: \\\"$project_name\\\" } }, first: 1) { nodes { issues(first: 30, filter: { state: { type: { nin: [\\\"completed\\\", \\\"canceled\\\"] } } }) { nodes { identifier title state { name } priority priorityLabel assignee { name } } } } } }" | format_issues ``` ```bash team_key="${issue_id%%-*}" issue_num="${issue_id##*-}" gql "{ issues(filter: { number: { eq: $issue_num }, team: { key: { eq: \\\"$team_key\\\" } } }) { nodes { identifier title description state { name } priority priorityLabel assignee { name } project { name } team { name } createdAt dueDate } } }" ``` ```bash # Escape quotes in title and description title="${title//\"/\\\"}" description="${description//\"/\\\"}" result=$(gql "mutation { issueCreate(input: { teamId: \\\"$team_id\\\", title: \\\"$title\\\", description: \\\"$description\\\" }) { success issue { identifier title url } } }") ``` ```bash body="${body//\"/\\\"}" result=$(gql "mutation { commentCreate(input: { issueId: \\\"$issue_uuid\\\", body: \\\"$body\\\" }) { success comment { id } } }") ``` ```bash user_id=$(gql "{ users(filter: { name: { containsIgnoreCase: \\\"$user_name\\\" } }) { nodes { id name } } }" | jq -r '.data.users.nodes[0].id') ...[truncated 2731 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Use GraphQL variables for every dynamic value rather than interpolating values into GraphQL source text. - Build the entire HTTP request with a real JSON encoder, for example: ```bash payload=$(jq -n \ --arg query "$query" \ --argjson variables "$variables" \ '{query: $query, variables: $variables}') curl --silent --show-error --fail-with-body \ -X POST "$API" \ -H "Content-Type: application/json" \ --data-binary "$payload" ``` - Define static GraphQL documents and pass titles, descriptions, comments, project names, usernames, IDs, and other values through the `variables` object. - Validate issue identifiers before use, for example against a strict format such as `^[A-Za-z][A-Za-z0-9_]*-[0-9]+$`. - Restrict team keys and status aliases to documented character sets or explicit allowlists. - Never treat quote replacement alone as sufficient GraphQL or JSON escaping. - Detect and reject GraphQL responses containing an `errors` array before processing `.data`. - Add regression tests containing quotes, backslashes, newlines, tabs, Unicode, malformed issue numbers, and GraphQL punctuation. ]]>
