Summary
Semgrep alerts #627 and #628 (both created 2026-06-16, open 17 days) flag unescaped GraphQL ID injection in two mutations in the project command.
File: pkg/cli/project_command.go lines 295 and 368
Rule: workflow-graphql-id-unescaped (CWE-89 — injection)
Severity: WARNING (defense-in-depth gap)
Tier & Risk Scoring
| Dimension |
Score |
Notes |
| Exposure amplification |
3 |
CLI command used for project management; affects any user invoking project commands |
| Patchability |
2 |
Two call sites; wrap with existing helper or parameterise |
| Detectability |
3 |
GitHub API node IDs are currently opaque; special chars unlikely but not impossible |
| Operational fragility |
3 |
Malformed query could fail silently or be exploited if ID format changes |
| Ownership confidence |
4 |
Active CLI team ownership |
| Aggregate |
15 |
Tier B — Open With Conditions |
SLA: High — fix within 7 days.
Root Cause
Alert #627 — createProject() (line 295)
ownerId is injected directly into the GraphQL mutation string via fmt.Sprintf without escapeGraphQLString(). The title argument on the same call (line 303) is already escaped — creating an inconsistency. If GitHub ever returns a node ID with special characters, this could trigger GraphQL injection.
Alert #628 — linkProjectToRepo() (line 368)
Both projectId and repositoryId are injected without escapeGraphQLString(). These values come from prior API calls, but are not escaped before interpolation into the mutation string.
Recommended Fix
Option A — wrap with existing helper:
// line 295 — createProject()
query := fmt.Sprintf(`mutation { createProject(input: {ownerId: "%s", title: "%s"}) { ... } }`,
escapeGraphQLString(ownerId), // add escape
escapeGraphQLString(title))
// line 368 — linkProjectToRepo()
query := fmt.Sprintf(`mutation { linkProjectV2ToRepository(input: {projectId: "%s", repositoryId: "%s"}) { ... } }`,
escapeGraphQLString(projectId), // add escape
escapeGraphQLString(repositoryId)) // add escape
Option B — parameterise via GraphQL variables (preferred):
gh api graphql -f query='mutation($ownerId:ID!,$title:String!){createProject(input:{ownerId:$ownerId,title:$title}){...}}' \
-f ownerId="$ownerId" -f title="$title"
Governance Context
Identified by the UK AI Open Code Risk & Resilience Governance scan (2026-07-03). See discussion report for full tier classification and remediation queue.
References: Semgrep alert #627 · Semgrep alert #628 · run 28671336499
Generated by UK AI Operational Resilience · 91.2 AIC · ⌖ 8.39 AIC · ⊞ 5.2K · ◷
Summary
Semgrep alerts #627 and #628 (both created 2026-06-16, open 17 days) flag unescaped GraphQL ID injection in two mutations in the project command.
File:
pkg/cli/project_command.golines 295 and 368Rule:
workflow-graphql-id-unescaped(CWE-89 — injection)Severity: WARNING (defense-in-depth gap)
Tier & Risk Scoring
SLA: High — fix within 7 days.
Root Cause
Alert #627 —
createProject()(line 295)ownerIdis injected directly into the GraphQL mutation string viafmt.SprintfwithoutescapeGraphQLString(). Thetitleargument on the same call (line 303) is already escaped — creating an inconsistency. If GitHub ever returns a node ID with special characters, this could trigger GraphQL injection.Alert #628 —
linkProjectToRepo()(line 368)Both
projectIdandrepositoryIdare injected withoutescapeGraphQLString(). These values come from prior API calls, but are not escaped before interpolation into the mutation string.Recommended Fix
Option A — wrap with existing helper:
Option B — parameterise via GraphQL variables (preferred):
Governance Context
Identified by the UK AI Open Code Risk & Resilience Governance scan (2026-07-03). See discussion report for full tier classification and remediation queue.
References: Semgrep alert #627 · Semgrep alert #628 · run 28671336499