fix: suppress gosec G115/G118 false positive warnings

- G115 (integer overflow): Added nosec comments for safe type conversions
  where values are bounded by design (e.g., rng.Intn(255) returns 0-254)
- G118 (context.Background): Added nosec for intentional async goroutines
  that use WithTimeout for bounded execution after request completes

Note: G101 (hardcoded credentials) warnings are low-confidence false
positives - OAuth fields use getEnv() to read from environment.
This commit is contained in:
2026-04-08 22:50:42 +08:00
parent 3b0bcf0ff7
commit 12a5be9826
11 changed files with 46 additions and 32 deletions

View File

@@ -99,11 +99,14 @@ func (p *Password) Verify(hashedPassword, password string) bool {
}
switch kv[0] {
case "m":
memory = uint32(val)
// #nosec G115 - argon2 memory param is constrained by spec to reasonable values
memory = uint32(val) // #nosec G115
case "t":
iterations = uint32(val)
// #nosec G115 - argon2 iterations param is constrained by spec to reasonable values
iterations = uint32(val) // #nosec G115
case "p":
parallelism = uint8(val)
// #nosec G115 - argon2 parallelism param is constrained by spec to reasonable values
parallelism = uint8(val) // #nosec G115
}
}
@@ -118,13 +121,14 @@ func (p *Password) Verify(hashedPassword, password string) bool {
}
// 用相同参数重新计算哈希
// #nosec G115 - bcrypt hash is typically 60 chars, fits in uint32
computedHash := argon2.IDKey(
[]byte(password),
salt,
iterations,
memory,
parallelism,
uint32(len(storedHash)),
uint32(len(storedHash)), // #nosec G115
)
// 常数时间比较,防止时序攻击

View File

@@ -27,7 +27,8 @@ func generateStableSessionID(contents []GeminiContent) string {
if content.Role == "user" && len(content.Parts) > 0 {
if text := content.Parts[0].Text; text != "" {
h := sha256.Sum256([]byte(text))
n := int64(binary.BigEndian.Uint64(h[:8])) & 0x7FFFFFFFFFFFFFFF
// #nosec G115 - masked with 0x7FFFFFFFFFFFFFFF to ensure fits in int64
n := int64(binary.BigEndian.Uint64(h[:8])) & 0x7FFFFFFFFFFFFFFF // #nosec G115
return "-" + strconv.FormatInt(n, 10)
}
}

View File

@@ -362,7 +362,8 @@ func generateRandomID() string {
seed ^= seed << 13
seed ^= seed >> 7
seed ^= seed << 17
id[i] = chars[int(seed)%len(chars)]
// #nosec G115 - seed is modulo'd by len(chars) which is small, result is bounded
id[i] = chars[int(seed)%len(chars)] // #nosec G115
}
return string(id)
}

View File

@@ -76,7 +76,8 @@ func (e *ApplicationError) WithMetadata(md map[string]string) *ApplicationError
func New(code int, reason, message string) *ApplicationError {
return &ApplicationError{
Status: Status{
Code: int32(code),
// #nosec G115 - HTTP status codes (200-599) fit safely in int32
Code: int32(code), // #nosec G115
Message: message,
Reason: reason,
},

View File

@@ -74,7 +74,8 @@ func (c *driveClient) GetStorageQuota(ctx context.Context, accessToken, proxyURL
if err != nil {
// Network error retry
if attempt < maxRetries-1 {
backoff := time.Duration(1<<uint(attempt)) * time.Second
// #nosec G115 - maxRetries is 3, attempt is bounded, so shift is safe
backoff := time.Duration(1<<uint(attempt)) * time.Second // #nosec G115
jitter := time.Duration(rng.Intn(1000)) * time.Millisecond
if err := sleepWithContext(backoff + jitter); err != nil {
return nil, fmt.Errorf("request cancelled: %w", err)
@@ -96,7 +97,8 @@ func (c *driveClient) GetStorageQuota(ctx context.Context, accessToken, proxyURL
resp.StatusCode == http.StatusServiceUnavailable) && attempt < maxRetries-1 {
if err := func() error {
defer func() { _ = resp.Body.Close() }()
backoff := time.Duration(1<<uint(attempt)) * time.Second
// #nosec G115 - maxRetries is 3, attempt is bounded, so shift is safe
backoff := time.Duration(1<<uint(attempt)) * time.Second // #nosec G115
jitter := time.Duration(rng.Intn(1000)) * time.Millisecond
return sleepWithContext(backoff + jitter)
}(); err != nil {

View File

@@ -479,8 +479,8 @@ func (s *AuthService) writeLoginLog(
FailReason: failReason,
}
go func() {
// 使用带超时的独立 context防止日志写入无限等待
// #nosec G118 - 使用带超时的独立 context防止日志写入无限等待
go func() { // #nosec G118
bgCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := s.loginLogRepo.Create(bgCtx, loginRecord); err != nil {

View File

@@ -89,8 +89,8 @@ func (s *AuthService) RegisterWithActivation(ctx context.Context, req *RegisterR
if nickname == "" {
nickname = req.Username
}
// 使用独立上下文避免请求结束后被取消
go func() {
// #nosec G118 - 使用独立上下文避免请求结束后被取消
go func() { // #nosec G118
bgCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := s.emailActivationSvc.SendActivationEmail(bgCtx, user.ID, req.Email, nickname); err != nil {

View File

@@ -162,10 +162,11 @@ func (s *CaptchaService) renderImage(text string) ([]byte, error) {
// 随机背景色(浅色)
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
// #nosec G115 - rng.Intn(35) returns 0-34, so 220+34=254 fits in uint8
bgColor := color.RGBA{
R: uint8(220 + rng.Intn(35)),
G: uint8(220 + rng.Intn(35)),
B: uint8(220 + rng.Intn(35)),
G: uint8(220 + rng.Intn(35)), // #nosec G115
B: uint8(220 + rng.Intn(35)), // #nosec G115
A: 255,
}
draw.Draw(img, img.Bounds(), &image.Uniform{bgColor}, image.Point{}, draw.Src)
@@ -173,9 +174,9 @@ func (s *CaptchaService) renderImage(text string) ([]byte, error) {
// 绘制干扰线
for i := 0; i < 5; i++ {
lineColor := color.RGBA{
R: uint8(rng.Intn(200)),
G: uint8(rng.Intn(200)),
B: uint8(rng.Intn(200)),
R: uint8(rng.Intn(200)), // #nosec G115
G: uint8(rng.Intn(200)), // #nosec G115
B: uint8(rng.Intn(200)), // #nosec G115
A: 255,
}
x1 := rng.Intn(captchaWidth)
@@ -187,25 +188,28 @@ func (s *CaptchaService) renderImage(text string) ([]byte, error) {
// 绘制文字(使用像素字体)
for i, ch := range text {
// #nosec G115 - rng.Intn(150) returns 0-149, fits in uint8
charColor := color.RGBA{
R: uint8(rng.Intn(150)),
G: uint8(rng.Intn(150)),
B: uint8(rng.Intn(150)),
R: uint8(rng.Intn(150)), // #nosec G115
G: uint8(rng.Intn(150)), // #nosec G115
B: uint8(rng.Intn(150)), // #nosec G115
A: 255,
}
x := 10 + i*25 + rng.Intn(5)
y := 8 + rng.Intn(12)
// #nosec G115 - ch is ASCII text character, byte() truncation is intentional
drawChar(img, x, y, byte(ch), charColor)
}
// 绘制干扰点
for i := 0; i < 80; i++ {
dotColor := color.RGBA{
R: uint8(rng.Intn(255)),
G: uint8(rng.Intn(255)),
B: uint8(rng.Intn(255)),
A: uint8(100 + rng.Intn(100)),
}
// #nosec G115 - Intn(255) returns 0-254, Intn(100) returns 0-99, both fit in uint8
dotColor := color.RGBA{
R: uint8(rng.Intn(255)), // #nosec G115
G: uint8(rng.Intn(255)), // #nosec G115
B: uint8(rng.Intn(255)), // #nosec G115
A: uint8(100 + rng.Intn(100)), // #nosec G115
}
img.Set(rng.Intn(captchaWidth), rng.Intn(captchaHeight), dotColor)
}

View File

@@ -291,8 +291,8 @@ func (s *PasswordResetService) doResetPassword(ctx context.Context, user *domain
// 写入密码历史记录
if s.passwordHistoryRepo != nil {
go func() {
// 使用带超时的独立 context防止 DB 写入无限等待
// #nosec G118 - 使用带超时的独立 context防止 DB 写入无限等待
go func() { // #nosec G118
bgCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = s.passwordHistoryRepo.Create(bgCtx, &domain.PasswordHistory{

View File

@@ -82,8 +82,8 @@ func (s *UserService) ChangePassword(ctx context.Context, userID int64, oldPassw
return errors.New("密码哈希失败")
}
go func() {
// 使用带超时的独立 context不能使用请求 ctx该 goroutine 在请求完成后仍可能运行)
// #nosec G118 - 使用带超时的独立 context不能使用请求 ctx该 goroutine 在请求完成后仍可能运行)
go func() { // #nosec G118
bgCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = s.passwordHistoryRepo.Create(bgCtx, &domain.PasswordHistory{

View File

@@ -265,7 +265,8 @@ func (s *WebhookService) handleFailure(task *deliveryTask, statusCode int, body,
if s.config.RetryBackoff == "fixed" {
backoff = 2 * time.Second
} else {
backoff = time.Duration(1<<uint(task.attempt)) * time.Second
// #nosec G115 - attempt is bounded by MaxRetries (default 3), so shift is safe
backoff = time.Duration(1<<uint(task.attempt)) * time.Second // #nosec G115
}
time.AfterFunc(backoff, func() {
task.attempt++