From 3c4b872acc4e86d3fd41fab90d6930984ac15bc1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2026 09:01:04 +0000 Subject: [PATCH 1/2] fix: escape GraphQL string interpolation in project mutations (CWE-89) Wrap ownerId, projectId, and repositoryId with escapeGraphQLString() in the createProjectV2 and linkProjectV2ToRepository GraphQL mutations. Previously, title was consistently escaped but the node ID fields were not, creating a defense-in-depth gap. While GitHub API node IDs are opaque and currently safe, consistently applying escapeGraphQLString() ensures that if the values ever contain special characters the queries cannot be altered. Fixes code scanning alerts #627 and #628. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- pkg/cli/project_command.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/cli/project_command.go b/pkg/cli/project_command.go index 0509d484c9a..61137612003 100644 --- a/pkg/cli/project_command.go +++ b/pkg/cli/project_command.go @@ -300,7 +300,7 @@ func createProject(ctx context.Context, ownerId, title string, verbose bool) (ma url } } - }`, ownerId, escapeGraphQLString(title)) + }`, escapeGraphQLString(ownerId), escapeGraphQLString(title)) output, err := workflow.RunGH("Creating project...", "api", "graphql", "-f", "query="+mutation) if err != nil { @@ -370,7 +370,7 @@ func linkProjectToRepo(ctx context.Context, projectId, repoSlug string, verbose id } } - }`, projectId, repoId) + }`, escapeGraphQLString(projectId), escapeGraphQLString(repoId)) _, err = workflow.RunGH("Linking project to repository...", "api", "graphql", "-f", "query="+mutation) if err != nil { From c969477ed4882d5dbff1e55d6135f73e63165d1c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Jun 2026 14:53:25 +0000 Subject: [PATCH 2/2] refactor: use GraphQL variables in project mutations to eliminate string interpolation Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/project_command.go | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/pkg/cli/project_command.go b/pkg/cli/project_command.go index 61137612003..06d1b334e2b 100644 --- a/pkg/cli/project_command.go +++ b/pkg/cli/project_command.go @@ -291,8 +291,8 @@ func createProject(ctx context.Context, ownerId, title string, verbose bool) (ma projectLog.Printf("Creating project: ownerId=%s, title=%s", ownerId, title) console.LogVerbose(verbose, "Creating project with owner ID: "+ownerId) - mutation := fmt.Sprintf(`mutation { - createProjectV2(input: { ownerId: "%s", title: "%s" }) { + mutation := `mutation($ownerId: ID!, $title: String!) { + createProjectV2(input: { ownerId: $ownerId, title: $title }) { projectV2 { id number @@ -300,9 +300,13 @@ func createProject(ctx context.Context, ownerId, title string, verbose bool) (ma url } } - }`, escapeGraphQLString(ownerId), escapeGraphQLString(title)) + }` - output, err := workflow.RunGH("Creating project...", "api", "graphql", "-f", "query="+mutation) + output, err := workflow.RunGH("Creating project...", "api", "graphql", + "-f", "query="+mutation, + "-f", "ownerId="+ownerId, + "-f", "title="+title, + ) if err != nil { // Check for permission errors //nolint:errstringmatch // gh CLI GraphQL surfaces missing Projects scope as INSUFFICIENT_SCOPES text. @@ -352,8 +356,13 @@ func linkProjectToRepo(ctx context.Context, projectId, repoSlug string, verbose repoName := parts[1] // Get repository ID - query := fmt.Sprintf(`query { repository(owner: "%s", name: "%s") { id } }`, escapeGraphQLString(repoOwner), escapeGraphQLString(repoName)) - output, err := workflow.RunGH("Getting repository ID...", "api", "graphql", "-f", "query="+query, "--jq", ".data.repository.id") + repoIdQuery := `query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { id } }` + output, err := workflow.RunGH("Getting repository ID...", "api", "graphql", + "-f", "query="+repoIdQuery, + "-f", "owner="+repoOwner, + "-f", "name="+repoName, + "--jq", ".data.repository.id", + ) if err != nil { return fmt.Errorf("repository '%s' not found: %w", repoSlug, err) } @@ -364,15 +373,19 @@ func linkProjectToRepo(ctx context.Context, projectId, repoSlug string, verbose } // Link project to repository - mutation := fmt.Sprintf(`mutation { - linkProjectV2ToRepository(input: { projectId: "%s", repositoryId: "%s" }) { + mutation := `mutation($projectId: ID!, $repositoryId: ID!) { + linkProjectV2ToRepository(input: { projectId: $projectId, repositoryId: $repositoryId }) { repository { id } } - }`, escapeGraphQLString(projectId), escapeGraphQLString(repoId)) + }` - _, err = workflow.RunGH("Linking project to repository...", "api", "graphql", "-f", "query="+mutation) + _, err = workflow.RunGH("Linking project to repository...", "api", "graphql", + "-f", "query="+mutation, + "-f", "projectId="+projectId, + "-f", "repositoryId="+repoId, + ) if err != nil { return fmt.Errorf("failed to link project to repository: %w", err) }