Add OSV-Scanner-based security workflow#362
Conversation
Single workflow, single job, three triggers:
- pull_request to main: fails on CVSS >= 7 findings only
(HIGH/CRITICAL block merges; MED/LOW visible but non-blocking)
- cron weekly (Sunday 00:00 UTC): reports ALL findings via email
- workflow_dispatch: behaves like cron
Mirrors the JDBC driver's security workflow (databricks-jdbc#1460)
adapted for Go:
- Reads go.mod natively via OSV-Scanner --lockfile (no SBOM step)
- Reuses the existing ./.github/actions/setup-jfrog composite action
for the GOPROXY OIDC token dance
- Suppressions in osv-scanner.toml ([[IgnoredVulns]] schema)
The workflow is not yet wired into branch protection. Day-one runs
against current main will surface 5 HIGH findings (golang-jwt/jwt/v5,
apache/thrift, golang.org/x/crypto, golang.org/x/oauth2,
google.golang.org/protobuf) that will be cleared by a follow-up
dep-bump PR.
Co-authored-by: Isaac
Signed-off-by: Vikrant Puppala <vikrant.puppala@databricks.com>
|
📄 .github/workflows/securityScan.yml (file-level) ▎ 📄 .github/workflows/securityScan.yml (Line 89) ▎ |
…eps (#368) ## Summary Brings the Go SQL driver to a state where **OSV-Scanner v2.3.8 reports zero non-stdlib findings** against `go.mod` — every CVE in dependency code is patched. Combined with [#363](#363) (already merged), this closes out the security-bump work. **Status**: companion to [#362](#362) (the OSV-Scanner workflow PR). ## go directive `go 1.20` → `go 1.25.0`. This is the minimum required by `github.com/apache/thrift@v0.23.0` (the version patched for `GHSA-wf45-q9ch-q8gh`), which declares `go 1.25` in its own `go.mod`. We don't pin to a patch (`.10`); the patch is determined by whatever toolchain the build environment uses, and `GOTOOLCHAIN=auto` (the default since Go 1.21) downloads the latest patched toolchain automatically. ### Why Go 1.25 (and not 1.23 or 1.24) Per [go.dev/doc/devel/release](https://go.dev/doc/devel/release), only the **latest two major releases** receive security patches. As of May 2026 that's **Go 1.25 (Active LTS)** and **Go 1.26**. The actual binding constraint is `apache/thrift@v0.23.0` requiring `go 1.25`. Without thrift 0.23, we cannot clear `GHSA-wf45-q9ch-q8gh` (HIGH 7.5) — there is no backport. So we cannot stay on `go 1.23`/`1.24` AND clear the HIGH CVE; pick one. We picked clearing the CVE. The CVE itself is in `TFramedTransport`, which this driver does not use (we use `THttpClient`), so it isn't reachable in practice — but customer scanners running against our lockfile WILL flag it. Patching the dep, rather than suppressing the finding, is the customer-friendly choice. ### Stdlib advisories visible in OSV OSV-Scanner reads the `go` directive literally and reports stdlib advisories targeting any version below the patched one. `go 1.25.0` triggers ~34 stdlib advisories (each fixed in some 1.25.x patch). **These are a lockfile-scanner artifact, not customer-reachable bugs:** - The driver doesn't ship a binary; consumers compile against our `go.mod` and link Go's stdlib at THEIR toolchain version. - `GOTOOLCHAIN=auto` (default since Go 1.21) downloads the latest patched toolchain — so a consumer's compiled driver picks up the 1.25.10 stdlib in practice. - A `go 1.25.10` directive would silence the scanner but doesn't change runtime behavior. We chose the minimum required directive (`1.25.0`) rather than over-pinning (`1.25.10`) to match the convention used by `grpc-go`, `golang.org/x/net`, and `aws-sdk-go-v2`. ## Runtime dep bumps | Package | From | To | Why | |---|---|---|---| | `github.com/apache/thrift` | `v0.17.0` | **`v0.23.0`** | `GHSA-wf45-q9ch-q8gh` (HIGH 7.5) | | `golang.org/x/oauth2` | `v0.7.0` | **`v0.27.0`** | `GO-2025-3488` / `GHSA-6v2p-p543-phr9` | | `golang.org/x/crypto` | `v0.31.0` | **`v0.52.0`** | `GO-2025-3487`, `GO-2026-5005..5033` (13 SSH-agent constraint advisories) | | `golang.org/x/sys` | `v0.30.0` | **`v0.45.0`** | required by `x/crypto v0.52.0` | Plus auto-bumped transitives via `go mod tidy`. ## Go 1.25 vet findings Go 1.25's `go vet` (run as part of `go test`) added `non-constant format string in call to ...Printf-like(...)`. Three latent footguns: - `internal/rows/arrowbased/arrowRows.go:224` — `Msgf(errFn(...))` → `Msg(...)` - `internal/rows/arrowbased/arrowRows.go:697` — `errors.Errorf(errFn(...))` → `errors.New(...)` - `internal/rows/rowscanner/resultPageIterator.go:300` — `Msgf(errFn(...))` → `Msg(...)` None had format verbs. All would have format-injected if the input ever contained `%`. ## CI matrix `[1.20.x]` → `['1.24.x', '1.25.x', '1.26.x']`. Matches Go's release support window (only 1.25/1.26 currently receive security patches; 1.24 included for one cycle of overlap). Lint Go version: `1.20.x` → `1.25.x`. `golangci-lint`: `v1.51` → `v1.61` (v1.51 chokes on `go 1.25` directives). ## OSV-Scanner result ``` Before: 40 findings (1 HIGH thrift + 39 unrated stdlib) After: ~34 unrated stdlib advisories (lockfile-artifact, runtime-fixed by toolchain) 0 dependency-code CVEs ``` ## Test plan - [x] `go build ./...` clean on Go 1.25.10 toolchain - [x] `go test ./...` passes (full unit suite) - [x] `go mod tidy` produces no further changes - [x] OSV-Scanner v2.3.8 confirms zero non-stdlib findings - [ ] CI green across `[1.24.x, 1.25.x, 1.26.x]` matrix ## Customer impact | Surface | Change | Impact | |---|---|---| | `go` directive | `1.20` → `1.25.0` | Users compiling need Go ≥ 1.25.0. With `GOTOOLCHAIN=auto` (default), Go auto-downloads — most users see no friction. `GOTOOLCHAIN=local` users on Go < 1.25.0 get a compile error pointing to update. | | Runtime behavior | None | The three vet-fix source changes are equivalent rewrites of error/log calls. | | Public API | Unchanged | No exported function signatures, types, or constants changed. | Anyone on Go < 1.25 is already running upstream-unsupported Go. This pull request was AI-assisted by Isaac. --------- Signed-off-by: Vikrant Puppala <vikrant.puppala@databricks.com>
Addresses both [MAJOR] findings from PR review: 1. Empty max_severity bypassed the CVSS>=7 gate (fail-open). OSV emits max_severity="" for scoreless advisory groups (GHSA-only, GO-xxxx, MAL-* malware). jq's `//` only coalesces null/false, so "" passed through and `"" | tonumber? // 0` scored it 0 -- a real HIGH could land with high_count=0 and turn the gate green. Fix: resolve severity as group max_severity -> else max CVSS across the group's vulnerabilities' .severities[].score -> else "UNKNOWN" sentinel. UNKNOWN is counted as BLOCKING (fail closed), never scored 0. Uses `try (x|tonumber) catch null` (not `tonumber?`, which yields EMPTY and would silently drop the whole finding row inside an `as` binding). 2. Scanner errors / partial writes silently passed the gate (fail-open). `|| true` discarded osv-scanner's exit code and the only guard was a zero-byte check, which a truncated-but-non-empty JSON defeats. Fix: capture the exit code, tolerate only 0 (clean) and 1 (findings present), fail closed on any other code; then validate the output is parseable JSON with a .results array before deriving counts. Also default counts and fail closed if they don't resolve to integers. Verified locally: synthetic OSV output with (9.8 scored / 7.5 empty-group- but-scored-vuln / scoreless-malware) now yields TOTAL=3 HIGH=3 UNKNOWN=1; clean scan yields 0/0; partial JSON is rejected. YAML + bash syntax check. Co-authored-by: Isaac Signed-off-by: Vikrant Puppala <vikrant.puppala@databricks.com>
#368 raised the go.mod directive to `go 1.25.0`, but this workflow still pinned setup-go to 1.20.x. osv-scanner shells out to govulncheck, which loads the module with the installed toolchain; Go 1.20 can't parse a `go 1.25.0` directive ("invalid go version '1.25.0': must match format 1.23"), so the code-analysis pass degrades and every finding comes back unscored. The new fail-closed gate then (correctly) blocks on ~40 UNKNOWN stdlib findings. Aligning the scan toolchain with the go.mod floor fixes it. Co-authored-by: Isaac Signed-off-by: Vikrant Puppala <vikrant.puppala@databricks.com>
…s openpgp Three related changes to get the OSV-Scanner gate green and clean: 1. Remove the email delivery steps (Compose email body / Send Email). Notification will be handled by a separate cross-repo action that collates findings from all driver repos into a single digest. The weekly cron still runs, still fails red on any finding, and now uploads osv-out.json + all-findings.json as artifacts for the collator to consume. Drops the SMTP_USERNAME/SMTP_PASSWORD/EMAIL_RECIPIENTS secret usage from this workflow. 2. Bump the go.mod directive 1.25.0 -> 1.25.12. The fail-closed gate fix correctly surfaced ~39 scoreless stdlib GO-advisories that were silently passing before; all are fixed in a 1.25.x patch (highest: GO-2026-5856 fixed in 1.25.12). Declaring the patched floor clears them from the source scan. GOTOOLCHAIN=auto delivers the patch to consumers. 3. Suppress GO-2026-5932 (golang.org/x/crypto/openpgp) with an EXPIRING ignore. openpgp is "unmaintained, unsafe by design" with no fixed version ever (introduced:0, no fix event). We use x/crypto's chacha20poly1305/pbkdf2/cryptobyte but never openpgp (verified via `go list -deps ./...`), so it's unreachable in our build. Uses `ignoreUntil = 2027-01-02` so the suppression is not permanent -- it lapses in ~6 months and forces a re-review. Establishes the convention (documented in the toml header) that every IgnoredVulns entry carries an expiry. Local verification (osv-scanner v2.3.8, Go 1.26): scan returns 0 findings; GO-2026-5932 filtered with the documented reason. Co-authored-by: Isaac Signed-off-by: Vikrant Puppala <vikrant.puppala@databricks.com>
setup-go '1.25.x' resolves to the runner's cached patch (1.25.11), which is below the go.mod directive (1.25.12). On protected runners (GOTOOLCHAIN=local) that fails to build/scan. Pin go-version to the exact directive patch in both go.yml (lint + the 1.25 matrix entry; 1.26.x stays floating since any 1.26 >= the floor) and securityScan.yml. Bump these in lockstep with the go.mod directive patch. Co-authored-by: Isaac Signed-off-by: Vikrant Puppala <vikrant.puppala@databricks.com>
…t floor Supersedes the earlier 1.25.12 directive bump / exact-patch CI pin, which inflated the customer-facing floor purely to satisfy the scanner and created a per-patch maintenance treadmill (CI setup-go 1.25.x lags the directive on GOTOOLCHAIN=local protected runners). Instead: - Revert go.mod directive to the honest floor `go 1.25.0`. - Revert CI setup-go back to floating `1.25.x` in go.yml + securityScan.yml (no exact-patch pin, no treadmill). - Change the gate: scoreless (UNKNOWN) findings are split by package. stdlib UNKNOWNs are REPORT-ONLY -- they are Go stdlib advisories flagged against the directive floor and are delivered to consumers via GOTOOLCHAIN patch auto-download, not a real exposure of the module. Non-stdlib UNKNOWNs STILL BLOCK, preserving the malware/GHSA-only fail-closed protection the review asked for. Each finding carries an `is_stdlib` flag; gate = (CVSS>=7) OR (UNKNOWN and not is_stdlib). The GO-2026-5932 (x/crypto/openpgp) suppression stays: it is third-party and scoreless, so it would otherwise block; the expiring ignore remains. Verified locally (osv-scanner v2.3.8): real scan -> 39 stdlib UNKNOWNs, all report-only, 0 blocking. Synthetic mix -> third-party malware + CVSS 9.1 block, stdlib does not. Co-authored-by: Isaac Signed-off-by: Vikrant Puppala <vikrant.puppala@databricks.com>
|
Thanks for the review — both 1. Empty
|
Summary
.github/workflows/securityScan.yml— single workflow, single job, three triggers (PR / weekly cron / manual). PR runs fail on CVSS ≥ 7 only; weekly runs report all findings and email the team.osv-scanner.toml— empty suppressions file (populate iteratively as real false positives surface)../.github/actions/setup-jfrogcomposite action — no duplicate OIDC-token logic.Mirrors the JDBC driver's workflow (databricks-jdbc#1460), adapted for Go: reads
go.modnatively via OSV-Scanner (no separate SBOM tool needed).Day-one results
The workflow is not yet wired into branch protection, so its first PR-time runs are advisory. A dry-run against current
mainsurfaces:golang-jwt/jwt/v5@5.2.1,apache/thrift@0.17.0,golang.org/x/crypto@0.31.0,golang.org/x/oauth2@0.7.0,google.golang.org/protobuf@1.28.1stdlib@1.20.99advisories — addressed by bumping the Go toolchain)All are legitimate findings, not false positives. A follow-up dep-bump PR will clear them. Once that's green, branch protection can be flipped to require this check.
Test plan
go.mod— produces expected findingsworkflow_dispatchafter merge exercises the weekly pathSMTP_USERNAME,SMTP_PASSWORD,EMAIL_RECIPIENTS) wired in repo settings before the first scheduled runThis pull request was AI-assisted by Isaac.