Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 ./...
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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: |
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/link-check.yml
Original file line number Diff line number Diff line change
@@ -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"
6 changes: 4 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 ./...
Expand Down
30 changes: 30 additions & 0 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
4 changes: 3 additions & 1 deletion cmd/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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),
)
Expand Down
9 changes: 9 additions & 0 deletions cmd/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ func restoreOAuthCredentials(t *testing.T) {
origReadAppKey := readAppKey
origReadAppCredentials := readAppCredentials
origGenerateOAuthVerifier := generateOAuthVerifier
origGenerateOAuthState := generateOAuthState
origRefreshOAuthToken := refreshOAuthToken
t.Cleanup(func() {
personalAppKey = origPersonalAppKey
Expand All @@ -167,6 +168,7 @@ func restoreOAuthCredentials(t *testing.T) {
readAppKey = origReadAppKey
readAppCredentials = origReadAppCredentials
generateOAuthVerifier = origGenerateOAuthVerifier
generateOAuthState = origGenerateOAuthState
refreshOAuthToken = origRefreshOAuthToken
})
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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)
}
Expand Down
6 changes: 3 additions & 3 deletions tools/gen-docs/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -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 {
Expand Down
Loading