diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d8add188442..44e18446edc 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,7 +3,7 @@ "image": "mcr.microsoft.com/devcontainers/base:ubuntu", "features": { "ghcr.io/devcontainers/features/go:1": { - "version": "1.25" + "version": "1.26" }, "ghcr.io/devcontainers/features/docker-in-docker:2.11.0": { "version": "latest", diff --git a/.devcontainer/light/devcontainer.json b/.devcontainer/light/devcontainer.json index c21d242d835..2225d671c92 100644 --- a/.devcontainer/light/devcontainer.json +++ b/.devcontainer/light/devcontainer.json @@ -3,7 +3,7 @@ "image": "mcr.microsoft.com/devcontainers/base:ubuntu", "features": { "ghcr.io/devcontainers/features/go:1": { - "version": "1.25" + "version": "1.26" }, "ghcr.io/devcontainers/features/docker-in-docker:2.11.0": { "version": "latest", diff --git a/.github/workflows/cli-ci.yml b/.github/workflows/cli-ci.yml index 5999add6ec7..1aa2421364d 100644 --- a/.github/workflows/cli-ci.yml +++ b/.github/workflows/cli-ci.yml @@ -5,6 +5,8 @@ on: paths: - "cli/**" - ".github/workflows/cli-ci.yml" + paths-ignore: + - "cli/azd/extensions/**" branches: [main] # If two events are triggered within a short time in the same PR, cancel the run of the oldest event @@ -18,21 +20,9 @@ permissions: jobs: azd-lint: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest] - steps: - - uses: actions/setup-go@v6 - with: - go-version: "^1.25" - - uses: actions/checkout@v4 - - name: golangci-lint - uses: golangci/golangci-lint-action@v8 - with: - version: v2.6 - args: -v --timeout 10m0s - working-directory: cli/azd + uses: ./.github/workflows/lint-go.yml + with: + working-directory: cli/azd cspell-lint: runs-on: ubuntu-latest diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 21ea02e0ae6..9aeecbe1ea3 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -38,7 +38,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 with: - go-version: "^1.25" + go-version: "^1.26" - name: Set up Node.js uses: actions/setup-node@v4 @@ -46,7 +46,7 @@ jobs: node-version: "20" - name: Install golangci-lint - run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.6.0 + run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.10.1 - name: Install cspell run: npm install -g cspell@8.13.1 diff --git a/.github/workflows/ext-registry-ci.yml b/.github/workflows/ext-registry-ci.yml index 08eb665ddfc..09ffc3159bb 100644 --- a/.github/workflows/ext-registry-ci.yml +++ b/.github/workflows/ext-registry-ci.yml @@ -27,7 +27,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 with: - go-version: "^1.25" + go-version: "^1.26" cache-dependency-path: | cli/azd/go.sum diff --git a/.github/workflows/lint-go.yml b/.github/workflows/lint-go.yml new file mode 100644 index 00000000000..0f5cd39e2e6 --- /dev/null +++ b/.github/workflows/lint-go.yml @@ -0,0 +1,41 @@ +name: lint-go + +on: + workflow_call: + inputs: + working-directory: + description: "Path to the Go module directory to lint" + required: true + type: string + go-version: + description: "Go version to use" + required: false + type: string + default: "^1.26" + golangci-lint-version: + description: "GolangCI-Lint version to use" + required: false + type: string + default: "v2.10.1" + +permissions: + contents: read + pull-requests: write + +jobs: + golangci-lint: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + steps: + - uses: actions/setup-go@v6 + with: + go-version: ${{ inputs.go-version }} + - uses: actions/checkout@v4 + - name: golangci-lint + uses: golangci/golangci-lint-action@v8 + with: + version: ${{ inputs.golangci-lint-version }} + args: -v --timeout 10m0s + working-directory: ${{ inputs.working-directory }} diff --git a/cli/azd/.golangci.yaml b/cli/azd/.golangci.yaml index 6ca03546689..000b4285ec7 100644 --- a/cli/azd/.golangci.yaml +++ b/cli/azd/.golangci.yaml @@ -38,6 +38,7 @@ linters: - G302 # TODO: Expect file permissions to be 0600 or less - G304 # TODO: Potential file inclusion via variable - G104 # TODO: Errors unhandled + - G117 # Exported struct fields matching secret patterns (false positives on field names) formatters: enable: - gofmt diff --git a/cli/azd/AGENTS.md b/cli/azd/AGENTS.md index 3f557615ace..39d2c8e5a0c 100644 --- a/cli/azd/AGENTS.md +++ b/cli/azd/AGENTS.md @@ -148,7 +148,7 @@ func (a *myAction) Run(ctx context.Context) (*actions.ActionResult, error) { ### Modern Go -This project uses Go 1.25. Use modern standard library features: +This project uses Go 1.26. Use modern standard library features: - **`slices`, `maps`, `cmp` packages**: Use for searching, sorting, cloning, and iterating—avoid manual loops - **Iterators**: Use `range` over functions/iterators (e.g., `maps.Keys()`, `slices.All()`) diff --git a/cli/azd/CONTRIBUTING.md b/cli/azd/CONTRIBUTING.md index 2f399cf4dc9..55c0989d92a 100644 --- a/cli/azd/CONTRIBUTING.md +++ b/cli/azd/CONTRIBUTING.md @@ -20,7 +20,7 @@ In general, to make contributions a smooth and easy experience, we encourage the Prerequisites: -- [Go](https://go.dev/dl/) 1.25 +- [Go](https://go.dev/dl/) 1.26 Build: diff --git a/cli/azd/ci-test.ps1 b/cli/azd/ci-test.ps1 index c2f59857245..7183b15529e 100644 --- a/cli/azd/ci-test.ps1 +++ b/cli/azd/ci-test.ps1 @@ -36,7 +36,7 @@ Write-Host "Running unit tests..." # --test.gocoverdir is currently a "under-the-cover" way to pass the coverage directory to a test binary # See https://github.com/golang/go/issues/51430#issuecomment-1344711300 # -# As of Go 1.25, it’s still an “under-the-hood” option. +# As of Go 1.26, it’s still an “under-the-hood” option. & $gotestsum -- ./... -short -v -cover -args --test.gocoverdir="$($unitCoverDir.FullName)" if ($LASTEXITCODE) { exit $LASTEXITCODE diff --git a/cli/azd/cmd/middleware/experimentation.go b/cli/azd/cmd/middleware/experimentation.go index 7cf319cda77..e0b1e7d7fdb 100644 --- a/cli/azd/cmd/middleware/experimentation.go +++ b/cli/azd/cmd/middleware/experimentation.go @@ -31,6 +31,7 @@ func (m *ExperimentationMiddleware) Run(ctx context.Context, next NextFn) (*acti // Allow overriding the assignment endpoint, either for local development (where you want to hit a private instance) // or testing (we use this in our end to end tests to control assignment behavior for the CLI under test)/ if override := os.Getenv("AZD_DEBUG_EXPERIMENTATION_TAS_ENDPOINT"); override != "" { + //nolint:gosec // G706: env var in debug log log.Printf("using override assignment endpoint: %s, from AZD_DEBUG_EXPERIMENTATION_TAS_ENDPOINT", override) endpoint = override } @@ -42,6 +43,7 @@ func (m *ExperimentationMiddleware) Run(ctx context.Context, next NextFn) (*acti if assignment, err := assignmentManager.Assignment(ctx); err != nil { log.Printf("failed to get variant assignments: %v", err) } else { + //nolint:gosec // G706: internal API value in debug log log.Printf("assignment context: %v", assignment.AssignmentContext) tracing.SetGlobalAttributes(fields.ExpAssignmentContextKey.String(assignment.AssignmentContext)) diff --git a/cli/azd/extensions/microsoft.azd.extensions/internal/helpers.go b/cli/azd/extensions/microsoft.azd.extensions/internal/helpers.go index 7bb30b11a84..b05c73daea0 100644 --- a/cli/azd/extensions/microsoft.azd.extensions/internal/helpers.go +++ b/cli/azd/extensions/microsoft.azd.extensions/internal/helpers.go @@ -140,19 +140,19 @@ func DownloadAssetToTemp(assetUrl, assetName string) (string, error) { // #nosec G107: Potential HTTP request made with variable url resp, err := http.Get(assetUrl) if err != nil { - os.Remove(tempFile.Name()) + os.Remove(tempFile.Name()) //nolint:gosec // G703: temp file cleanup return "", fmt.Errorf("failed to download asset: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - os.Remove(tempFile.Name()) + os.Remove(tempFile.Name()) //nolint:gosec // G703: temp file cleanup return "", fmt.Errorf("download returned status %d", resp.StatusCode) } reader = resp.Body } else { localFile, err := os.Open(assetUrl) if err != nil { - os.Remove(tempFile.Name()) + os.Remove(tempFile.Name()) //nolint:gosec // G703: temp file cleanup return "", fmt.Errorf("failed to open local file: %w", err) } defer localFile.Close() @@ -160,7 +160,7 @@ func DownloadAssetToTemp(assetUrl, assetName string) (string, error) { } if _, err := io.Copy(tempFile, reader); err != nil { - os.Remove(tempFile.Name()) + os.Remove(tempFile.Name()) //nolint:gosec // G703: temp file cleanup return "", fmt.Errorf("failed to write to temp file: %w", err) } tempFile.Close() @@ -377,6 +377,7 @@ func CreateLocalRegistry() error { return fmt.Errorf("failed to marshal empty registry: %w", err) } + //nolint:gosec // G703: path from azd config directory if err := os.WriteFile(localRegistryPath, registryJson, PermissionFile); err != nil { return fmt.Errorf("failed to create local registry file: %w", err) } @@ -389,7 +390,7 @@ func CreateLocalRegistry() error { } /* #nosec G204 - args are hardcoded above, not user-controlled */ - createExtSourceCmd := exec.Command("azd", args...) + createExtSourceCmd := exec.Command("azd", args...) //nolint:gosec // G702: args are hardcoded above, not user-controlled if _, err := createExtSourceCmd.CombinedOutput(); err != nil { return fmt.Errorf("failed to create local extension source: %w", err) } diff --git a/cli/azd/go.mod b/cli/azd/go.mod index bf8e23cb2f3..a5614ad1d91 100644 --- a/cli/azd/go.mod +++ b/cli/azd/go.mod @@ -1,6 +1,6 @@ module github.com/azure/azure-dev/cli/azd -go 1.25 +go 1.26 require ( dario.cat/mergo v1.0.2 diff --git a/cli/azd/internal/cmd/errors_test.go b/cli/azd/internal/cmd/errors_test.go index 62ca29a8115..a095c1bdd37 100644 --- a/cli/azd/internal/cmd/errors_test.go +++ b/cli/azd/internal/cmd/errors_test.go @@ -621,6 +621,7 @@ func Test_isNetworkError(t *testing.T) { // 2. Add it to the excludedErrors list below with a comment explaining why. func Test_PackageLevelErrorsMapped(t *testing.T) { // Package-level error variables that are intentionally NOT mapped in MapError, with reasons: + //nolint:gosec // G101: map variable name, not credentials excludedErrors := map[string]string{ // Internal-only errors that never propagate to command-level "ErrDuplicateRegistration": "internal/mapper: programming error, not a runtime user error", diff --git a/cli/azd/internal/mcp/tools/azd_yaml_schema_test.go b/cli/azd/internal/mcp/tools/azd_yaml_schema_test.go index 8929783f619..8739611c90f 100644 --- a/cli/azd/internal/mcp/tools/azd_yaml_schema_test.go +++ b/cli/azd/internal/mcp/tools/azd_yaml_schema_test.go @@ -36,7 +36,7 @@ func TestHandleAzdYamlSchema_ValidYaml(t *testing.T) { oldWd, _ := os.Getwd() os.Chdir(tmpDir) defer os.Chdir(oldWd) - os.Rename(yamlPath, "azure.yaml") + os.Rename(yamlPath, "azure.yaml") //nolint:gosec // G703: test file rename with controlled paths defer os.Remove("azure.yaml") req := mcp.CallToolRequest{} @@ -85,7 +85,7 @@ func TestHandleAzdYamlSchema_InvalidYaml(t *testing.T) { oldWd, _ := os.Getwd() os.Chdir(tmpDir) defer os.Chdir(oldWd) - os.Rename(yamlPath, "azure.yaml") + os.Rename(yamlPath, "azure.yaml") //nolint:gosec // G703: test file rename with controlled paths defer os.Remove("azure.yaml") req := mcp.CallToolRequest{} @@ -115,7 +115,7 @@ func TestHandleAzdYamlSchema_YamlNotValidSyntax(t *testing.T) { oldWd, _ := os.Getwd() os.Chdir(tmpDir) defer os.Chdir(oldWd) - os.Rename(yamlPath, "azure.yaml") + os.Rename(yamlPath, "azure.yaml") //nolint:gosec // G703: test file rename with controlled paths defer os.Remove("azure.yaml") req := mcp.CallToolRequest{} @@ -146,7 +146,7 @@ func TestHandleAzdYamlSchema_YamlValidButSchemaInvalid(t *testing.T) { oldWd, _ := os.Getwd() os.Chdir(tmpDir) defer os.Chdir(oldWd) - os.Rename(yamlPath, "azure.yaml") + os.Rename(yamlPath, "azure.yaml") //nolint:gosec // G703: test file rename with controlled paths defer os.Remove("azure.yaml") req := mcp.CallToolRequest{} @@ -176,7 +176,7 @@ func TestHandleAzdYamlSchema_InvalidYaml_Structural(t *testing.T) { oldWd, _ := os.Getwd() os.Chdir(tmpDir) defer os.Chdir(oldWd) - os.Rename(yamlPath, "azure.yaml") + os.Rename(yamlPath, "azure.yaml") //nolint:gosec // G703: test file rename with controlled paths defer os.Remove("azure.yaml") req := mcp.CallToolRequest{} diff --git a/cli/azd/internal/scaffold/resource_meta.go b/cli/azd/internal/scaffold/resource_meta.go index 82226ca9d27..d70dd36143d 100644 --- a/cli/azd/internal/scaffold/resource_meta.go +++ b/cli/azd/internal/scaffold/resource_meta.go @@ -81,6 +81,7 @@ var Resources = []ResourceMeta{ ResourceType: "Microsoft.Cache/redis", ApiVersion: "2024-03-01", StandardVarPrefix: "REDIS", + //nolint:gosec // G101: template variable references, not hardcoded credentials Variables: map[string]string{ "host": "${.properties.hostName}", "port": "6380", @@ -136,6 +137,7 @@ var Resources = []ResourceMeta{ ApiVersion: "2023-12-30", StandardVarPrefix: "MYSQL", ParentForEval: "Microsoft.DBforMySQL/flexibleServers", + //nolint:gosec // G101: template variable references, not hardcoded credentials Variables: map[string]string{ "database": "${spec.name}", "host": "${.properties.fullyQualifiedDomainName}", @@ -150,6 +152,7 @@ var Resources = []ResourceMeta{ ApiVersion: "2022-12-01", StandardVarPrefix: "POSTGRES", ParentForEval: "Microsoft.DBforPostgreSQL/flexibleServers", + //nolint:gosec // G101: template variable references, not hardcoded credentials Variables: map[string]string{ "database": "${spec.name}", "host": "${.properties.fullyQualifiedDomainName}", diff --git a/cli/azd/internal/telemetry/appinsights-exporter/transmitter.go b/cli/azd/internal/telemetry/appinsights-exporter/transmitter.go index cdd61044595..772b07d0714 100644 --- a/cli/azd/internal/telemetry/appinsights-exporter/transmitter.go +++ b/cli/azd/internal/telemetry/appinsights-exporter/transmitter.go @@ -86,7 +86,7 @@ func (transmitter *httpTransmitter) Transmit(payload []byte, items TelemetryItem req.Header.Set("Content-Type", "application/x-json-stream") req.Header.Set("Accept-Encoding", "gzip, deflate") - resp, err := transmitter.client.Do(req) + resp, err := transmitter.client.Do(req) //nolint:gosec // G704: endpoint is a known telemetry ingestion URL if err != nil { diagLog.Printf("Failed to transmit telemetry: %s", err.Error()) return nil, err diff --git a/cli/azd/internal/telemetry/notice_test.go b/cli/azd/internal/telemetry/notice_test.go index f7e93a847ef..991af8639e7 100644 --- a/cli/azd/internal/telemetry/notice_test.go +++ b/cli/azd/internal/telemetry/notice_test.go @@ -32,7 +32,7 @@ func setupSuite(withFirstRunFile bool, t *testing.T) func(t *testing.T) { file.Close() tmpFilename = file.Name() - err = os.Rename(firstRunFilePath, tmpFilename) + err = os.Rename(firstRunFilePath, tmpFilename) //nolint:gosec // G703: test file paths if err != nil { t.Fatalf("failed to rename first run file: %v", err) } diff --git a/cli/azd/internal/telemetry/storage.go b/cli/azd/internal/telemetry/storage.go index d769c46a563..869f25a7a35 100644 --- a/cli/azd/internal/telemetry/storage.go +++ b/cli/azd/internal/telemetry/storage.go @@ -113,6 +113,7 @@ func (stg *StorageQueue) save(delayDuration time.Duration, retryCount int, messa } tempFileName := file.Name() + //nolint:gosec // G703: tempFileName from os.CreateTemp err = os.WriteFile(tempFileName, message, osutil.PermissionFile) if err != nil { _ = removeIfExists(tempFileName) @@ -124,6 +125,7 @@ func (stg *StorageQueue) save(delayDuration time.Duration, retryCount int, messa randomSuffix := generatedFileName[:strings.LastIndex(generatedFileName, "_")] readyTime := stg.clock.Now().Add(delayDuration) fileName := formatFileName(readyTime, retryCount, randomSuffix, stg.itemFileExtension) + //nolint:gosec // G703: paths from os.CreateTemp within known folder err = os.Rename(tempFileName, filepath.Join(stg.folder, fileName)) if err != nil { _ = removeIfExists(tempFileName) @@ -189,7 +191,7 @@ func (stg *StorageQueue) Remove(item *StoredItem) error { } func removeIfExists(filename string) error { - err := os.Remove(filename) + err := os.Remove(filename) //nolint:gosec // G703: internal cleanup if errors.Is(err, os.ErrNotExist) { return nil } diff --git a/cli/azd/magefile.go b/cli/azd/magefile.go index 2a5d567368f..acac504229d 100644 --- a/cli/azd/magefile.go +++ b/cli/azd/magefile.go @@ -146,7 +146,7 @@ func Preflight() error { // Check required tools are installed before running anything. if err := requireTool("golangci-lint", - "go install github.com/golangci/golangci-lint/cmd/golangci-lint@v2.6"); err != nil { + "go install github.com/golangci/golangci-lint/cmd/golangci-lint@v2.10.1"); err != nil { return err } if err := requireTool("cspell", "npm install -g cspell@8.13.1"); err != nil { diff --git a/cli/azd/pkg/auth/errors.go b/cli/azd/pkg/auth/errors.go index cb52cbc6231..85614dc231c 100644 --- a/cli/azd/pkg/auth/errors.go +++ b/cli/azd/pkg/auth/errors.go @@ -197,7 +197,7 @@ func (e *AuthFailedError) Error() string { func (e *AuthFailedError) httpErrorDetails() string { msg := &bytes.Buffer{} - fmt.Fprintf(msg, + fmt.Fprintf(msg, //nolint:gosec // G705: writing to bytes.Buffer, not an HTTP response "%s %s://%s%s\n", e.RawResp.Request.Method, e.RawResp.Request.URL.Scheme, diff --git a/cli/azd/pkg/config/manager.go b/cli/azd/pkg/config/manager.go index 870da9b074c..2e0a91b5795 100644 --- a/cli/azd/pkg/config/manager.go +++ b/cli/azd/pkg/config/manager.go @@ -78,6 +78,7 @@ func GetUserConfigDir() (string, error) { configDirPath = filepath.Join(homeDir, ".azd") } + //nolint:gosec // G703: configDirPath is derived from user home directory err := os.MkdirAll(configDirPath, osutil.PermissionDirectoryOwnerOnly) if err != nil { return configDirPath, err @@ -87,6 +88,7 @@ func GetUserConfigDir() (string, error) { // user. In cases where the config directory is ~/.azd, OS upgrades and // other processes can remove the "x" permission if runtime.GOOS == "linux" || runtime.GOOS == "darwin" { + //nolint:gosec // G703: configDirPath is derived from user home directory info, err := os.Stat(configDirPath) if err != nil { return configDirPath, err @@ -95,6 +97,7 @@ func GetUserConfigDir() (string, error) { permissions := info.Mode().Perm() if permissions&osutil.PermissionMaskDirectoryExecute == 0 { // Ensure user execute permissions + //nolint:gosec // G703: configDirPath from user home err := os.Chmod(configDirPath, permissions|osutil.PermissionMaskDirectoryExecute) return configDirPath, err } diff --git a/cli/azd/pkg/exec/command_runner.go b/cli/azd/pkg/exec/command_runner.go index 71a5e0dc02a..4d7eb805ed0 100644 --- a/cli/azd/pkg/exec/command_runner.go +++ b/cli/azd/pkg/exec/command_runner.go @@ -340,7 +340,8 @@ func newCmdTree(ctx context.Context, cmd string, args []string, useShell bool, i } else { return CmdTree{ CmdTreeOptions: options, - Cmd: exec.CommandContext(ctx, cmd, args...), + //nolint:gosec // G204: cmd/args are caller-controlled + Cmd: exec.CommandContext(ctx, cmd, args...), }, nil } } @@ -389,6 +390,6 @@ func newCmdTree(ctx context.Context, cmd string, args []string, useShell bool, i // #nosec G204 - Subprocess launched with a potential tainted input or cmd arguments false positive return CmdTree{ CmdTreeOptions: options, - Cmd: exec.Command(shellName, allArgs...), + Cmd: exec.Command(shellName, allArgs...), //nolint:gosec // G702: shellName is a known system shell path }, nil } diff --git a/cli/azd/pkg/extensions/manager.go b/cli/azd/pkg/extensions/manager.go index ac1202b9961..92a12f2b2e2 100644 --- a/cli/azd/pkg/extensions/manager.go +++ b/cli/azd/pkg/extensions/manager.go @@ -773,6 +773,7 @@ func validateChecksum(filePath string, checksum ExtensionChecksum) error { } // Open the file for reading + //nolint:gosec // G703: filePath from extension install file, err := os.Open(filePath) if err != nil { return fmt.Errorf("failed to open file for checksum validation: %w", err) diff --git a/cli/azd/pkg/extensions/registry_cache.go b/cli/azd/pkg/extensions/registry_cache.go index 3428ebb6bb6..83a741ac360 100644 --- a/cli/azd/pkg/extensions/registry_cache.go +++ b/cli/azd/pkg/extensions/registry_cache.go @@ -80,9 +80,11 @@ func NewRegistryCacheManager() (*RegistryCacheManager, error) { func getCacheTTL() time.Duration { if envTTL := os.Getenv(cacheTTLEnvVar); envTTL != "" { if duration, err := time.ParseDuration(envTTL); err == nil { + //nolint:gosec // G706: env var in debug log log.Printf("using custom cache TTL from %s: %s", cacheTTLEnvVar, duration) return duration } + //nolint:gosec // G706: env var in debug log log.Printf("invalid cache TTL value '%s', using default %s", envTTL, defaultCacheTTL) } return defaultCacheTTL diff --git a/cli/azd/pkg/infra/provisioning/terraform/terraform_provider.go b/cli/azd/pkg/infra/provisioning/terraform/terraform_provider.go index ab1d1b8c242..82eb3e20f42 100644 --- a/cli/azd/pkg/infra/provisioning/terraform/terraform_provider.go +++ b/cli/azd/pkg/infra/provisioning/terraform/terraform_provider.go @@ -682,6 +682,7 @@ func (t *TerraformProvider) isRemoteBackendConfig() (bool, error) { for index := range files { if !files[index].IsDir() && filepath.Ext(files[index].Name()) == ".tf" { + //nolint:gosec // G703: modulePath is a trusted project directory fileContent, err := os.ReadFile(filepath.Join(modulePath, files[index].Name())) if err != nil { diff --git a/cli/azd/pkg/pipeline/pipeline_test.go b/cli/azd/pkg/pipeline/pipeline_test.go index 6bd7e14e570..ebe7b46c04d 100644 --- a/cli/azd/pkg/pipeline/pipeline_test.go +++ b/cli/azd/pkg/pipeline/pipeline_test.go @@ -63,6 +63,7 @@ func Test_ConfigOptions_EscapedValues(t *testing.T) { // This simulates a value that is read from config.json. // After JSON unmarshaling, the value `"[\"api://...\"]"` becomes `["api://..."]` (backslashes consumed) // We need to re-escape it before sending to the pipeline so it's treated as a string, not an array + //nolint:gosec // G101: test data, not actual credentials env := map[string]string{ "AzureAd_TokenValidationParameters_ValidAudiences": "[\"api://e935a748-8b59-4c26-a59c-9bcc83f5ab57\"]", } @@ -73,6 +74,7 @@ func Test_ConfigOptions_EscapedValues(t *testing.T) { // After escaping, the value should have backslashes to prevent JSON parsing in the pipeline // The value becomes: [\"api://e935a748-8b59-4c26-a59c-9bcc83f5ab57\"] + //nolint:gosec // G101: test data, not actual credentials expectedVariables := map[string]string{ "AzureAd_TokenValidationParameters_ValidAudiences": "[\\\"api://e935a748-8b59-4c26-a59c-9bcc83f5ab57\\\"]", } diff --git a/cli/azd/pkg/project/project_utils.go b/cli/azd/pkg/project/project_utils.go index 67e6a83bcf9..82164e7fca7 100644 --- a/cli/azd/pkg/project/project_utils.go +++ b/cli/azd/pkg/project/project_utils.go @@ -95,13 +95,13 @@ func createDeployableZip(svc *ServiceConfig, root string) (string, error) { if err := rzip.CreateFromDirectory(root, zipFile, onZip); err != nil { // if we fail here just do our best to close things out and cleanup zipFile.Close() - os.Remove(zipFile.Name()) + os.Remove(zipFile.Name()) //nolint:gosec // G703: temp file cleanup return "", fmt.Errorf("creating deployable zip: %w", err) } if err := zipFile.Close(); err != nil { // may fail but, again, we'll do our best to cleanup here. - os.Remove(zipFile.Name()) + os.Remove(zipFile.Name()) //nolint:gosec // G703: temp file cleanup return "", err } diff --git a/cli/azd/pkg/project/service_target_dotnet_containerapp.go b/cli/azd/pkg/project/service_target_dotnet_containerapp.go index 582eea61292..bd3cdb5cebe 100644 --- a/cli/azd/pkg/project/service_target_dotnet_containerapp.go +++ b/cli/azd/pkg/project/service_target_dotnet_containerapp.go @@ -851,6 +851,7 @@ func compileBicep( if ext == ".bicep" { paramFilePath := strings.TrimSuffix(bicepModulePath, ext) + ".parameters.json" + //nolint:gosec // G703: paramFilePath derived from project config parametersBytes, err := os.ReadFile(paramFilePath) if err != nil { return result, fmt.Errorf("reading parameters file: %w", err) diff --git a/cli/azd/pkg/rzip/rzip.go b/cli/azd/pkg/rzip/rzip.go index acada008054..67c47c6745a 100644 --- a/cli/azd/pkg/rzip/rzip.go +++ b/cli/azd/pkg/rzip/rzip.go @@ -280,6 +280,7 @@ func ExtractTarGzToDirectory(artifactPath string, targetDirectory string) error case tar.TypeReg: // Create the file + //nolint:gosec // G703: filePath is validated earlier err = os.MkdirAll(filepath.Dir(filePath), os.ModePerm) if err != nil { return err diff --git a/cli/azd/pkg/tools/bicep/bicep.go b/cli/azd/pkg/tools/bicep/bicep.go index 083f0825a63..455a8703f23 100644 --- a/cli/azd/pkg/tools/bicep/bicep.go +++ b/cli/azd/pkg/tools/bicep/bicep.go @@ -76,6 +76,7 @@ func (cli *Cli) ensureInstalledOnce(ctx context.Context) error { func (cli *Cli) ensureInstalled(ctx context.Context) error { if override := os.Getenv("AZD_BICEP_TOOL_PATH"); override != "" { + //nolint:gosec // G706: env var in debug log log.Printf("using external bicep tool: %s", override) cli.path = override return nil @@ -213,7 +214,7 @@ func downloadBicep(ctx context.Context, transporter policy.Transporter, bicepVer } defer func() { _ = f.Close() - _ = os.Remove(f.Name()) + _ = os.Remove(f.Name()) //nolint:gosec // G703: temp file cleanup }() if _, err := io.Copy(f, resp.Body); err != nil { diff --git a/cli/azd/pkg/tools/docker/acceptance/acceptance_test.go b/cli/azd/pkg/tools/docker/acceptance/acceptance_test.go index fe8de3b3d2c..1ae7cdce616 100644 --- a/cli/azd/pkg/tools/docker/acceptance/acceptance_test.go +++ b/cli/azd/pkg/tools/docker/acceptance/acceptance_test.go @@ -150,7 +150,7 @@ func loginToAzureRegistry(t *testing.T, ctx context.Context, cli *docker.Cli, re // Get access token using managed identity /* #nosec G204 - Subprocess launched with a potential tainted input or cmd arguments */ - cmd := osexec.CommandContext(ctx, "az", "acr", "login", + cmd := osexec.CommandContext(ctx, "az", "acr", "login", //nolint:gosec // G702: args are hardcoded literals "--name", registryName, "--expose-token", "--query", "accessToken", diff --git a/cli/azd/pkg/tools/github/github.go b/cli/azd/pkg/tools/github/github.go index e04a983539e..03f0e6a5eb5 100644 --- a/cli/azd/pkg/tools/github/github.go +++ b/cli/azd/pkg/tools/github/github.go @@ -112,6 +112,7 @@ func (cli *Cli) EnsureInstalled(ctx context.Context) error { func (cli *Cli) ensureInstalled(ctx context.Context) error { if override := os.Getenv("AZD_GH_TOOL_PATH"); override != "" { + //nolint:gosec // G706: env var in debug log log.Printf("using external github cli tool: %s", override) cli.path = override cli.logVersion(ctx) @@ -612,6 +613,7 @@ func extractFromTar(src, dst string) (string, error) { // cspell: disable-next-line `Typeflag` is comming fron *tar.Header if fileHeader.Typeflag == tar.TypeReg && fileName == "gh" { filePath := filepath.Join(dst, fileName) + //nolint:gosec // G703: dst is a known install directory ghCliFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fileHeader.FileInfo().Mode()) if err != nil { return extractedAt, err @@ -706,7 +708,7 @@ func downloadGh( } defer func() { _ = compressedRelease.Close() - _ = os.Remove(compressedRelease.Name()) + _ = os.Remove(compressedRelease.Name()) //nolint:gosec // G703: temp file cleanup }() if _, err := io.Copy(compressedRelease, resp.Body); err != nil { diff --git a/cli/azd/pkg/tools/pack/pack.go b/cli/azd/pkg/tools/pack/pack.go index bb252a7a0c7..b71cfcbdd89 100644 --- a/cli/azd/pkg/tools/pack/pack.go +++ b/cli/azd/pkg/tools/pack/pack.go @@ -131,6 +131,7 @@ func (cli *Cli) EnsureInstalled(ctx context.Context) error { func (cli *Cli) ensureInstalled(ctx context.Context) error { if override := os.Getenv("AZD_PACK_TOOL_PATH"); override != "" { + //nolint:gosec // G706: env var in debug log log.Printf("using external pack tool: %s", override) cli.path = override return nil @@ -342,6 +343,7 @@ func extractFromTar( // cspell: disable-next-line `Typeflag` is comming fron *tar.Header if fileHeader.Typeflag == tar.TypeReg && fileName == "pack" { filePath := filepath.Join(out, fileName) + //nolint:gosec // G703: out is a known install directory packCliFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fileHeader.FileInfo().Mode()) if err != nil { return extractedAt, err @@ -422,7 +424,7 @@ func downloadPack( } defer func() { _ = compressedRelease.Close() - _ = os.Remove(compressedRelease.Name()) + _ = os.Remove(compressedRelease.Name()) //nolint:gosec // G703: temp file cleanup }() if _, err := io.Copy(compressedRelease, resp.Body); err != nil { diff --git a/cli/azd/pkg/ux/confirm.go b/cli/azd/pkg/ux/confirm.go index 7f4102894fa..fb49fbc2215 100644 --- a/cli/azd/pkg/ux/confirm.go +++ b/cli/azd/pkg/ux/confirm.go @@ -186,10 +186,10 @@ func (p *Confirm) Ask(ctx context.Context) (*bool, error) { // Render renders the Confirm component. func (p *Confirm) Render(printer Printer) error { - printer.Fprintf(output.WithHighLightFormat("? ")) + printer.Fprintf("%s", output.WithHighLightFormat("? ")) // Message - printer.Fprintf(BoldString("%s: ", p.options.Message)) + printer.Fprintf("%s", BoldString("%s: ", p.options.Message)) // Hint if !p.cancelled && !p.complete && p.options.Hint != "" { @@ -208,7 +208,7 @@ func (p *Confirm) Render(printer Printer) error { valueOutput = output.WithErrorFormat("(Cancelled)") } - printer.Fprintf(valueOutput) + printer.Fprintf("%s", valueOutput) p.cursorPosition = Ptr(printer.CursorPosition()) printer.Fprintln() diff --git a/cli/azd/pkg/ux/multi_select.go b/cli/azd/pkg/ux/multi_select.go index fede341bd02..5afbe74769d 100644 --- a/cli/azd/pkg/ux/multi_select.go +++ b/cli/azd/pkg/ux/multi_select.go @@ -405,14 +405,14 @@ func (p *MultiSelect) renderValidation(printer Printer) { } func (p *MultiSelect) renderMessage(printer Printer) { - printer.Fprintf(output.WithHighLightFormat("? ")) + printer.Fprintf("%s", output.WithHighLightFormat("? ")) // Message - printer.Fprintf(BoldString("%s: ", p.options.Message)) + printer.Fprintf("%s", BoldString("%s: ", p.options.Message)) // Cancelled if p.cancelled { - printer.Fprintf(output.WithErrorFormat("(Cancelled)")) + printer.Fprintf("%s", output.WithErrorFormat("(Cancelled)")) } // Selected Value(s) @@ -424,7 +424,7 @@ func (p *MultiSelect) renderMessage(printer Printer) { } rawValue := strings.Join(selectionValues, ", ") - printer.Fprintf(output.WithHighLightFormat(rawValue)) + printer.Fprintf("%s", output.WithHighLightFormat(rawValue)) } printer.Fprintln() @@ -436,9 +436,9 @@ func (p *MultiSelect) renderMessage(printer Printer) { if p.filter == "" { p.cursorPosition = Ptr(printer.CursorPosition()) - printer.Fprintf(output.WithGrayFormat("Type to filter list")) + printer.Fprintf("%s", output.WithGrayFormat("Type to filter list")) } else { - printer.Fprintf(p.filter) + printer.Fprintf("%s", p.filter) p.cursorPosition = Ptr(printer.CursorPosition()) } diff --git a/cli/azd/pkg/ux/prompt.go b/cli/azd/pkg/ux/prompt.go index 7fe4e1f1c3c..829948e5083 100644 --- a/cli/azd/pkg/ux/prompt.go +++ b/cli/azd/pkg/ux/prompt.go @@ -189,10 +189,10 @@ func (p *Prompt) Render(printer Printer) error { return nil } - printer.Fprintf(output.WithHighLightFormat("? ")) + printer.Fprintf("%s", output.WithHighLightFormat("? ")) // Message - printer.Fprintf(BoldString("%s: ", p.options.Message)) + printer.Fprintf("%s", BoldString("%s: ", p.options.Message)) // Cancelled if p.cancelled { @@ -213,7 +213,7 @@ func (p *Prompt) Render(printer Printer) error { // Placeholder if p.value == "" && p.options.PlaceHolder != "" { p.cursorPosition = Ptr(printer.CursorPosition()) - printer.Fprintf(output.WithGrayFormat(p.options.PlaceHolder)) + printer.Fprintf("%s", output.WithGrayFormat(p.options.PlaceHolder)) } // Value @@ -224,7 +224,7 @@ func (p *Prompt) Render(printer Printer) error { valueOutput = output.WithHighLightFormat(p.value) } - printer.Fprintf(valueOutput) + printer.Fprintf("%s", valueOutput) p.cursorPosition = Ptr(printer.CursorPosition()) } diff --git a/cli/azd/pkg/ux/select.go b/cli/azd/pkg/ux/select.go index 5668483db5b..0169be95486 100644 --- a/cli/azd/pkg/ux/select.go +++ b/cli/azd/pkg/ux/select.go @@ -337,20 +337,20 @@ func (p *Select) renderValidation(printer Printer) { } func (p *Select) renderMessage(printer Printer) { - printer.Fprintf(output.WithHighLightFormat("? ")) + printer.Fprintf("%s", output.WithHighLightFormat("? ")) // Message - printer.Fprintf(BoldString("%s: ", p.options.Message)) + printer.Fprintf("%s", BoldString("%s: ", p.options.Message)) // Cancelled if p.cancelled { - printer.Fprintf(output.WithErrorFormat("(Cancelled)")) + printer.Fprintf("%s", output.WithErrorFormat("(Cancelled)")) } // Selected Value if !p.cancelled && p.selectedChoice != nil { rawValue := p.selectedChoice.Label - printer.Fprintf(output.WithHighLightFormat(rawValue)) + printer.Fprintf("%s", output.WithHighLightFormat(rawValue)) } printer.Fprintln() @@ -362,9 +362,9 @@ func (p *Select) renderMessage(printer Printer) { if p.filter == "" { p.cursorPosition = Ptr(printer.CursorPosition()) - printer.Fprintf(output.WithGrayFormat("Type to filter list")) + printer.Fprintf("%s", output.WithGrayFormat("Type to filter list")) } else { - printer.Fprintf(p.filter) + printer.Fprintf("%s", p.filter) p.cursorPosition = Ptr(printer.CursorPosition()) } diff --git a/cli/azd/test/cmdrecord/cmdrecorder_test.go b/cli/azd/test/cmdrecord/cmdrecorder_test.go index f78427cbe8f..dfff91d2165 100644 --- a/cli/azd/test/cmdrecord/cmdrecorder_test.go +++ b/cli/azd/test/cmdrecord/cmdrecorder_test.go @@ -196,6 +196,6 @@ func TestPassthrough(t *testing.T) { } func runCmd(name string, args ...string) (string, error) { - output, err := exec.Command(name, args...).CombinedOutput() + output, err := exec.Command(name, args...).CombinedOutput() //nolint:gosec // G204: test helper with controlled inputs return string(output), err } diff --git a/cli/azd/test/internal/tfoidc/main.go b/cli/azd/test/internal/tfoidc/main.go index ecd9c89a3c5..22c38219ca4 100644 --- a/cli/azd/test/internal/tfoidc/main.go +++ b/cli/azd/test/internal/tfoidc/main.go @@ -176,7 +176,7 @@ func fetchOIDCToken(ctx context.Context) (string, error) { } tokenReq.Header.Set("Authorization", "Bearer "+os.Getenv("SYSTEM_ACCESSTOKEN")) - tokenRes, err := http.DefaultClient.Do(tokenReq) + tokenRes, err := http.DefaultClient.Do(tokenReq) //nolint:gosec // G704: URL is constructed from a trusted OIDC endpoint if err != nil { return "", err } diff --git a/cli/azd/tools/avmres/main.go b/cli/azd/tools/avmres/main.go index cfd06774f39..0c363776f4b 100644 --- a/cli/azd/tools/avmres/main.go +++ b/cli/azd/tools/avmres/main.go @@ -202,7 +202,7 @@ func fetchGithub( req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) } - resp, err := http.DefaultClient.Do(req) + resp, err := http.DefaultClient.Do(req) //nolint:gosec // G704: URL is a known GitHub API endpoint if err != nil { return nil, err } diff --git a/eng/pipelines/templates/steps/setup-go.yml b/eng/pipelines/templates/steps/setup-go.yml index cd4375a9b15..7e7bef39d4a 100644 --- a/eng/pipelines/templates/steps/setup-go.yml +++ b/eng/pipelines/templates/steps/setup-go.yml @@ -1,5 +1,5 @@ parameters: - GoVersion: 1.25.0 + GoVersion: 1.26.0 Condition: succeeded() steps: