-
Notifications
You must be signed in to change notification settings - Fork 34
Expand file tree
/
Copy pathsecure-dev-golang.mdc
More file actions
268 lines (224 loc) · 8.93 KB
/
secure-dev-golang.mdc
File metadata and controls
268 lines (224 loc) · 8.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
---
description: This rule contains important information about secure coding
globs: **/*.go
alwaysApply: false
---
These rules apply to all Go code in the repository (CLI tools, services, handlers, background jobs) and aim to prevent common security risks through disciplined input handling, safe APIs, and secure defaults.
All violations must include a clear explanation of which rule was triggered and why, so developers can fix issues quickly.\
Generated code must not violate these rules. If a rule is violated, add a code comment that explains the problem and proposes a correction.
## 1. Decode Untrusted Data Safely
- **Rule:** Do not deserialize untrusted data with unsafe or permissive decoders. Prefer strict JSON or protobuf with size limits. Reject unknown fields. Avoid `encoding/gob` for untrusted input. Use strict YAML decoding only if required.
- **Unsafe:**
```go
// Accepts arbitrarily large input and unknown fields
var in any
_ = json.NewDecoder(r.Body).Decode(&in)
```
- **Safe:**
```go
type CreateUser struct {
Name string `json:"name"`
Email string `json:"email"`
}
dec := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)) // 1 MB cap
dec.DisallowUnknownFields()
dec.UseNumber()
var in CreateUser
if err := dec.Decode(&in); err != nil { /* handle */ }
```
- **YAML (only if needed):**
```go
dec := yaml.NewDecoder(bytes.NewReader(b))
dec.KnownFields(true) // yaml.v3
if err := dec.Decode(&cfg); err != nil { /* handle */ }
```
- **Protobuf JSON:**
```go
opts := protojson.UnmarshalOptions{DiscardUnknown: false}
if err := opts.Unmarshal(b, msg); err != nil { /* handle */ }
```
## 2. Use Parameterized Queries for Database Access
- **Rule:** Never format SQL with user input. Use placeholders and arguments. Use context with timeouts.
- **Unsafe:**
```go
query := fmt.Sprintf("SELECT * FROM users WHERE name = '%s'", name)
rows, _ := db.Query(query)
```
- **Safe:**
```go
ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
defer cancel()
row := db.QueryRowContext(ctx, "SELECT id FROM users WHERE name = $1", name)
```
## 3. Prevent Command Injection
- **Rule:** Do not pass untrusted input to shells. Use `exec.CommandContext` with fixed program and separate args. Validate inputs against allow lists.
- **Unsafe:**
```go
exec.Command("sh", "-c", "ls "+userArg).Run()
```
- **Safe:**
```go
// validatedArg must pass strict allow list or regex
cmd := exec.CommandContext(ctx, "ls", validatedArg)
cmd.Stdout = w
cmd.Stderr = w
_ = cmd.Run()
```
## 4. File and Path Safety
- **Rule:** Block path traversal and restrict file permissions. Keep writes inside an allow-listed base directory.
- **Unsafe:**
```go
// userPath like "../../etc/shadow"
f, _ := os.Create(filepath.Join(base, userPath))
```
- **Safe:**
```go
clean := filepath.Clean("/" + userRel) // force relative
full := filepath.Join(base, clean)
rel, err := filepath.Rel(base, full)
if err != nil || strings.HasPrefix(rel, "..") {
return fmt.Errorf("outside base dir")
}
f, err := os.OpenFile(full, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0600)
```
## 5. Secure HTTP Server Defaults
- **Rule:** Set timeouts, cap headers and bodies, and avoid unsafe defaults.
- **Safe:**
```go
mux := http.NewServeMux()
// Wrap handlers to cap body size per request
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, 1<<20)
// ...
})
srv := &http.Server{
Addr: ":8080",
Handler: handler,
ReadTimeout: 10 * time.Second,
ReadHeaderTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 60 * time.Second,
MaxHeaderBytes: 1 << 20,
}
```
- **Also:** Always use `Context` on outbound calls, enforce per request deadlines, and close response bodies.
## 6. Template Safety
- **Rule:** Use `html/template` for HTML to get auto-escaping. Never use `text/template` for HTML.
- **Unsafe:**
```go
t := template.Must(template.New("x").Parse("<div>{{.UserInput}}</div>"))
```
- **Safe:**
```go
t := template.Must(htmltemplate.New("x").Parse("<div>{{.UserInput}}</div>"))
```
## 7. Log and Error Hygiene
- **Rule:** Do not log secrets, tokens, personal data, or full request bodies. Redact sensitive fields. Return generic error messages to clients. Use a recover middleware to hide panics.
- **Safe:**
```go
// Example redaction
logger.Info("login", "user", in.Email, "token", "[redacted]")
// Recover middleware
func Recover(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rec := recover(); rec != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
```
## 8. Strong Cryptography and Randomness
- **Rule:** Use `crypto/rand` for secrets and IDs. Do not hardcode keys. Prefer modern AEADs such as AES-GCM or ChaCha20-Poly1305. Use Argon2id or scrypt for password hashing.
- **Safe:**
```go
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil { /* handle */ }
token := base64.RawURLEncoding.EncodeToString(b)
```
## 9. TLS and Outbound HTTP Safety
- **Rule:** Do not set `InsecureSkipVerify: true`. Pin minimum TLS version. Defend against SSRF with host allow lists and IP checks.
- **Unsafe:**
```go
tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
```
- **Safe:**
```go
tr := &http.Transport{
TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12},
}
client := &http.Client{Transport: tr, Timeout: 10 * time.Second}
```
- **SSRF hint:** Resolve hostnames and reject private or link-local IPs before dialing. Allow list destinations wherever possible.
## 10. JWT and Token Validation
- **Rule:** Verify signature, issuer, audience, expiry, and not-before. Reject `alg` none. Prefer asymmetric algorithms like RS256 or EdDSA. Enforce short TTL and rotation.
- **Unsafe:**
```go
token, _ := jwt.Parse(tokStr, nil) // no keyfunc, accepts none
```
- **Safe:**
```go
token, err := jwt.Parse(tokStr, func(t *jwt.Token) (any, error) {
if t.Method != jwt.SigningMethodRS256 { return nil, fmt.Errorf("alg mismatch") }
return pubKey, nil
})
```
## 11. Concurrency and Race Safety
- **Rule:** Use the race detector in CI. Avoid TOCTOU on files and permissions. Guard shared state.
- **Safe file create without race:**
```go
f, err := os.OpenFile(p, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600)
```
## 12. Cookies and Sessions
- **Rule:** Set `Secure`, `HttpOnly`, and an appropriate `SameSite`. Do not store secrets in client cookies unless encrypted and signed with a server key.
- **Safe:**
```go
http.SetCookie(w, &http.Cookie{
Name: "sid",
Value: token,
Secure: true,
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
Path: "/",
})
```
## 13. CSRF and CORS
- **Rule:** Use CSRF protections for state-changing requests in browser-based apps. For CORS, allow list origins and avoid `Access-Control-Allow-Origin: *` with credentials.
- **Safe CORS example:**
```go
// Pseudocode: only allow https://app.example.com
w.Header().Set("Access-Control-Allow-Origin", "https://app.example.com")
w.Header().Set("Vary", "Origin")
```
## 14. Avoid Reflection, `unsafe`, and Cgo for Untrusted Data
- **Rule:** Do not use `reflect`, `unsafe`, or Cgo to parse or transform untrusted inputs. Keep type boundaries strict.
## 15. Dependencies and Toolchain
- **Rule:** Keep modules updated and audited. Use `govulncheck` in CI. Track your Go version and upgrade regularly.
- **Safe:**
```bash
go vet ./...
go test -race ./...
govulncheck ./...
go list -m -u all
```
## 16. Operational Hardening
- **Rule:** Run with least privilege. In containers, avoid root, drop capabilities, mount only what you need. Never expose debug endpoints publicly.
- **Unsafe:**
```go
// Exposing pprof on 0.0.0.0 in prod
http.ListenAndServe(":6060", http.DefaultServeMux)
```
- **Safe:**
```go
// Bind pprof to localhost only, or protect with auth and network policy
go func() { _ = http.ListenAndServe("127.0.0.1:6060", nil) }()
```
---
### Additional Guidance
- Validate inputs early: type, length, format, and allow lists.
- Cap all untrusted input sizes: HTTP bodies, file uploads, compressed archives.
- Do not echo attacker-controlled data in errors, HTML, or logs.
- Prefer `context.Context` for cancellation and deadlines across all I/O.
- Document any exception to these rules in code with a clear rationale and a follow-up task to remove the exception.