From 97352a572cec9f6687d155d3748cafc4a2e09cf0 Mon Sep 17 00:00:00 2001 From: Andrey Markelov Date: Sat, 4 Jul 2026 19:53:28 -0700 Subject: [PATCH] Add Scorecard, link checks, golangci-lint, cache paths, and OAuth state fix Replace standalone Staticcheck with a narrow golangci-lint v2 config (ineffassign, staticcheck, unused). Add OSSF Scorecard and Markdown link check workflows. Set explicit go.sum cache-dependency-path on all actions/setup-go steps. Generate a non-constant OAuth state to resolve the CodeQL go/constant-oauth2-state alert. --- .github/workflows/ci.yml | 14 +++++++++++--- .github/workflows/codeql.yml | 1 + .github/workflows/link-check.yml | 26 ++++++++++++++++++++++++++ .github/workflows/release.yml | 6 ++++-- .github/workflows/scorecard.yml | 30 ++++++++++++++++++++++++++++++ .golangci.yml | 15 +++++++++++++++ CHANGELOG.md | 7 +++++++ cmd/auth.go | 4 +++- cmd/auth_test.go | 9 +++++++++ tools/gen-docs/main.go | 6 +++--- 10 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/link-check.yml create mode 100644 .github/workflows/scorecard.yml create mode 100644 .golangci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c65a3ca0..b9ed44fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,7 @@ jobs: - uses: actions/setup-go@v6 with: go-version: "1.25" + cache-dependency-path: go.sum - run: go install golang.org/x/vuln/cmd/govulncheck@latest - run: govulncheck ./... - run: go vet ./... @@ -36,18 +37,21 @@ jobs: - uses: actions/setup-go@v6 with: go-version: "1.25" + cache-dependency-path: go.sum - run: go test -race ./... lint: - name: Staticcheck + name: golangci-lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - uses: actions/setup-go@v6 with: go-version: "1.25" - - run: go install honnef.co/go/tools/cmd/staticcheck@v0.7.0 - - run: staticcheck ./... + cache-dependency-path: go.sum + - uses: golangci/golangci-lint-action@v9 + with: + version: v2.12.2 workflow-lint: name: GitHub Actions workflow lint @@ -57,6 +61,7 @@ jobs: - uses: actions/setup-go@v6 with: go-version: "1.25" + cache-dependency-path: go.sum - run: go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 - run: actionlint @@ -68,6 +73,7 @@ jobs: - uses: actions/setup-go@v6 with: go-version: "1.25" + cache-dependency-path: go.sum - run: go install golang.org/x/vuln/cmd/govulncheck@latest - run: govulncheck ./... - run: ./build.sh @@ -83,6 +89,7 @@ jobs: - uses: actions/setup-go@v6 with: go-version: "1.25" + cache-dependency-path: go.sum - run: go run ./tools/gen-docs - run: go run ./tools/gen-json-schemas - run: git diff --exit-code docs/commands @@ -98,6 +105,7 @@ jobs: - uses: actions/setup-go@v6 with: go-version: "1.25" + cache-dependency-path: go.sum - name: Build Windows binary shell: pwsh run: | diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index cee84921..fc4883d3 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -23,6 +23,7 @@ jobs: - uses: actions/setup-go@v6 with: go-version: "1.25" + cache-dependency-path: go.sum - name: Initialize CodeQL uses: github/codeql-action/init@v4 diff --git a/.github/workflows/link-check.yml b/.github/workflows/link-check.yml new file mode 100644 index 00000000..65d4887d --- /dev/null +++ b/.github/workflows/link-check.yml @@ -0,0 +1,26 @@ +name: Link Check + +on: + schedule: + - cron: "23 9 * * 2" + workflow_dispatch: + +permissions: + contents: read + +jobs: + links: + name: Markdown links + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v7 + - name: Check Markdown links + uses: lycheeverse/lychee-action@v2 + with: + args: >- + --verbose + --no-progress + --exclude "https://github.com/dropbox/dbxcli/releases/download/vX.Y.Z/.*" + README.md + "docs/**/*.md" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 36b33fe4..07697222 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,14 +22,16 @@ jobs: - uses: actions/setup-go@v6 with: go-version: "1.25" + cache-dependency-path: go.sum - name: Set release version id: version shell: bash run: echo "asset_version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" - run: go install golang.org/x/vuln/cmd/govulncheck@latest - run: govulncheck ./... - - run: go install honnef.co/go/tools/cmd/staticcheck@v0.7.0 - - run: staticcheck ./... + - uses: golangci/golangci-lint-action@v9 + with: + version: v2.12.2 - run: go vet ./... - run: go test ./... - run: go build ./... diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 00000000..56389db3 --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,30 @@ +name: Scorecard + +on: + schedule: + - cron: "17 8 * * 3" + workflow_dispatch: + +permissions: + contents: read + +jobs: + scorecard: + name: OSSF Scorecard + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - name: Run Scorecard + uses: ossf/scorecard-action@v2.4.3 + with: + results_file: scorecard-results.json + results_format: json + publish_results: false + - name: Upload Scorecard results + uses: actions/upload-artifact@v5 + with: + name: scorecard-results + path: scorecard-results.json + retention-days: 14 diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..c3d81ff6 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,15 @@ +version: "2" + +run: + timeout: 5m + +linters: + default: none + enable: + - ineffassign + - staticcheck + - unused + +issues: + max-issues-per-linter: 0 + max-same-issues: 0 diff --git a/CHANGELOG.md b/CHANGELOG.md index b09856e4..72530d2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ [Full Changelog](https://github.com/dropbox/dbxcli/compare/v3.6.0...HEAD) +**Infrastructure:** + +- Added scheduled/manual OSSF Scorecard scanning without public Scorecard API publishing. +- Added scheduled/manual non-blocking Markdown link checks for README and docs. +- Replaced standalone Staticcheck workflow steps with a narrow `golangci-lint` configuration. +- Added explicit `go.sum` cache dependency paths for `actions/setup-go`. + ## [v3.6.0](https://github.com/dropbox/dbxcli/tree/v3.6.0) (2026-07-02) [Full Changelog](https://github.com/dropbox/dbxcli/compare/v3.5.1...v3.6.0) diff --git a/cmd/auth.go b/cmd/auth.go index 05ef5244..22d4317b 100644 --- a/cmd/auth.go +++ b/cmd/auth.go @@ -96,6 +96,7 @@ var readAuthorizationCode = func() (string, error) { } var generateOAuthVerifier = oauth2.GenerateVerifier +var generateOAuthState = oauth2.GenerateVerifier var exchangeAuthorizationCode = func(ctx context.Context, conf *oauth2.Config, code string, verifier string) (*oauth2.Token, error) { return conf.Exchange(ctx, code, oauth2.VerifierOption(verifier)) @@ -363,7 +364,8 @@ func requestAccessCredential(tokType string, domain string) (storedCredential, e conf := oauthConfig(tokType, domain) verifier := generateOAuthVerifier() - authCodeURL := conf.AuthCodeURL("state", + state := generateOAuthState() + authCodeURL := conf.AuthCodeURL(state, oauth2.S256ChallengeOption(verifier), oauth2.SetAuthURLParam(tokenAccessTypeParam, tokenAccessTypeOffline), ) diff --git a/cmd/auth_test.go b/cmd/auth_test.go index 1db0b5b2..429afa35 100644 --- a/cmd/auth_test.go +++ b/cmd/auth_test.go @@ -159,6 +159,7 @@ func restoreOAuthCredentials(t *testing.T) { origReadAppKey := readAppKey origReadAppCredentials := readAppCredentials origGenerateOAuthVerifier := generateOAuthVerifier + origGenerateOAuthState := generateOAuthState origRefreshOAuthToken := refreshOAuthToken t.Cleanup(func() { personalAppKey = origPersonalAppKey @@ -167,6 +168,7 @@ func restoreOAuthCredentials(t *testing.T) { readAppKey = origReadAppKey readAppCredentials = origReadAppCredentials generateOAuthVerifier = origGenerateOAuthVerifier + generateOAuthState = origGenerateOAuthState refreshOAuthToken = origRefreshOAuthToken }) } @@ -674,9 +676,13 @@ func TestRequestAccessTokenUsesPKCEOfflineAuthURL(t *testing.T) { }) const verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" + const state = "test-oauth-state" generateOAuthVerifier = func() string { return verifier } + generateOAuthState = func() string { + return state + } readAuthorizationCode = func() (string, error) { return "auth-code", nil } @@ -702,6 +708,9 @@ func TestRequestAccessTokenUsesPKCEOfflineAuthURL(t *testing.T) { if !strings.Contains(out, "token_access_type=offline") { t.Fatalf("expected offline token access type in auth URL, got %q", out) } + if !strings.Contains(out, "state=test-oauth-state") { + t.Fatalf("expected generated OAuth state in auth URL, got %q", out) + } if !strings.Contains(out, "code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM") { t.Fatalf("expected PKCE code challenge in auth URL, got %q", out) } diff --git a/tools/gen-docs/main.go b/tools/gen-docs/main.go index c541dc87..7698d541 100644 --- a/tools/gen-docs/main.go +++ b/tools/gen-docs/main.go @@ -139,7 +139,7 @@ func commandMetadataSection(command *cobra.Command) []byte { manifest := cmd.CommandManifestFor(command) var buf bytes.Buffer buf.WriteString("### Command metadata\n\n") - buf.WriteString(fmt.Sprintf("* Structured JSON output: %s\n", yesNo(manifest.SupportsStructuredOutput))) + fmt.Fprintf(&buf, "* Structured JSON output: %s\n", yesNo(manifest.SupportsStructuredOutput)) buf.WriteString("* JSON help manifest: yes\n") buf.WriteString("* Manifest version: `" + manifest.ManifestVersion + "`\n") buf.WriteString("* Auth modes: " + markdownValueList(manifest.AuthModes) + "\n") @@ -178,7 +178,7 @@ func commandMetadataSection(command *cobra.Command) []byte { if len(arg.EnumValues) > 0 { enumValues = "; values: " + markdownValueList(arg.EnumValues) } - buf.WriteString(fmt.Sprintf("`%s` (%s, %s%s%s%s)", arg.Name, requirement, arg.ValueKind, variadic, streamDash, enumValues)) + fmt.Fprintf(&buf, "`%s` (%s, %s%s%s%s)", arg.Name, requirement, arg.ValueKind, variadic, streamDash, enumValues) } buf.WriteString("\n") } @@ -211,7 +211,7 @@ func commandMetadataSection(command *cobra.Command) []byte { if note, ok := stdinStdoutNotes[relativeCommandPath(command)]; ok { buf.WriteString("* Stdin/stdout behavior: " + note + "\n") } else if manifest.StdinStdout.ReadsStdin || manifest.StdinStdout.WritesBinaryStdout { - buf.WriteString(fmt.Sprintf("* Stdin/stdout behavior: reads_stdin=%t, writes_binary_stdout=%t\n", manifest.StdinStdout.ReadsStdin, manifest.StdinStdout.WritesBinaryStdout)) + fmt.Fprintf(&buf, "* Stdin/stdout behavior: reads_stdin=%t, writes_binary_stdout=%t\n", manifest.StdinStdout.ReadsStdin, manifest.StdinStdout.WritesBinaryStdout) } if level := manifest.DestructiveLevel; level != destructiveLevelNone {