@sun-asterisk/sunlint 1.3.40 → 1.3.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/rule-selection-service.js +11 -0
- package/package.json +1 -1
- package/skill-assets/sunlint-code-quality/rules/go/C006-verb-noun-functions.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/go/C013-no-dead-code.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/C014-dependency-injection.md +85 -0
- package/skill-assets/sunlint-code-quality/rules/go/C017-no-constructor-logic.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/go/C018-generic-errors.md +63 -0
- package/skill-assets/sunlint-code-quality/rules/go/C019-error-log-level.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/go/C020-no-unused-imports.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/go/C022-no-unused-variables.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/go/C023-no-duplicate-names.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/go/C024-centralize-constants.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/go/C029-catch-log-root-cause.md +56 -0
- package/skill-assets/sunlint-code-quality/rules/go/C030-custom-error-classes.md +69 -0
- package/skill-assets/sunlint-code-quality/rules/go/C033-separate-data-access.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/go/C035-error-context-logging.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/C041-no-hardcoded-secrets.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/go/C042-boolean-naming.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/go/C052-controller-parsing.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/go/C060-superclass-logic.md +60 -0
- package/skill-assets/sunlint-code-quality/rules/go/C067-no-hardcoded-config.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S003-open-redirect.md +80 -0
- package/skill-assets/sunlint-code-quality/rules/go/S004-no-log-credentials.md +66 -0
- package/skill-assets/sunlint-code-quality/rules/go/S005-server-authorization.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/go/S006-default-credentials.md +47 -0
- package/skill-assets/sunlint-code-quality/rules/go/S007-output-encoding.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/go/S009-approved-crypto.md +63 -0
- package/skill-assets/sunlint-code-quality/rules/go/S010-csprng.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S011-encrypted-client-hello.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/go/S012-secrets-management.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S013-tls-connections.md +61 -0
- package/skill-assets/sunlint-code-quality/rules/go/S016-no-sensitive-query-string.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/go/S017-parameterized-queries.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/go/S019-email-input-sanitization.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/go/S020-eval-code-execution.md +47 -0
- package/skill-assets/sunlint-code-quality/rules/go/S022-context-escaping.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S023-dynamic-js-encoding.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S025-server-validation.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/go/S026-tls-encryption.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/go/S027-mtls-validation.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/go/S028-upload-limits.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/go/S029-csrf-protection.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S030-directory-browsing.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S031-secure-cookie-flag.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/S032-httponly-cookie.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/go/S033-samesite-cookie.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S034-host-prefix-cookie.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/go/S035-app-hostnames.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/go/S036-internal-file-paths.md +56 -0
- package/skill-assets/sunlint-code-quality/rules/go/S037-anti-cache-headers.md +43 -0
- package/skill-assets/sunlint-code-quality/rules/go/S039-tls-certificate-validation.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/go/S041-logout-invalidation.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/go/S042-long-lived-sessions.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/go/S044-critical-changes-reauth.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S045-brute-force-protection.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/go/S047-oauth-csrf-protection.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S048-oauth-redirect-validation.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/go/S049-auth-code-expiry.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/go/S050-token-entropy.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S051-password-length.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S052-otp-entropy.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/S053-generic-error-messages.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S054-no-default-admin.md +43 -0
- package/skill-assets/sunlint-code-quality/rules/go/S055-content-type-validation.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/go/S056-log-injection.md +40 -0
- package/skill-assets/sunlint-code-quality/rules/go/S057-synchronized-time.md +40 -0
- package/skill-assets/sunlint-code-quality/rules/go/S058-ssrf-protection.md +70 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Re-authenticate Before Critical Changes
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: prevents unauthorized critical operations
|
|
5
|
+
tags: authentication, critical, reauthentication, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Re-authenticate Before Critical Changes
|
|
9
|
+
|
|
10
|
+
Critical actions like password change, email change, or account deletion require fresh authentication.
|
|
11
|
+
|
|
12
|
+
**Incorrect (no re-authentication):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// Dangerous - no password confirmation
|
|
16
|
+
func DeleteAccountHandler(w http.ResponseWriter, r *http.Request) {
|
|
17
|
+
userID := r.Context().Value("userID").(string)
|
|
18
|
+
deleteAccount(userID)
|
|
19
|
+
w.Write([]byte(`{"success": true}`))
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Correct (require password confirmation):**
|
|
24
|
+
|
|
25
|
+
```go
|
|
26
|
+
func DeleteAccountHandler(w http.ResponseWriter, r *http.Request) {
|
|
27
|
+
userID := r.Context().Value("userID").(string)
|
|
28
|
+
currentPassword := r.FormValue("password")
|
|
29
|
+
|
|
30
|
+
// 1. Verify current password
|
|
31
|
+
if !verifyPassword(userID, currentPassword) {
|
|
32
|
+
http.Error(w, "Invalid password", 401)
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 2. Perform critical action
|
|
37
|
+
deleteAccount(userID)
|
|
38
|
+
|
|
39
|
+
// 3. Log the security event
|
|
40
|
+
slog.Info("Account deleted", "user_id", userID)
|
|
41
|
+
|
|
42
|
+
w.Write([]byte(`{"success": true}`))
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Critical actions requiring re-auth:**
|
|
47
|
+
- Password change
|
|
48
|
+
- Email change
|
|
49
|
+
- Phone number change
|
|
50
|
+
- Account deletion
|
|
51
|
+
- Major security settings changes
|
|
52
|
+
|
|
53
|
+
**Tools:** Manual Review, Security Audit
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Implement Brute-force Protection
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: prevents password guessing attacks
|
|
5
|
+
tags: brute-force, rate-limiting, authentication, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Implement Brute-force Protection
|
|
9
|
+
|
|
10
|
+
Without rate limiting, attackers can try millions of password combinations.
|
|
11
|
+
|
|
12
|
+
**Incorrect (no protection):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func LoginHandler(w http.ResponseWriter, r *http.Request) {
|
|
16
|
+
user, err := authenticate(r.FormValue("email"), r.FormValue("password"))
|
|
17
|
+
// No limit on attempts!
|
|
18
|
+
if err != nil {
|
|
19
|
+
http.Error(w, "Invalid credentials", 401)
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Correct (rate limiting with middleware):**
|
|
26
|
+
|
|
27
|
+
```go
|
|
28
|
+
import "golang.org/x/time/rate"
|
|
29
|
+
|
|
30
|
+
var loginLimiter = rate.NewLimiter(rate.Every(3*time.Minute), 5) // 5 attempts per window
|
|
31
|
+
|
|
32
|
+
func LoginHandler(w http.ResponseWriter, r *http.Request) {
|
|
33
|
+
if !loginLimiter.Allow() {
|
|
34
|
+
http.Error(w, "Too many login attempts", 429)
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
user, err := authenticate(r.FormValue("email"), r.FormValue("password"))
|
|
39
|
+
// ...
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Better: persistent rate limiting via Redis
|
|
43
|
+
func RateLimitMiddleware(next http.Handler) http.Handler {
|
|
44
|
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
45
|
+
key := "login_limit:" + r.RemoteAddr
|
|
46
|
+
if isRateLimited(key) {
|
|
47
|
+
http.Error(w, "Too many attempts", 429)
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
next.ServeHTTP(w, r)
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Tools:** `golang.org/x/time/rate`, Redis, WAF
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Protect OAuth Code Flow Vs CSRF
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: prevents OAuth authorization code theft
|
|
5
|
+
tags: oauth, csrf, state, authorization, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Protect OAuth Code Flow Vs CSRF
|
|
9
|
+
|
|
10
|
+
Without state parameter validation, attackers can use their own authorization codes to link their accounts to a victim's session.
|
|
11
|
+
|
|
12
|
+
**Incorrect (no state parameter):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func OAuthInitHandler(w http.ResponseWriter, r *http.Request) {
|
|
16
|
+
url := fmt.Sprintf("https://accounts.google.com/o/oauth2/auth?client_id=%s&redirect_uri=%s&response_type=code", clientID, redirectURI)
|
|
17
|
+
// No state parameter!
|
|
18
|
+
http.Redirect(w, r, url, http.StatusFound)
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (state parameter validation):**
|
|
23
|
+
|
|
24
|
+
```go
|
|
25
|
+
func OAuthInitHandler(w http.ResponseWriter, r *http.Request) {
|
|
26
|
+
state := generateRandomState() // CSPRNG
|
|
27
|
+
|
|
28
|
+
// Store in session (cookie or DB)
|
|
29
|
+
session := getSession(r)
|
|
30
|
+
session.Values["oauth_state"] = state
|
|
31
|
+
session.Save(r, w)
|
|
32
|
+
|
|
33
|
+
url := googleConfig.AuthCodeURL(state)
|
|
34
|
+
http.Redirect(w, r, url, http.StatusFound)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
func OAuthCallbackHandler(w http.ResponseWriter, r *http.Request) {
|
|
38
|
+
queryState := r.URL.Query().Get("state")
|
|
39
|
+
session := getSession(r)
|
|
40
|
+
|
|
41
|
+
// Validate state
|
|
42
|
+
if queryState == "" || queryState != session.Values["oauth_state"] {
|
|
43
|
+
http.Error(w, "Invalid state", 403)
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Exchange code for token
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Tools:** `golang.org/x/oauth2`, Security Audit
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Validate OAuth Redirect URIs Exactly
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: prevents OAuth redirect hijacking
|
|
5
|
+
tags: oauth, redirect, uri, validation, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Validate OAuth Redirect URIs Exactly
|
|
9
|
+
|
|
10
|
+
Loose redirect URI validation allows attackers to steal authorization codes by redirecting users to malicious sites.
|
|
11
|
+
|
|
12
|
+
**Incorrect (partial/loose validation):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// Dangerous - substring match
|
|
16
|
+
if strings.Contains(redirectURI, "example.com") {
|
|
17
|
+
// Allows attacker.com?example.com
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Dangerous - prefix match
|
|
21
|
+
if strings.HasPrefix(redirectURI, "https://example.com") {
|
|
22
|
+
// Allows https://example.com.attacker.com
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Correct (exact match against registered URIs):**
|
|
27
|
+
|
|
28
|
+
```go
|
|
29
|
+
var registeredRedirectURIs = []string{
|
|
30
|
+
"https://app.example.com/callback",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func isValidRedirect(uri string) bool {
|
|
34
|
+
// Exact match required
|
|
35
|
+
for _, r := range registeredRedirectURIs {
|
|
36
|
+
if r == uri {
|
|
37
|
+
return true
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func AuthorizeHandler(w http.ResponseWriter, r *http.Request) {
|
|
44
|
+
redirectURI := r.URL.Query().Get("redirect_uri")
|
|
45
|
+
if !isValidRedirect(redirectURI) {
|
|
46
|
+
http.Error(w, "Invalid redirect URI", 400)
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
// ...
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Requirements:**
|
|
54
|
+
- Exact string match for redirect URIs.
|
|
55
|
+
- No wildcards or pattern matching.
|
|
56
|
+
- HTTPS required for production.
|
|
57
|
+
|
|
58
|
+
**Tools:** OAuth Security Testing, `golang.org/x/oauth2`
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Authentication Codes Must Expire Quickly
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: limits window for code interception attacks
|
|
5
|
+
tags: authentication, codes, expiry, otp, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Authentication Codes Must Expire Quickly
|
|
9
|
+
|
|
10
|
+
Long-lived codes give attackers more time to intercept and use them. Short expiry limits the attack window.
|
|
11
|
+
|
|
12
|
+
**Incorrect (codes last too long):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// Code valid for 24 hours
|
|
16
|
+
db.SaveToken(userID, token, time.Now().Add(24 * time.Hour))
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Correct (short expiry with proper handling):**
|
|
20
|
+
|
|
21
|
+
```go
|
|
22
|
+
const CodeExpiry = 5 * time.Minute
|
|
23
|
+
|
|
24
|
+
func GenerateAuthCode(userID string) (string, error) {
|
|
25
|
+
code := generateOTP() // 6-digit CSPRNG
|
|
26
|
+
|
|
27
|
+
// Store in Redis with TTL
|
|
28
|
+
err := redisClient.Set(ctx, "otp:"+userID, code, CodeExpiry).Err()
|
|
29
|
+
return code, err
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func VerifyAuthCode(userID, inputCode string) (bool, error) {
|
|
33
|
+
storedCode, err := redisClient.Get(ctx, "otp:"+userID).Result()
|
|
34
|
+
if err == redis.Nil {
|
|
35
|
+
return false, nil // Expired or not found
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if storedCode == inputCode {
|
|
39
|
+
redisClient.Del(ctx, "otp:"+userID) // Single use!
|
|
40
|
+
return true, nil
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return false, nil
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Recommended expiry times:**
|
|
48
|
+
- 2FA/OTP: 5-10 minutes
|
|
49
|
+
- Password reset: 15-60 minutes
|
|
50
|
+
- Email verification: 24 hours
|
|
51
|
+
|
|
52
|
+
**Tools:** Redis (TTL), Database (expiry column), Manual Review
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Reference Tokens 128-bit Entropy CSPRNG
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: prevents token brute-forcing
|
|
5
|
+
tags: tokens, entropy, csprng, session, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Reference Tokens 128-bit Entropy CSPRNG
|
|
9
|
+
|
|
10
|
+
Low-entropy tokens can be brute-forced. 128 bits of entropy makes attacks computationally infeasible.
|
|
11
|
+
|
|
12
|
+
**Incorrect (low entropy tokens):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// Low entropy
|
|
16
|
+
token := fmt.Sprintf("%d", rand.Int63())
|
|
17
|
+
|
|
18
|
+
// Predictable
|
|
19
|
+
token := "session_" + strconv.Itoa(counter)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (high entropy tokens via crypto/rand):**
|
|
23
|
+
|
|
24
|
+
```go
|
|
25
|
+
import (
|
|
26
|
+
"crypto/rand"
|
|
27
|
+
"encoding/base64"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
func GenerateToken(length int) string {
|
|
31
|
+
b := make([]byte, length)
|
|
32
|
+
if _, err := rand.Read(b); err != nil {
|
|
33
|
+
panic(err)
|
|
34
|
+
}
|
|
35
|
+
return base64.URLEncoding.EncodeToString(b)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 128-bit minimum entropy (16 bytes)
|
|
39
|
+
sessionToken := GenerateToken(16)
|
|
40
|
+
|
|
41
|
+
// 256-bit recommended (32 bytes)
|
|
42
|
+
refreshToken := GenerateToken(32)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Entropy levels:**
|
|
46
|
+
|
|
47
|
+
| Bytes | Bits | Security Level |
|
|
48
|
+
|-------|------|----------------|
|
|
49
|
+
| 8 | 64 | Weak |
|
|
50
|
+
| 16 | 128 | Minimum |
|
|
51
|
+
| 32 | 256 | Recommended |
|
|
52
|
+
|
|
53
|
+
**Tools:** `crypto/rand`, Security Audit
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Support 12-64 Character Passwords
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: enables secure passphrase usage
|
|
5
|
+
tags: password, length, passphrase, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Support 12-64 Character Passwords
|
|
9
|
+
|
|
10
|
+
Long passwords/passphrases are more secure than complex short ones. Don't impose restrictive limits that prevent users from using passphrases.
|
|
11
|
+
|
|
12
|
+
**Incorrect (restrictive limits):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// Too restrictive max length
|
|
16
|
+
if len(password) < 8 || len(password) > 16 {
|
|
17
|
+
return errors.New("password must be 8-16 characters")
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct (reasonable limits):**
|
|
22
|
+
|
|
23
|
+
```go
|
|
24
|
+
func ValidatePassword(password string) error {
|
|
25
|
+
length := utf8.RuneCountInString(password)
|
|
26
|
+
|
|
27
|
+
if length < 12 {
|
|
28
|
+
return errors.New("password too short (min 12)")
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if length > 64 {
|
|
32
|
+
return errors.New("password too long (max 64)")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// For long passwords (e.g., 20+), complexity rules can be relaxed
|
|
36
|
+
if length < 20 {
|
|
37
|
+
// check for symbols, numbers, etc.
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return nil
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**NIST Guidelines:**
|
|
45
|
+
- Minimum 8-12+ characters.
|
|
46
|
+
- Maximum 64+ characters.
|
|
47
|
+
- Allow space and all printable Unicode characters.
|
|
48
|
+
|
|
49
|
+
**Tools:** Password Policy logic, Manual Review
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: OTPs Must Have 20-bit Entropy Minimum
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: prevents OTP brute-forcing
|
|
5
|
+
tags: otp, entropy, authentication, 2fa, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## OTPs Must Have 20-bit Entropy Minimum
|
|
9
|
+
|
|
10
|
+
Low-entropy OTPs (like 4 digits) can be brute-forced easily. 20 bits of entropy requires at least 6 decimal digits.
|
|
11
|
+
|
|
12
|
+
**Incorrect (low entropy OTPs):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// 4 digits = ~13 bits entropy
|
|
16
|
+
otp := fmt.Sprintf("%04d", rand.Intn(10000))
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Correct (high entropy OTPs via crypto/rand):**
|
|
20
|
+
|
|
21
|
+
```go
|
|
22
|
+
import (
|
|
23
|
+
"crypto/rand"
|
|
24
|
+
"math/big"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
// 6-digit numeric OTP (≈20 bits entropy)
|
|
28
|
+
func GenerateOTP() string {
|
|
29
|
+
max := big.NewInt(1000000)
|
|
30
|
+
n, _ := rand.Int(rand.Reader, max)
|
|
31
|
+
return fmt.Sprintf("%06d", n)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 8-digit for higher security (≈26 bits)
|
|
35
|
+
func GenerateStrongOTP() string {
|
|
36
|
+
max := big.NewInt(100000000)
|
|
37
|
+
n, _ := rand.Int(rand.Reader, max)
|
|
38
|
+
return fmt.Sprintf("%08d", n)
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**OTP requirements:**
|
|
43
|
+
- Must use `crypto/rand` (CSPRNG).
|
|
44
|
+
- Minimum 6 digits (20 bits).
|
|
45
|
+
- Short expiration (5-10 minutes).
|
|
46
|
+
- Rate limit verification attempts.
|
|
47
|
+
|
|
48
|
+
**Tools:** Manual Review, Unit Test
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Return Generic Error Messages
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: prevents information disclosure
|
|
5
|
+
tags: error-messages, information-disclosure, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Return Generic Error Messages
|
|
9
|
+
|
|
10
|
+
Detailed error messages can help attackers understand your system's internals. Return generic messages to end-users.
|
|
11
|
+
|
|
12
|
+
**Incorrect (detailed errors to user):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func Handler(w http.ResponseWriter, r *http.Request) {
|
|
16
|
+
err := db.QueryRow("...").Scan(&id)
|
|
17
|
+
if err != nil {
|
|
18
|
+
// Exposes database details!
|
|
19
|
+
http.Error(w, err.Error(), 500)
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// User enumeration
|
|
25
|
+
if userNotFound {
|
|
26
|
+
http.Error(w, "User john@example.com dose not exist", 404)
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Correct (generic errors with internal logging):**
|
|
31
|
+
|
|
32
|
+
```go
|
|
33
|
+
func Handler(w http.ResponseWriter, r *http.Request) {
|
|
34
|
+
err := db.QueryRow("...").Scan(&id)
|
|
35
|
+
if err != nil {
|
|
36
|
+
// Log internally
|
|
37
|
+
slog.Error("query failed", "error", err, "request_id", r.Header.Get("X-Request-Id"))
|
|
38
|
+
|
|
39
|
+
// Return generic message
|
|
40
|
+
http.Error(w, "An internal error occurred", 500)
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Same message for "user not found" and "wrong password"
|
|
46
|
+
if !userExists || !passwordMatches {
|
|
47
|
+
http.Error(w, "Invalid credentials", 401)
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Tools:** Global Error Middleware, `slog`
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Avoid Default Admin/Root Accounts
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: prevents easy initial access by attackers
|
|
5
|
+
tags: admin, default-accounts, credentials, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Avoid Default Admin/Root Accounts
|
|
9
|
+
|
|
10
|
+
Default accounts with known credentials (e.g., admin/admin) are the first thing attackers check.
|
|
11
|
+
|
|
12
|
+
**Incorrect (default admin in seed):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// Seed script
|
|
16
|
+
db.Exec("INSERT INTO users (email, password, role) VALUES ('admin@example.com', 'admin123', 'admin')")
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Correct (secure initial setup):**
|
|
20
|
+
|
|
21
|
+
```go
|
|
22
|
+
// 1. Setup wizard on first run
|
|
23
|
+
func SetupHandler(w http.ResponseWriter, r *http.Request) {
|
|
24
|
+
if adminExists() {
|
|
25
|
+
http.Error(w, "Setup already done", 403)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
// Form to create first admin...
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 2. Or use environment variables for bootstrap
|
|
32
|
+
func BootstrapAdmin() {
|
|
33
|
+
email := os.Getenv("INITIAL_ADMIN_EMAIL")
|
|
34
|
+
pass := os.Getenv("INITIAL_ADMIN_PASSWORD")
|
|
35
|
+
|
|
36
|
+
if pass == "" || len(pass) < 16 {
|
|
37
|
+
log.Fatal("Secure INITIAL_ADMIN_PASSWORD required")
|
|
38
|
+
}
|
|
39
|
+
createAdmin(email, pass)
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Tools:** Security Audit, Configuration Review
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Validate Content-Type In REST Services
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: prevents content-type confusion attacks
|
|
5
|
+
tags: rest, content-type, validation, api, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Validate Content-Type In REST Services
|
|
9
|
+
|
|
10
|
+
Accepting unexpected content types can lead to parsing vulnerabilities or bypass security controls.
|
|
11
|
+
|
|
12
|
+
**Incorrect (accepting any content):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func Handler(w http.ResponseWriter, r *http.Request) {
|
|
16
|
+
// No check on Content-Type header
|
|
17
|
+
var data map[string]interface{}
|
|
18
|
+
json.NewDecoder(r.Body).Decode(&data)
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (strict content-type validation):**
|
|
23
|
+
|
|
24
|
+
```go
|
|
25
|
+
func validateContentType(allowed ...string) func(http.Handler) http.Handler {
|
|
26
|
+
return func(next http.Handler) http.Handler {
|
|
27
|
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
28
|
+
ct := r.Header.Get("Content-Type")
|
|
29
|
+
|
|
30
|
+
isValid := false
|
|
31
|
+
for _, a := range allowed {
|
|
32
|
+
if strings.Contains(strings.ToLower(ct), strings.ToLower(a)) {
|
|
33
|
+
isValid = true
|
|
34
|
+
break
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if !isValid {
|
|
39
|
+
http.Error(w, "Unsupported Media Type", http.StatusUnsupportedMediaType)
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
next.ServeHTTP(w, r)
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Router usage
|
|
49
|
+
mux.Handle("/api/data", validateContentType("application/json")(http.HandlerFunc(Handler)))
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Tools:** API Gateway, Middleware, OWASP ZAP
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Protect Against Log Injection
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: prevents log forging and exploitation
|
|
5
|
+
tags: logging, injection, sanitization, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Protect Against Log Injection
|
|
9
|
+
|
|
10
|
+
Log injection allows attackers to forge log entries, hide their tracks, or inject malicious control characters.
|
|
11
|
+
|
|
12
|
+
**Incorrect (unsanitized logging):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func Handler(w http.ResponseWriter, r *http.Request) {
|
|
16
|
+
user := r.FormValue("user")
|
|
17
|
+
slog.Info("User logged in: " + user)
|
|
18
|
+
// Attacker: "admin\n[ERROR] Payment failed for user: victim"
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (sanitized structured logging):**
|
|
23
|
+
|
|
24
|
+
```go
|
|
25
|
+
// 1. Use structured logging (automatically handles quotes/escaping in key-value pairs)
|
|
26
|
+
slog.Info("User logged in", "username", sanitizeForLog(user))
|
|
27
|
+
|
|
28
|
+
// 2. Sanitize input
|
|
29
|
+
func sanitizeForLog(input string) string {
|
|
30
|
+
// Replace CRLF/tabs with space
|
|
31
|
+
replacer := strings.NewReplacer("\r", " ", "\n", " ", "\t", " ")
|
|
32
|
+
return replacer.Replace(input)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 3. Recommended: Use JSON logger in production
|
|
36
|
+
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
|
37
|
+
logger.Info("User logged in", "username", user)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Tools:** `log/slog`, `zap`, `gosec`
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Synchronized Time (UTC) In Logs
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: enables accurate incident correlation
|
|
5
|
+
tags: logging, time, utc, synchronization, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Synchronized Time (UTC) In Logs
|
|
9
|
+
|
|
10
|
+
Inconsistent timestamps across servers/services make incident investigation difficult. Use UTC and ISO 8601 format.
|
|
11
|
+
|
|
12
|
+
**Incorrect (local time/custom formats):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// Local time - depends on server TZ
|
|
16
|
+
log.Printf("Event time: %v", time.Now())
|
|
17
|
+
|
|
18
|
+
// Different formats
|
|
19
|
+
fmt.Println(time.Now().Unix())
|
|
20
|
+
fmt.Println(time.Now().Format("2006-01-02"))
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Correct (UTC, ISO 8601):**
|
|
24
|
+
|
|
25
|
+
```go
|
|
26
|
+
// Use .UTC() and .Format(time.RFC3339)
|
|
27
|
+
slog.Info("User action",
|
|
28
|
+
"timestamp", time.Now().UTC().Format(time.RFC3339Nano),
|
|
29
|
+
"action", "login",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
// Output: {"timestamp":"2024-01-15T10:30:00.123456Z", "level":"INFO", "message":"User action", "action":"login"}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Requirements:**
|
|
36
|
+
- Use UTC timezone externally.
|
|
37
|
+
- Use ISO 8601 (RFC 3339) with nanosecond/millisecond precision.
|
|
38
|
+
- Ensure servers are NTP-synchronized.
|
|
39
|
+
|
|
40
|
+
**Tools:** `time` (Standard Library), `slog`, NTP
|