chore: update golangci-lint config and fix lint issues (#542)

* chore: update golangci-lint config and fix lint issues
This commit is contained in:
Roger
2026-04-04 16:26:07 +08:00
committed by GitHub
parent e35907de6f
commit 92053b85b0
14 changed files with 116 additions and 138 deletions
+32 -66
View File
@@ -10,7 +10,7 @@ run:
linters:
default: none
enable:
# Default tier
# Default tier — must-have for any Go project
- errcheck
- govet
- staticcheck
@@ -21,11 +21,18 @@ linters:
- errorlint
- gosec
- sqlclosecheck
- nilerr
- bodyclose
- durationcheck
- errchkjson
- exhaustive
- forcetypeassert
# Code quality
- depguard
- dogsled
- dupl
- dupword
- errname
- funlen
- gocheckcompilerdirectives
@@ -35,20 +42,24 @@ linters:
- godox
- goprintffuncname
- lll
- mirror
- misspell
- nakedret
- predeclared
- revive
- testifylint
- unconvert
- unparam
- usestdlibvars
- wastedassign
- whitespace
# Complexity
- gocognit
- nestif
# Note: copyloopvar, intrange, modernize, perfsprint require Go 1.22+
# They will be enabled when Go version constraint is lifted
# They will be enabled when Go version constraint is lifted.
settings:
depguard:
@@ -61,6 +72,8 @@ linters:
desc: Deprecated since Go 1.16. Use io and os packages instead.
- pkg: "github.com/instana/testify"
desc: Use github.com/stretchr/testify instead.
exhaustive:
default-signifies-exhaustive: true
dupl:
threshold: 100
funlen:
@@ -82,17 +95,15 @@ linters:
disabled-checks:
- dupImport
- hugeParam
- rangeValCopy
- ifElseChain
- octalLiteral
- rangeValCopy # keychainbreaker structs are large by design
- unnamedResult # crypto functions returning (key, iv) are clear without names
- whyNoLint
- singleCaseSwitch
- exitAfterDefer
- commentedOutCode
lll:
line-length: 140
gocognit:
min-complexity: 30
nestif:
min-complexity: 5
godox:
keywords:
- FIXME
@@ -103,17 +114,17 @@ linters:
asserts: false
gosec:
excludes:
- G101
- G104
- G304
- G401
- G405
- G501
- G502
- G505
- G115
- G117
- G204
- G101 # hardcoded credentials — false positives on const names
- G115 # integer overflow on conversion — false positives on safe narrowing
- G117 # struct field matches secret pattern — false positive on Password fields
- G204 # exec.Command with variable — required for macOS `security` command
- G304 # file inclusion via variable — required for dynamic browser paths
- G401 # weak crypto SHA1 — required for Chromium PBKDF2 key derivation
- G402 # TLS MinVersion — not applicable (no TLS in this tool)
- G405 # weak crypto DES — required for Firefox 3DES decryption
- G501 # blocklisted import crypto/md5 — not used, keep for safety
- G502 # blocklisted import crypto/des — required for Firefox decryption
- G505 # blocklisted import crypto/sha1 — required for PBKDF2
errcheck:
check-type-assertions: true
exclude-functions:
@@ -127,22 +138,13 @@ linters:
rules:
- name: indent-error-flow
- name: unexported-return
disabled: true
- name: unused-parameter
disabled: true
- name: package-comments
disabled: true
- name: exported
disabled: true
staticcheck:
checks:
- "all"
- "-ST1000"
- "-ST1003"
- "-ST1016"
- "-ST1020"
- "-ST1021"
- "-ST1022"
- "-ST1000" # package comment — not a public library
- "-ST1003" # naming convention — allow platform-specific names
exclusions:
presets:
@@ -157,7 +159,6 @@ linters:
- funlen
- gosec
- errcheck
- testifylint
- lll
- source: "defer"
linters:
@@ -165,47 +166,12 @@ linters:
- text: "SELECT"
linters:
- gosec
# Temporary: known issues in pre-refactoring code (will be removed during refactoring)
- text: "result 0 .* is always nil"
linters:
- unparam
- text: "result 0 .* is never used"
linters:
- unparam
- path: "browser/firefox/firefox.go"
text: "field .* is unused"
linters:
- unused
- path: "browserdata/sessionstorage/"
text: "is unused"
linters:
- unused
- path: "cmd/hack-browser-data/main.go"
linters:
- lll
# Temporary: pre-refactoring code issues (all will be rewritten)
- path: "browserdata/"
linters:
- dupl
- gochecknoinits
- goconst
- lll
- path: "browser/firefox/"
linters:
- gocritic
- path: "crypto/"
linters:
- gocritic
- path: "crypto/keyretriever/gcoredump_darwin.go"
linters:
- gocognit
# Temporary: new v2 extract files have no callers until Phase 8 wiring
- path: "browser/chromium/(source|decrypt|extract_.*)\\.go"
linters:
- unused
- path: "browser/firefox/(source|extract_.*)\\.go"
linters:
- unused
formatters:
enable:
+1 -1
View File
@@ -21,7 +21,7 @@ func mkFile(t *testing.T, parts ...string) {
func TestListBrowsers(t *testing.T) {
list := ListBrowsers()
assert.True(t, len(list) > 0)
assert.NotEmpty(t, list)
assert.True(t, sort.StringsAreSorted(list))
}
+1 -1
View File
@@ -399,7 +399,7 @@ func TestAcquireFiles(t *testing.T) {
assert.Len(t, paths, len(cats))
for _, p := range paths {
_, err := os.Stat(p)
assert.NoError(t, err, "acquired file should exist")
require.NoError(t, err, "acquired file should exist")
}
}
+1 -1
View File
@@ -40,5 +40,5 @@ func TestExtractHistories_NullFields(t *testing.T) {
require.NoError(t, err)
require.Len(t, got, 1)
assert.Equal(t, "https://null.test", got[0].URL)
assert.Equal(t, "", got[0].Title)
assert.Empty(t, got[0].Title)
}
+23 -22
View File
@@ -7,6 +7,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
@@ -74,14 +75,14 @@ var (
func TestNewASN1PBE(t *testing.T) {
for _, tc := range nssPBETestCases {
nssRaw, err := hex.DecodeString(tc.RawHexPBE)
assert.Equal(t, nil, err)
require.NoError(t, err)
pbe, err := NewASN1PBE(nssRaw)
assert.Equal(t, nil, err)
require.NoError(t, err)
nssPBETC, ok := pbe.(nssPBE)
assert.Equal(t, true, ok)
assert.True(t, ok)
assert.Equal(t, nssPBETC.Encrypted, tc.Encrypted)
assert.Equal(t, nssPBETC.AlgoAttr.SaltAttr.EntrySalt, tc.GlobalSalt)
assert.Equal(t, nssPBETC.AlgoAttr.SaltAttr.Len, 20)
assert.Equal(t, 20, nssPBETC.AlgoAttr.SaltAttr.Len)
assert.Equal(t, nssPBETC.AlgoAttr.ObjectIdentifier, tc.ObjectIdentifier)
}
}
@@ -108,8 +109,8 @@ func TestNssPBE_Encrypt(t *testing.T) {
},
}
encrypted, err := nssPBETC.Encrypt(tc.GlobalSalt, tc.Plaintext)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(encrypted) > 0)
require.NoError(t, err)
assert.NotEmpty(t, encrypted)
assert.Equal(t, nssPBETC.Encrypted, encrypted)
}
}
@@ -136,8 +137,8 @@ func TestNssPBE_Decrypt(t *testing.T) {
},
}
decrypted, err := nssPBETC.Decrypt(tc.GlobalSalt)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(decrypted) > 0)
require.NoError(t, err)
assert.NotEmpty(t, decrypted)
assert.Equal(t, pbePlaintext, decrypted)
}
}
@@ -145,11 +146,11 @@ func TestNssPBE_Decrypt(t *testing.T) {
func TestNewASN1PBE_MetaPBE(t *testing.T) {
for _, tc := range metaPBETestCases {
metaRaw, err := hex.DecodeString(tc.RawHexPBE)
assert.Equal(t, nil, err)
require.NoError(t, err)
pbe, err := NewASN1PBE(metaRaw)
assert.Equal(t, nil, err)
require.NoError(t, err)
metaPBETC, ok := pbe.(metaPBE)
assert.Equal(t, true, ok)
assert.True(t, ok)
assert.Equal(t, metaPBETC.Encrypted, tc.Encrypted)
assert.Equal(t, metaPBETC.AlgoAttr.Data.IVData.IV, tc.IV)
assert.Equal(t, metaPBETC.AlgoAttr.Data.IVData.ObjectIdentifier, objWithSHA256AndAES)
@@ -193,8 +194,8 @@ func TestMetaPBE_Encrypt(t *testing.T) {
Encrypted: tc.Encrypted,
}
encrypted, err := metaPBETC.Encrypt(tc.GlobalSalt, tc.Plaintext)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(encrypted) > 0)
require.NoError(t, err)
assert.NotEmpty(t, encrypted)
assert.Equal(t, metaPBETC.Encrypted, encrypted)
}
}
@@ -236,8 +237,8 @@ func TestMetaPBE_Decrypt(t *testing.T) {
Encrypted: tc.Encrypted,
}
decrypted, err := metaPBETC.Decrypt(tc.GlobalSalt)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(decrypted) > 0)
require.NoError(t, err)
assert.NotEmpty(t, decrypted)
assert.Equal(t, pbePlaintext, decrypted)
}
}
@@ -245,11 +246,11 @@ func TestMetaPBE_Decrypt(t *testing.T) {
func TestNewASN1PBE_LoginPBE(t *testing.T) {
for _, tc := range loginPBETestCases {
loginRaw, err := hex.DecodeString(tc.RawHexPBE)
assert.Equal(t, nil, err)
require.NoError(t, err)
pbe, err := NewASN1PBE(loginRaw)
assert.Equal(t, nil, err)
require.NoError(t, err)
loginPBETC, ok := pbe.(loginPBE)
assert.Equal(t, true, ok)
assert.True(t, ok)
assert.Equal(t, loginPBETC.Encrypted, tc.Encrypted)
assert.Equal(t, loginPBETC.Data.IV, tc.IV)
assert.Equal(t, loginPBETC.Data.ObjectIdentifier, objWithMD5AndDESCBC)
@@ -270,8 +271,8 @@ func TestLoginPBE_Encrypt(t *testing.T) {
Encrypted: tc.Encrypted,
}
encrypted, err := loginPBETC.Encrypt(tc.GlobalSalt, plainText)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(encrypted) > 0)
require.NoError(t, err)
assert.NotEmpty(t, encrypted)
assert.Equal(t, loginPBETC.Encrypted, encrypted)
}
}
@@ -290,8 +291,8 @@ func TestLoginPBE_Decrypt(t *testing.T) {
Encrypted: tc.Encrypted,
}
decrypted, err := loginPBETC.Decrypt(tc.GlobalSalt)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(decrypted) > 0)
require.NoError(t, err)
assert.NotEmpty(t, decrypted)
assert.Equal(t, pbePlaintext, decrypted)
}
}
+13 -12
View File
@@ -8,6 +8,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const baseKey = "moond4rk"
@@ -28,45 +29,45 @@ var (
func TestAES128CBCEncrypt(t *testing.T) {
encrypted, err := AES128CBCEncrypt(aesKey, aesIV, plainText)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(encrypted) > 0)
require.NoError(t, err)
assert.NotEmpty(t, encrypted)
assert.Equal(t, aes128Ciphertext, fmt.Sprintf("%x", encrypted))
}
func TestAES128CBCDecrypt(t *testing.T) {
ciphertext, _ := hex.DecodeString(aes128Ciphertext)
decrypted, err := AES128CBCDecrypt(aesKey, aesIV, ciphertext)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(decrypted) > 0)
require.NoError(t, err)
assert.NotEmpty(t, decrypted)
assert.Equal(t, plainText, decrypted)
}
func TestDES3Encrypt(t *testing.T) {
encrypted, err := DES3Encrypt(des3Key, des3IV, plainText)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(encrypted) > 0)
require.NoError(t, err)
assert.NotEmpty(t, encrypted)
assert.Equal(t, des3Ciphertext, fmt.Sprintf("%x", encrypted))
}
func TestDES3Decrypt(t *testing.T) {
ciphertext, _ := hex.DecodeString(des3Ciphertext)
decrypted, err := DES3Decrypt(des3Key, des3IV, ciphertext)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(decrypted) > 0)
require.NoError(t, err)
assert.NotEmpty(t, decrypted)
assert.Equal(t, plainText, decrypted)
}
func TestAESGCMEncrypt(t *testing.T) {
encrypted, err := AESGCMEncrypt(aesKey, aesGCMNonce, plainText)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(encrypted) > 0)
require.NoError(t, err)
assert.NotEmpty(t, encrypted)
assert.Equal(t, aesGCMCiphertext, fmt.Sprintf("%x", encrypted))
}
func TestAESGCMDecrypt(t *testing.T) {
ciphertext, _ := hex.DecodeString(aesGCMCiphertext)
decrypted, err := AESGCMDecrypt(aesKey, aesGCMNonce, ciphertext)
assert.Equal(t, nil, err)
assert.Equal(t, true, len(decrypted) > 0)
require.NoError(t, err)
assert.NotEmpty(t, decrypted)
assert.Equal(t, plainText, decrypted)
}
+2 -2
View File
@@ -43,7 +43,7 @@ func TestChainRetriever_AllFail(t *testing.T) {
&mockRetriever{err: errors.New("second failed")},
)
key, err := chain.RetrieveKey("Chrome", "")
assert.Error(t, err)
require.Error(t, err)
assert.Nil(t, key)
assert.Contains(t, err.Error(), "all retrievers failed")
assert.Contains(t, err.Error(), "first failed")
@@ -64,6 +64,6 @@ func TestChainRetriever_SkipEmptyKey(t *testing.T) {
func TestChainRetriever_Empty(t *testing.T) {
chain := NewChain()
key, err := chain.RetrieveKey("Chrome", "")
assert.Error(t, err)
require.Error(t, err)
assert.Nil(t, key)
}
+8 -8
View File
@@ -25,12 +25,12 @@ func TestCopyLocked_ExclusiveLock(t *testing.T) {
// Normal copy should fail
err := copyFile(src, filepath.Join(dir, "normal_copy.db"))
assert.Error(t, err, "normal copy should fail on exclusively locked file")
require.Error(t, err, "normal copy should fail on exclusively locked file")
// copyLocked should succeed via DuplicateHandle + FileMapping
lockedDst := filepath.Join(dir, "locked_copy.db")
err = copyLocked(src, lockedDst)
assert.NoError(t, err, "copyLocked should bypass exclusive lock")
require.NoError(t, err, "copyLocked should bypass exclusive lock")
copied, err := os.ReadFile(lockedDst)
require.NoError(t, err)
@@ -62,7 +62,7 @@ func TestCopyLocked_WriteThenRead(t *testing.T) {
// copyLocked should read the full content including appended data
lockedDst := filepath.Join(dir, "modified_copy.db")
copyErr := copyLocked(src, lockedDst)
assert.NoError(t, copyErr)
require.NoError(t, copyErr)
copied, err := os.ReadFile(lockedDst)
require.NoError(t, err)
@@ -86,17 +86,17 @@ func TestCopyLocked_LargeFile(t *testing.T) {
lockedDst := filepath.Join(dir, "large_copy.db")
err := copyLocked(src, lockedDst)
assert.NoError(t, err)
require.NoError(t, err)
copied, err := os.ReadFile(lockedDst)
require.NoError(t, err)
assert.Equal(t, len(data), len(copied), "file sizes should match")
assert.Len(t, copied, len(data), "file sizes should match")
assert.True(t, bytes.Equal(data, copied), "file content should match byte-for-byte")
}
func TestCopyLocked_FileNotFound(t *testing.T) {
err := copyLocked("/nonexistent/file.db", filepath.Join(t.TempDir(), "dst.db"))
assert.Error(t, err)
require.Error(t, err)
}
func TestAcquire_FallbackToLocked(t *testing.T) {
@@ -115,7 +115,7 @@ func TestAcquire_FallbackToLocked(t *testing.T) {
dst := filepath.Join(session.TempDir(), "cookies.db")
err = session.Acquire(src, dst, false)
assert.NoError(t, err, "Acquire should succeed via locked fallback")
require.NoError(t, err, "Acquire should succeed via locked fallback")
copied, err := os.ReadFile(dst)
require.NoError(t, err)
@@ -136,7 +136,7 @@ func TestAcquire_NormalCopyWhenNotLocked(t *testing.T) {
dst := filepath.Join(session.TempDir(), "unlocked.db")
err = session.Acquire(src, dst, false)
assert.NoError(t, err)
require.NoError(t, err)
copied, err := os.ReadFile(dst)
require.NoError(t, err)
+4 -4
View File
@@ -42,7 +42,7 @@ func TestSession_Acquire_File(t *testing.T) {
// Acquire it
dst := filepath.Join(s.TempDir(), "Login Data")
err = s.Acquire(srcFile, dst, false)
assert.NoError(t, err)
require.NoError(t, err)
// Verify copy
data, err := os.ReadFile(dst)
@@ -63,7 +63,7 @@ func TestSession_Acquire_WAL(t *testing.T) {
dst := filepath.Join(s.TempDir(), "Cookies")
err = s.Acquire(srcFile, dst, false)
assert.NoError(t, err)
require.NoError(t, err)
// Main file copied
assert.FileExists(t, dst)
@@ -85,7 +85,7 @@ func TestSession_Acquire_Dir(t *testing.T) {
dst := filepath.Join(s.TempDir(), "leveldb")
err = s.Acquire(srcDir, dst, true)
assert.NoError(t, err)
require.NoError(t, err)
// Data file copied
assert.FileExists(t, filepath.Join(dst, "000001.ldb"))
@@ -99,5 +99,5 @@ func TestSession_Acquire_NotFound(t *testing.T) {
dst := filepath.Join(s.TempDir(), "nope")
err = s.Acquire("/nonexistent/file", dst, false)
assert.Error(t, err)
require.Error(t, err)
}
+15 -5
View File
@@ -7,17 +7,27 @@ import (
"github.com/moond4rk/hackbrowserdata/types"
)
type cookieEditorFormatter struct{}
// cookieEditorFormatter outputs cookies in the CookieEditor browser extension
// format. Non-cookie categories fall back to standard JSON output.
type cookieEditorFormatter struct {
fallback *jsonFormatter
}
func (f *cookieEditorFormatter) ext() string { return "json" }
func (f *cookieEditorFormatter) format(w io.Writer, rows []row) error {
if len(rows) == 0 {
return nil
}
// aggregate() guarantees all rows in a batch share the same type;
// check the first row to decide the format.
if _, ok := rows[0].entry.(types.CookieEntry); !ok {
return f.fallback.format(w, rows)
}
entries := make([]cookieEditorEntry, 0, len(rows))
for _, r := range rows {
c, ok := r.entry.(types.CookieEntry)
if !ok {
return nil // not cookies, skip
}
c, _ := r.entry.(types.CookieEntry)
var expDate float64
if !c.ExpireAt.IsZero() {
expDate = float64(c.ExpireAt.Unix())
+1 -1
View File
@@ -18,7 +18,7 @@ func newFormatter(name string) (formatter, error) {
case "json":
return &jsonFormatter{}, nil
case "cookie-editor":
return &cookieEditorFormatter{}, nil
return &cookieEditorFormatter{fallback: &jsonFormatter{}}, nil
default:
return nil, fmt.Errorf("unsupported format: %s", name)
}
+6 -6
View File
@@ -65,10 +65,10 @@ func TestNew(t *testing.T) {
t.Run(tt.format, func(t *testing.T) {
out, err := NewWriter(t.TempDir(), tt.format)
if tt.wantErr {
assert.Error(t, err)
require.Error(t, err)
assert.Nil(t, out)
} else {
assert.NoError(t, err)
require.NoError(t, err)
assert.NotNil(t, out)
}
})
@@ -142,7 +142,7 @@ func TestWrite_CSV_UTF8BOM(t *testing.T) {
raw, err := os.ReadFile(filepath.Join(dir, "password.csv"))
require.NoError(t, err)
require.True(t, len(raw) >= 3)
require.GreaterOrEqual(t, len(raw), 3)
assert.Equal(t, utf8BOM, raw[:3], "CSV should start with UTF-8 BOM")
}
@@ -249,7 +249,7 @@ func TestWrite_CookieEditor(t *testing.T) {
}, entries[0])
}
func TestWrite_CookieEditor_SkipsNonCookie(t *testing.T) {
func TestWrite_CookieEditor_FallbackJSON(t *testing.T) {
dir := t.TempDir()
out, err := NewWriter(dir, "cookie-editor")
require.NoError(t, err)
@@ -258,9 +258,9 @@ func TestWrite_CookieEditor_SkipsNonCookie(t *testing.T) {
})
require.NoError(t, out.Write())
// password file should not be created (cookie-editor only exports cookies)
// non-cookie categories fall back to standard JSON format
_, err = os.Stat(filepath.Join(dir, "password.json"))
assert.True(t, os.IsNotExist(err))
assert.False(t, os.IsNotExist(err), "password.json should be created via JSON fallback")
}
// --- File creation ---
+3 -3
View File
@@ -29,7 +29,7 @@ func TestCompressDir(t *testing.T) {
defer os.RemoveAll(tempDir)
err := CompressDir(tempDir)
assert.NoError(t, err, "compressDir should not return an error")
require.NoError(t, err, "compressDir should not return an error")
// Check if the zip file exists
zipFile := filepath.Join(tempDir, filepath.Base(tempDir)+".zip")
@@ -38,7 +38,7 @@ func TestCompressDir(t *testing.T) {
t.Run("Directory Does Not Exist", func(t *testing.T) {
err := CompressDir("/path/to/nonexistent/directory")
assert.Error(t, err, "should return an error for non-existent directory")
require.Error(t, err, "should return an error for non-existent directory")
})
t.Run("Empty Directory", func(t *testing.T) {
@@ -47,6 +47,6 @@ func TestCompressDir(t *testing.T) {
defer os.RemoveAll(tempDir)
err = CompressDir(tempDir)
assert.Error(t, err, "should return an error for an empty directory")
require.Error(t, err, "should return an error for an empty directory")
})
}
+6 -6
View File
@@ -34,7 +34,7 @@ func TestQuerySQLite(t *testing.T) {
return nil
})
assert.NoError(t, err)
require.NoError(t, err)
assert.Equal(t, []string{"alpha", "beta", "gamma"}, names)
}
@@ -59,7 +59,7 @@ func TestQuerySQLite_JournalOff(t *testing.T) {
values = append(values, v)
return nil
})
assert.NoError(t, err)
require.NoError(t, err)
assert.Equal(t, []string{"ok"}, values)
}
@@ -67,7 +67,7 @@ func TestQuerySQLite_FileNotFound(t *testing.T) {
err := QuerySQLite("/nonexistent/path.db", false, "SELECT 1", func(rows *sql.Rows) error {
return nil
})
assert.Error(t, err)
require.Error(t, err)
}
func TestQuerySQLite_BadQuery(t *testing.T) {
@@ -83,7 +83,7 @@ func TestQuerySQLite_BadQuery(t *testing.T) {
err = QuerySQLite(dbPath, false, "SELECT nonexistent FROM t", func(rows *sql.Rows) error {
return nil
})
assert.Error(t, err)
require.Error(t, err)
}
func TestQueryRows(t *testing.T) {
@@ -110,7 +110,7 @@ func TestQueryRows(t *testing.T) {
return u, err
})
assert.NoError(t, err)
require.NoError(t, err)
assert.Equal(t, []user{{"alice", 30}, {"bob", 25}}, users)
}
@@ -133,6 +133,6 @@ func TestQueryRows_Empty(t *testing.T) {
return v, nil
})
assert.NoError(t, err)
require.NoError(t, err)
assert.Nil(t, results)
}