@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,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Only Approved Crypto Algorithms
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: ensures cryptographic strength
|
|
5
|
+
tags: cryptography, algorithms, hashing, encryption, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Only Approved Crypto Algorithms
|
|
9
|
+
|
|
10
|
+
Weak algorithms are broken. MD5, SHA1, DES, and ECB mode have known vulnerabilities.
|
|
11
|
+
|
|
12
|
+
**Incorrect (weak algorithms):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
import (
|
|
16
|
+
"crypto/md5"
|
|
17
|
+
"crypto/des"
|
|
18
|
+
"crypto/cipher"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
// WEAK hash
|
|
22
|
+
h := md5.New()
|
|
23
|
+
h.Write([]byte(password))
|
|
24
|
+
hash := h.Sum(nil)
|
|
25
|
+
|
|
26
|
+
// WEAK algorithm
|
|
27
|
+
block, _ := des.NewCipher(key)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Correct (approved algorithms):**
|
|
31
|
+
|
|
32
|
+
```go
|
|
33
|
+
import (
|
|
34
|
+
"crypto/aes"
|
|
35
|
+
"crypto/cipher"
|
|
36
|
+
"crypto/sha256"
|
|
37
|
+
"golang.org/x/crypto/bcrypt"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
// STRONG hash (for data integrity)
|
|
41
|
+
h := sha256.New()
|
|
42
|
+
h.Write(data)
|
|
43
|
+
hash := h.Sum(nil)
|
|
44
|
+
|
|
45
|
+
// STRONG authenticated encryption (GCM mode)
|
|
46
|
+
block, _ := aes.NewCipher(key)
|
|
47
|
+
aesGCM, _ := cipher.NewGCM(block)
|
|
48
|
+
ciphertext := aesGCM.Seal(nil, nonce, plaintext, associatedData)
|
|
49
|
+
|
|
50
|
+
// For passwords - use specialized functions
|
|
51
|
+
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
52
|
+
err := bcrypt.CompareHashAndPassword(hashedPassword, []byte(password))
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Approved vs Prohibited:**
|
|
56
|
+
|
|
57
|
+
| Purpose | Approved | Prohibited |
|
|
58
|
+
|---------|----------|------------|
|
|
59
|
+
| Hash | SHA-256, SHA-3, BLAKE2 | MD5, SHA-1 |
|
|
60
|
+
| Encryption | AES-GCM, ChaCha20-Poly1305 | DES, 3DES, AES-ECB |
|
|
61
|
+
| Password | bcrypt, Argon2, scrypt | MD5, SHA-*, plain AES |
|
|
62
|
+
|
|
63
|
+
**Tools:** SonarQube, Semgrep, `crypto` (standard library)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use CSPRNG For Security Purposes
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: prevents predictable tokens and session hijacking
|
|
5
|
+
tags: random, csprng, tokens, session, cryptography, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use CSPRNG For Security Purposes
|
|
9
|
+
|
|
10
|
+
Non-cryptographic random generators are predictable. Attackers can guess session tokens, OTPs, and password reset links generated with weak random sources.
|
|
11
|
+
|
|
12
|
+
**Incorrect (predictable random):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
import "math/rand"
|
|
16
|
+
|
|
17
|
+
// INSECURE - predictable!
|
|
18
|
+
sessionId := fmt.Sprintf("%d", rand.Intn(1000000))
|
|
19
|
+
|
|
20
|
+
// INSECURE - Seeded with time isn't enough for security tokens
|
|
21
|
+
rand.Seed(time.Now().UnixNano())
|
|
22
|
+
token := rand.Int63()
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Correct (cryptographically secure):**
|
|
26
|
+
|
|
27
|
+
```go
|
|
28
|
+
import (
|
|
29
|
+
"crypto/rand"
|
|
30
|
+
"encoding/hex"
|
|
31
|
+
"math/big"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
// Cryptographically secure session ID
|
|
35
|
+
b := make([]byte, 32)
|
|
36
|
+
rand.Read(b)
|
|
37
|
+
sessionId := hex.EncodeToString(b) // 256-bit entropy
|
|
38
|
+
|
|
39
|
+
// Secure OTP generation
|
|
40
|
+
func generateOTP(length int) string {
|
|
41
|
+
max := big.NewInt(int64(math.Pow10(length)))
|
|
42
|
+
n, _ := rand.Int(rand.Reader, max)
|
|
43
|
+
return fmt.Sprintf("%0*d", length, n)
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**CSPRNG by language:**
|
|
48
|
+
|
|
49
|
+
| Language | Secure | Insecure |
|
|
50
|
+
|----------|--------|----------|
|
|
51
|
+
| Go | `crypto/rand` | `math/rand` |
|
|
52
|
+
|
|
53
|
+
**Tools:** SonarQube, Semgrep, `crypto/rand`
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Enable Encrypted Client Hello (ECH)
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: protects SNI from eavesdropping
|
|
5
|
+
tags: tls, ech, sni, privacy, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Enable Encrypted Client Hello (ECH)
|
|
9
|
+
|
|
10
|
+
ECH encrypts the Server Name Indication (SNI) to prevent network observers from seeing which site you're connecting to.
|
|
11
|
+
|
|
12
|
+
**About ECH:**
|
|
13
|
+
|
|
14
|
+
Encrypted Client Hello (formerly ESNI) is a TLS extension that encrypts the ClientHello message, hiding the destination hostname from network observers.
|
|
15
|
+
|
|
16
|
+
**Implementation:**
|
|
17
|
+
|
|
18
|
+
```nginx
|
|
19
|
+
# Nginx with ECH (when supported)
|
|
20
|
+
ssl_ech on;
|
|
21
|
+
ssl_ech_key /path/to/ech-private-key.pem;
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**DNS Configuration:**
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
# HTTPS DNS record for ECH
|
|
28
|
+
_https.example.com. IN HTTPS 1 . alpn="h2,h3" ipv4hint=192.0.2.1 ech="..."
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Go Support:**
|
|
32
|
+
Go's `crypto/tls` currently (as of Go 1.22) has limited native support for ECH, but it can be implemented via specialized packages or handled at the infrastructure level (e.g., Cloudflare).
|
|
33
|
+
|
|
34
|
+
**Tools:** Cloudflare ECH, DNS Configuration
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Secrets Management For Backend Secrets
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: centralizes and secures credential storage
|
|
5
|
+
tags: secrets, vault, credentials, configuration, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Secrets Management For Backend Secrets
|
|
9
|
+
|
|
10
|
+
Hardcoded secrets are exposed in version control and can be accessed by anyone with code access. Use dedicated secrets management systems.
|
|
11
|
+
|
|
12
|
+
**Incorrect (hardcoded or plain env files):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// Hardcoded in code
|
|
16
|
+
const APIKey = "sk-abc123xyz789"
|
|
17
|
+
|
|
18
|
+
// .env file committed to repo
|
|
19
|
+
DATABASE_URL=postgres://admin:password@localhost/db
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (secrets management):**
|
|
23
|
+
|
|
24
|
+
```go
|
|
25
|
+
// Using secrets manager (AWS, HashiCorp Vault, etc.)
|
|
26
|
+
dbPassword, _ := secretManager.GetSecret(ctx, "production/db-password")
|
|
27
|
+
|
|
28
|
+
// Kubernetes secrets
|
|
29
|
+
secret := os.Getenv("DB_PASSWORD") // Mounted from K8s secret
|
|
30
|
+
|
|
31
|
+
// Environment-specific with validation
|
|
32
|
+
config := struct {
|
|
33
|
+
DBPassword string
|
|
34
|
+
}{
|
|
35
|
+
DBPassword: os.Getenv("DB_PASSWORD"),
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if config.DBPassword == "" {
|
|
39
|
+
log.Fatal("DB_PASSWORD environment variable required")
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Best practices:**
|
|
44
|
+
- Never commit secrets to version control
|
|
45
|
+
- Use secrets rotation
|
|
46
|
+
- Audit secret access
|
|
47
|
+
- Use different secrets per environment
|
|
48
|
+
|
|
49
|
+
**Tools:** HashiCorp Vault, AWS Secrets Manager, Azure Key Vault
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Always Use TLS For All Connections
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: protects data in transit from eavesdropping
|
|
5
|
+
tags: tls, https, encryption, transport, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Always Use TLS For All Connections
|
|
9
|
+
|
|
10
|
+
Unencrypted traffic exposes data to anyone on the network path - ISPs, WiFi operators, and attackers.
|
|
11
|
+
|
|
12
|
+
**Incorrect (unencrypted connections):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// HTTP API calls
|
|
16
|
+
resp, _ := http.Get("http://api.example.com/users")
|
|
17
|
+
|
|
18
|
+
// Unencrypted database
|
|
19
|
+
db, _ := sql.Open("postgres", "postgres://user:pass@localhost/db?sslmode=disable")
|
|
20
|
+
|
|
21
|
+
// Redis without TLS
|
|
22
|
+
client := redis.NewClient(&redis.Options{
|
|
23
|
+
Addr: "localhost:6379",
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Correct (TLS everywhere):**
|
|
28
|
+
|
|
29
|
+
```go
|
|
30
|
+
// HTTPS for all APIs
|
|
31
|
+
resp, _ := http.Get("https://api.example.com/users")
|
|
32
|
+
|
|
33
|
+
// TLS for database
|
|
34
|
+
db, _ := sql.Open("postgres", "postgres://user:pass@localhost/db?sslmode=verify-full")
|
|
35
|
+
|
|
36
|
+
// Redis with TLS
|
|
37
|
+
client := redis.NewClient(&redis.Options{
|
|
38
|
+
Addr: "localhost:6380",
|
|
39
|
+
TLSConfig: &tls.Config{...},
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// Force HTTPS in a Go web server
|
|
43
|
+
func enforceHTTPS(next http.Handler) http.Handler {
|
|
44
|
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
45
|
+
if r.Header.Get("X-Forwarded-Proto") != "https" && os.Getenv("ENV") == "production" {
|
|
46
|
+
http.Redirect(w, r, "https://"+r.Host+r.RequestURI, http.StatusMovedPermanently)
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
next.ServeHTTP(w, r)
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Checklist:**
|
|
55
|
+
- [ ] All HTTP → HTTPS
|
|
56
|
+
- [ ] Database connections encrypted (sslmode=verify-full)
|
|
57
|
+
- [ ] Redis/memcached TLS
|
|
58
|
+
- [ ] Message queues TLS
|
|
59
|
+
- [ ] HSTS headers enabled
|
|
60
|
+
|
|
61
|
+
**Tools:** OWASP ZAP, SSLyze, `crypto/tls`
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Do Not Pass Sensitive Data In Query String
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: prevents credential leakage in logs and history
|
|
5
|
+
tags: url, query-string, sensitive-data, leakage, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Do Not Pass Sensitive Data In Query String
|
|
9
|
+
|
|
10
|
+
Query strings appear in server logs, browser history, referrer headers, and can be cached by proxies and CDNs.
|
|
11
|
+
|
|
12
|
+
**Incorrect (sensitive data in URL):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// Tokens in URL
|
|
16
|
+
http.Get(fmt.Sprintf("https://api.example.com/data?token=%s", accessToken))
|
|
17
|
+
|
|
18
|
+
// Password in URL
|
|
19
|
+
http.Post(fmt.Sprintf("https://api.example.com/login?user=admin&pass=%s", password), "application/json", nil)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (sensitive data in body/headers):**
|
|
23
|
+
|
|
24
|
+
```go
|
|
25
|
+
// Token in header
|
|
26
|
+
req, _ := http.NewRequest("GET", "https://api.example.com/data", nil)
|
|
27
|
+
req.Header.Set("Authorization", "Bearer "+accessToken)
|
|
28
|
+
client.Do(req)
|
|
29
|
+
|
|
30
|
+
// Credentials in body
|
|
31
|
+
payload := map[string]string{"user": "admin", "pass": password}
|
|
32
|
+
jsonPayload, _ := json.Marshal(payload)
|
|
33
|
+
http.Post("https://api.example.com/login", "application/json", bytes.NewBuffer(jsonPayload))
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Where query strings leak:**
|
|
37
|
+
- Server access logs
|
|
38
|
+
- Browser history
|
|
39
|
+
- Referrer headers
|
|
40
|
+
- Proxy/CDN logs
|
|
41
|
+
|
|
42
|
+
**Tools:** Semgrep, Manual Review, Proxy log scanner
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Always Use Parameterized Queries
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: prevents SQL and NoSQL injection attacks
|
|
5
|
+
tags: injection, sql, nosql, database, parameterized, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Always Use Parameterized Queries
|
|
9
|
+
|
|
10
|
+
SQL injection allows attackers to execute arbitrary database commands, steal data, or destroy databases.
|
|
11
|
+
|
|
12
|
+
**Incorrect (string concatenation):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// SQL Injection vulnerability
|
|
16
|
+
userId := r.URL.Query().Get("id")
|
|
17
|
+
query := fmt.Sprintf("SELECT * FROM users WHERE id = '%s'", userId)
|
|
18
|
+
db.Query(query)
|
|
19
|
+
|
|
20
|
+
// Attacker input: ' OR '1'='1
|
|
21
|
+
// Resulting query: SELECT * FROM users WHERE id = '' OR '1'='1'
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Correct (parameterized queries):**
|
|
25
|
+
|
|
26
|
+
```go
|
|
27
|
+
// Parameterized query using database/sql
|
|
28
|
+
userId := r.URL.Query().Get("id")
|
|
29
|
+
db.Query("SELECT * FROM users WHERE id = ?", userId) // Postgres uses $1, $2
|
|
30
|
+
|
|
31
|
+
// Using GORM (safely handles parameters)
|
|
32
|
+
var user User
|
|
33
|
+
db.First(&user, "id = ?", userId)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Tools:** SonarQube, Semgrep, `sqlclosecheck`, `gosec` (G201, G202)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Sanitize Input Before Sending Emails
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: prevents email header injection
|
|
5
|
+
tags: email, injection, sanitization, input-validation, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Sanitize Input Before Sending Emails
|
|
9
|
+
|
|
10
|
+
Email header injection allows attackers to add recipients, change headers, or send spam through your system.
|
|
11
|
+
|
|
12
|
+
**Incorrect (unsanitized email input):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// Email injection vulnerability
|
|
16
|
+
subject := r.FormValue("subject") // "Hello\r\nBcc: spam@evil.com"
|
|
17
|
+
msg := []byte("Subject: " + subject + "\r\n\r\n" + "Body")
|
|
18
|
+
smtp.SendMail("smtp.example.com:25", auth, from, to, msg)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct (sanitized email fields):**
|
|
22
|
+
|
|
23
|
+
```go
|
|
24
|
+
func sanitizeEmailField(input string) string {
|
|
25
|
+
// Remove CRLF characters that could inject headers
|
|
26
|
+
return strings.NewReplacer("\r", "", "\n", "").Replace(input)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
func validateEmail(email string) bool {
|
|
30
|
+
// Use a robust regex or net/mail
|
|
31
|
+
_, err := mail.ParseAddress(email)
|
|
32
|
+
return err == nil && !strings.ContainsAny(email, "\r\n")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// In handler
|
|
36
|
+
subject := sanitizeEmailField(r.FormValue("subject"))
|
|
37
|
+
to := r.FormValue("to")
|
|
38
|
+
if !validateEmail(to) {
|
|
39
|
+
http.Error(w, "Invalid email", 400)
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Tools:** Email Libraries with Built-in Protection, Manual Review, `net/mail`
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Avoid Dynamic Code Execution
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: prevents remote code execution vulnerabilities
|
|
5
|
+
tags: eval, code-execution, rce, injection, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Avoid Dynamic Code Execution
|
|
9
|
+
|
|
10
|
+
Executing arbitrary strings as code is extremely dangerous. Attackers can run any code on your server. While Go doesn't have a native `eval()` function, similar risks exist with certain libraries or `os/exec`.
|
|
11
|
+
|
|
12
|
+
**Incorrect (dynamic execution/dangerous OS calls):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// INSECURE: Executing user-provided command
|
|
16
|
+
cmd := exec.Command("bash", "-c", r.URL.Query().Get("cmd"))
|
|
17
|
+
cmd.Run()
|
|
18
|
+
|
|
19
|
+
// INSECURE: Using a dangerous expression library with unsanitized input
|
|
20
|
+
result, _ := govau.Eval(r.URL.Query().Get("formula"))
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Correct (safe alternatives):**
|
|
24
|
+
|
|
25
|
+
```go
|
|
26
|
+
// Use switch/mapping for dynamic behavior
|
|
27
|
+
var operations = map[string]func(int, int) int{
|
|
28
|
+
"add": func(a, b int) int { return a + b },
|
|
29
|
+
"subtract": func(a, b int) int { return a - b },
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
opFunc, ok := operations[r.FormValue("op")]
|
|
33
|
+
if !ok {
|
|
34
|
+
http.Error(w, "Invalid operation", 400)
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
result := opFunc(a, b)
|
|
38
|
+
|
|
39
|
+
// Use safe parsers for math
|
|
40
|
+
// import "github.com/Knetic/govaluate"
|
|
41
|
+
expression, _ := govaluate.NewEvaluable("10 + x")
|
|
42
|
+
parameters := make(map[string]interface{})
|
|
43
|
+
parameters["x"] = 5
|
|
44
|
+
result, _ := expression.Evaluate(parameters)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Tools:** `gosec` (G204), Semgrep, Code Review
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Escape Data By Output Context
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: ensures correct encoding for each output context
|
|
5
|
+
tags: xss, escaping, context, encoding, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Escape Data By Output Context
|
|
9
|
+
|
|
10
|
+
Different contexts require different escaping strategies. Using HTML encoding in a JavaScript context doesn't prevent XSS.
|
|
11
|
+
|
|
12
|
+
**Incorrect (wrong encoding for context):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// Wrong: same escape for all contexts
|
|
16
|
+
escaped := html.EscapeString(userInput)
|
|
17
|
+
fmt.Fprintf(w, "<script>var x = '%s';</script>", escaped) // Still vulnerable!
|
|
18
|
+
|
|
19
|
+
// Wrong: no header injection protection
|
|
20
|
+
w.Header().Set("X-Custom", userInput) // Header injection!
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Correct (context-appropriate encoding):**
|
|
24
|
+
|
|
25
|
+
```go
|
|
26
|
+
import (
|
|
27
|
+
"encoding/json"
|
|
28
|
+
"html"
|
|
29
|
+
"net/url"
|
|
30
|
+
"strings"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
// HTML content context
|
|
34
|
+
fmt.Fprintf(w, "<p>%s</p>", html.EscapeString(userInput))
|
|
35
|
+
|
|
36
|
+
// JavaScript context
|
|
37
|
+
jsData, _ := json.Marshal(userInput)
|
|
38
|
+
fmt.Fprintf(w, "<script>var x = %s;</script>", jsData)
|
|
39
|
+
|
|
40
|
+
// URL parameter context
|
|
41
|
+
urlParam := url.QueryEscape(userInput)
|
|
42
|
+
http.Redirect(w, r, "/search?q="+urlParam, 302)
|
|
43
|
+
|
|
44
|
+
// HTTP header context - strip CRLF
|
|
45
|
+
safeHeader := strings.NewReplacer("\r", "", "\n", "").Replace(userInput)
|
|
46
|
+
w.Header().Set("X-Custom", safeHeader)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Tools:** `html/template` (handles context automatically), `gosec`
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Output Encoding For Dynamic JS/JSON
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: prevents injection in JavaScript contexts
|
|
5
|
+
tags: xss, javascript, json, encoding, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Output Encoding For Dynamic JS/JSON
|
|
9
|
+
|
|
10
|
+
Embedding user data in JavaScript or JSON requires proper encoding to prevent code injection.
|
|
11
|
+
|
|
12
|
+
**Incorrect (unescaped data in JS):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// XSS in inline script
|
|
16
|
+
func ProfileHandler(w http.ResponseWriter, r *http.Request) {
|
|
17
|
+
username := r.Context().Value("username").(string) // "</script><script>alert('xss')"
|
|
18
|
+
fmt.Fprintf(w, "<script>var user = '%s';</script>", username)
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (proper JSON encoding):**
|
|
23
|
+
|
|
24
|
+
```go
|
|
25
|
+
func ProfileHandler(w http.ResponseWriter, r *http.Request) {
|
|
26
|
+
user := struct {
|
|
27
|
+
Name string `json:"name"`
|
|
28
|
+
}{
|
|
29
|
+
Name: "User Name",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// json.Marshal properly escapes special characters
|
|
33
|
+
safeData, _ := json.Marshal(user)
|
|
34
|
+
|
|
35
|
+
fmt.Fprintf(w, `
|
|
36
|
+
<script>
|
|
37
|
+
var user = %s;
|
|
38
|
+
</script>
|
|
39
|
+
`, safeData)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Using html/template (SAFE)
|
|
43
|
+
tmpl := template.Must(template.New("profile").Parse(`
|
|
44
|
+
<script>
|
|
45
|
+
var user = {{.}};
|
|
46
|
+
</script>
|
|
47
|
+
`))
|
|
48
|
+
tmpl.Execute(w, user) // Automically encodes as JSON for JS context
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Tools:** `html/template`, `json.Marshal`
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Always Validate Client Data Server-side
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: ensures input validation cannot be bypassed
|
|
5
|
+
tags: validation, server-side, input, sanitization, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Always Validate Client Data Server-side
|
|
9
|
+
|
|
10
|
+
Client-side validation is for UX only - it can be bypassed easily. All input must be validated server-side.
|
|
11
|
+
|
|
12
|
+
**Incorrect (trusting client validation):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// No server validation - trusting frontend
|
|
16
|
+
func TransferHandler(w http.ResponseWriter, r *http.Request) {
|
|
17
|
+
amount, _ := strconv.Atoi(r.FormValue("amount"))
|
|
18
|
+
toAccount := r.FormValue("toAccount")
|
|
19
|
+
transferMoney(r.Context().Value("userId").(string), toAccount, amount)
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Correct (comprehensive server validation):**
|
|
24
|
+
|
|
25
|
+
```go
|
|
26
|
+
import "github.com/go-playground/validator/v10"
|
|
27
|
+
|
|
28
|
+
type TransferRequest struct {
|
|
29
|
+
Amount int `validate:"required,gt=0,lte=10000"`
|
|
30
|
+
ToAccount string `validate:"required,printascii,len=20"`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func TransferHandler(w http.ResponseWriter, r *http.Request) {
|
|
34
|
+
var req TransferRequest
|
|
35
|
+
// Parse and validate
|
|
36
|
+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
37
|
+
http.Error(w, "Invalid request", 400)
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
validate := validator.New()
|
|
42
|
+
if err := validate.Struct(req); err != nil {
|
|
43
|
+
http.Error(w, err.Error(), 400)
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Additional business validation
|
|
48
|
+
if !accountExists(req.ToAccount) {
|
|
49
|
+
http.Error(w, "Account not found", 404)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
transferMoney(r.Context().Value("userId").(string), req.ToAccount, req.Amount)
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Tools:** `go-playground/validator`, `ozzo-validation`, `asaskevich/govalidator`
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: TLS Encryption For All Connections
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: protects data in transit from interception
|
|
5
|
+
tags: tls, encryption, https, transport, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## TLS Encryption For All Connections
|
|
9
|
+
|
|
10
|
+
All network communications must use TLS to prevent eavesdropping and man-in-the-middle attacks.
|
|
11
|
+
|
|
12
|
+
**Incorrect (unencrypted connections):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// HTTP instead of HTTPS
|
|
16
|
+
http.Get("http://api.example.com/data")
|
|
17
|
+
|
|
18
|
+
// Unencrypted database connection
|
|
19
|
+
sql.Open("postgres", "host=db.example.com sslmode=disable")
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (TLS everywhere):**
|
|
23
|
+
|
|
24
|
+
```go
|
|
25
|
+
// HTTPS for all external calls
|
|
26
|
+
http.Get("https://api.example.com/data")
|
|
27
|
+
|
|
28
|
+
// TLS for database
|
|
29
|
+
sql.Open("postgres", "host=db.example.com sslmode=verify-full")
|
|
30
|
+
|
|
31
|
+
// HSTS header in Go
|
|
32
|
+
func hstsMiddleware(next http.Handler) http.Handler {
|
|
33
|
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
34
|
+
w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
|
|
35
|
+
next.ServeHTTP(w, r)
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Requirements:**
|
|
41
|
+
- All HTTP endpoints must redirect to HTTPS
|
|
42
|
+
- Database connections must use TLS (verify-full)
|
|
43
|
+
- Internal service-to-service calls (gRPC/HTTP) must use TLS
|
|
44
|
+
- HSTS headers should be enabled
|
|
45
|
+
|
|
46
|
+
**Tools:** `crypto/tls`, SSLyze, Qualys SSL Labs
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Validate mTLS Certificates Before Auth
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: ensures mutual authentication between services
|
|
5
|
+
tags: mtls, certificates, authentication, service-mesh, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Validate mTLS Certificates Before Auth
|
|
9
|
+
|
|
10
|
+
Mutual TLS ensures both parties are authenticated. Always validate client certificates before processing requests.
|
|
11
|
+
|
|
12
|
+
**Incorrect (skipping certificate validation):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// Accepting any client certificate
|
|
16
|
+
server := &http.Server{
|
|
17
|
+
TLSConfig: &tls.Config{
|
|
18
|
+
ClientAuth: tls.RequestClientCert, // Does NOT reject invalid certs
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Correct (proper mTLS validation):**
|
|
24
|
+
|
|
25
|
+
```go
|
|
26
|
+
// Load CA cert
|
|
27
|
+
caCert, _ := os.ReadFile("ca.crt")
|
|
28
|
+
caCertPool := x509.NewCertPool()
|
|
29
|
+
caCertPool.AppendCertsFromPEM(caCert)
|
|
30
|
+
|
|
31
|
+
tlsConfig := &tls.Config{
|
|
32
|
+
ClientCAs: caCertPool,
|
|
33
|
+
ClientAuth: tls.RequireAndVerifyClientCert, // Require AND Verify
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
server := &http.Server{
|
|
37
|
+
TLSConfig: tlsConfig,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Additional validation in handler
|
|
41
|
+
func handler(w http.ResponseWriter, r *http.Request) {
|
|
42
|
+
if len(r.TLS.PeerCertificates) > 0 {
|
|
43
|
+
cert := r.TLS.PeerCertificates[0]
|
|
44
|
+
if cert.Subject.CommonName != "trusted-service" {
|
|
45
|
+
http.Error(w, "Forbidden", 403)
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Tools:** `crypto/tls`, `crypto/x509`, Service Mesh (Istio, Linkerd)
|