diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2a7fbb34..dd69b2065 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -325,7 +325,7 @@ jobs: # Install pinned gosec using the job's existing setup-go. # The securego/gosec Docker action bundles its own Go toolchain which # cannot satisfy the module's go directive, causing a toolchain mismatch. - go install github.com/securego/gosec/v2/cmd/gosec@v2.26.1 + go install github.com/securego/gosec/v2/cmd/gosec@v2.28.0 # Multi-module repo: each ./... only walks the current module so scanning root # alone silently misses pkg/ and providers/*. Mirror the govulncheck per-module # loop, collect per-module SARIF, then merge for the upload step. diff --git a/internal/analytics/postgres_analytics.go b/internal/analytics/postgres_analytics.go index 5b117bdd4..a6a1cc97b 100644 --- a/internal/analytics/postgres_analytics.go +++ b/internal/analytics/postgres_analytics.go @@ -221,8 +221,6 @@ func (s *PostgresAnalyticsStore) BulkInsertSnapshots(ctx context.Context, snapsh func (s *PostgresAnalyticsStore) QuerySavings(ctx context.Context, req QueryRequest) ([]SavingsSnapshot, error) { accountClause, args := accountFilterClause(req.AccountUUIDs, req.AccountExternalIDsByProvider, []any{req.StartDate, req.EndDate}) - // #nosec G201 — accountClause references only parameter placeholders built - // internally; the optional provider/service filters below are also bound. query := ` SELECT id, account_id, cloud_account_id, timestamp, provider, service, region, commitment_type, total_commitment, total_usage, total_savings, @@ -304,7 +302,6 @@ func (s *PostgresAnalyticsStore) QueryMonthlyTotals(ctx context.Context, account // the INTERVAL '1 month' * N off-by-one (M1). accountClause, args := accountFilterClause(accountUUIDs, accountExternalIDsByProvider, []any{months}) - // #nosec G201 — accountClause uses only internally-built placeholders. query := ` SELECT month, account_id, cloud_account_id, provider, service, total_savings, avg_coverage, snapshot_count FROM monthly_savings_summary @@ -352,7 +349,6 @@ func (s *PostgresAnalyticsStore) QueryByProvider(ctx context.Context, accountUUI // timestamp and the outer query averages the instant totals over time; a // flat AVG would report the mean per-row run-rate instead of the bucket // total (COR-02). - // #nosec G201 — accountClause uses only internally-built placeholders. query := ` SELECT provider, service, AVG(ts_savings) as total_savings, AVG(ts_coverage) as avg_coverage FROM ( @@ -408,7 +404,6 @@ func (s *PostgresAnalyticsStore) QueryByService(ctx context.Context, accountUUID // Same H5 nested rollup as QueryByProvider: a (service, region) bucket // spans accounts and commitment types at the same timestamp, so SUM the // rows per timestamp first, then AVG the instant totals over time (COR-02). - // #nosec G201 — accountClause / providerClause use only internally-built placeholders. query := fmt.Sprintf(` SELECT service, region, AVG(ts_savings) as total_savings, AVG(ts_coverage) as avg_coverage FROM ( diff --git a/internal/api/analytics_postgres.go b/internal/api/analytics_postgres.go index a66619cf7..81fdbf4b1 100644 --- a/internal/api/analytics_postgres.go +++ b/internal/api/analytics_postgres.go @@ -165,9 +165,6 @@ func (c *PostgresAnalyticsClient) QueryHistory( // breakdowns without a second trip to the DB. The account predicate is the // shared dual-column clause (see accountFilterClause); the optional provider // predicate mirrors QueryByService (parameter-bound, "" = no filter). - // - // #nosec G201 — `unit` is allowlisted by intervalToTruncUnit above and the - // account / provider clauses are parameter-bound (no user input interpolated). accountClause, args := accountFilterClause(accountUUIDs, accountExternalIDsByProvider, []any{start, end}) providerClause := "" if provider != "" { @@ -269,9 +266,6 @@ func (c *PostgresAnalyticsClient) QueryBreakdown( // Dual-column account predicate: see accountFilterClause / QueryHistory for // rationale (issue #701/#498/#866). - // - // #nosec G201 — `column` is allowlisted by dimensionToColumn above and the - // account clause is parameter-bound (no user input interpolated). accountClause, args := accountFilterClause(accountUUIDs, accountExternalIDsByProvider, []any{start, end}) query := fmt.Sprintf(` SELECT %s AS bucket, diff --git a/internal/credentials/resolver.go b/internal/credentials/resolver.go index a9d6d1196..316c33e39 100644 --- a/internal/credentials/resolver.go +++ b/internal/credentials/resolver.go @@ -24,7 +24,7 @@ import ( // Credential type constants used as credType in CredentialStore. const ( CredTypeAWSAccessKeys = "aws_access_keys" - CredTypeAzureClientSecret = "azure_client_secret" // #nosec G101 -- credential type name constant; not a credential value + CredTypeAzureClientSecret = "azure_client_secret" CredTypeGCPServiceAccount = "gcp_service_account" // #nosec G101 -- credential type name constant; not a credential value CredTypeGCPWIFConfig = "gcp_workload_identity_config" // #nosec G101 -- credential type name constant; not a credential value ) diff --git a/internal/deploy/frontend.go b/internal/deploy/frontend.go index 4089c3e94..bfb69dfb9 100644 --- a/internal/deploy/frontend.go +++ b/internal/deploy/frontend.go @@ -102,7 +102,7 @@ func (s *FrontendService) uploadFile(ctx context.Context, distDir, bucketName, p } key := strings.ReplaceAll(relPath, string(filepath.Separator), "/") - content, err := os.ReadFile(path) // #nosec G304,G122 -- path is from WalkDir callback, symlinks rejected by caller; always a regular file descendant of distDir (npm build output under operator control) + content, err := os.ReadFile(path) // #nosec G304 -- path is from WalkDir callback, symlinks rejected by caller; always a regular file descendant of distDir (npm build output under operator control) if err != nil { return fmt.Errorf("failed to read %s: %w", path, err) } diff --git a/internal/email/templates.go b/internal/email/templates.go index 1328fbc62..89e0a6cfe 100644 --- a/internal/email/templates.go +++ b/internal/email/templates.go @@ -137,7 +137,7 @@ Review failed purchases: This is an automated message from CUDly. ` -const passwordResetTemplate = "" + // #nosec G101 -- email template; "password" in body text is email copy, not a hardcoded credential +const passwordResetTemplate = "" + `CUDly - Password Reset Request ============================== @@ -161,7 +161,7 @@ This is an automated message from CUDly. // Modeled on purchaseApprovalRequestHTMLTemplate (line 367) — inline styles // because most email clients (Outlook, mobile Gmail) ignore class-based CSS. // Issue #355. -const passwordResetHTMLTemplate = "" + // #nosec G101 -- HTML email template; "password" in body text is email copy, not a hardcoded credential +const passwordResetHTMLTemplate = "" + ` CUDly - Password Reset Request