ai-developer-skill-os 8.1.7 → 8.1.9

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.
Files changed (41) hide show
  1. package/.agents/README.md +3 -3
  2. package/.agents/skills/qk-code-review/SKILL.md +189 -0
  3. package/.agents/skills/qk-code-review/references/ai/ai-anti-patterns.md +28 -0
  4. package/.agents/skills/qk-code-review/references/ai/v8-schema-validation.md +65 -0
  5. package/.agents/skills/qk-code-review/references/cross-cutting/architecture-review-guide.md +212 -0
  6. package/.agents/skills/qk-code-review/references/cross-cutting/async-concurrency-patterns.md +515 -0
  7. package/.agents/skills/qk-code-review/references/cross-cutting/code-quality-universal.md +358 -0
  8. package/.agents/skills/qk-code-review/references/cross-cutting/code-review-best-practices.md +136 -0
  9. package/.agents/skills/qk-code-review/references/cross-cutting/common-bugs-checklist.md +124 -0
  10. package/.agents/skills/qk-code-review/references/cross-cutting/error-handling-principles.md +492 -0
  11. package/.agents/skills/qk-code-review/references/cross-cutting/n-plus-one-queries.md +309 -0
  12. package/.agents/skills/qk-code-review/references/cross-cutting/performance-review-guide.md +387 -0
  13. package/.agents/skills/qk-code-review/references/cross-cutting/security-review-guide.md +318 -0
  14. package/.agents/skills/qk-code-review/references/cross-cutting/sql-injection-prevention.md +308 -0
  15. package/.agents/skills/qk-code-review/references/cross-cutting/xss-prevention.md +264 -0
  16. package/.agents/skills/qk-code-review/references/languages/angular.md +768 -0
  17. package/.agents/skills/qk-code-review/references/languages/c.md +890 -0
  18. package/.agents/skills/qk-code-review/references/languages/cpp.md +893 -0
  19. package/.agents/skills/qk-code-review/references/languages/csharp.md +519 -0
  20. package/.agents/skills/qk-code-review/references/languages/css-less-sass.md +661 -0
  21. package/.agents/skills/qk-code-review/references/languages/django.md +985 -0
  22. package/.agents/skills/qk-code-review/references/languages/fastapi.md +580 -0
  23. package/.agents/skills/qk-code-review/references/languages/go.md +993 -0
  24. package/.agents/skills/qk-code-review/references/languages/java.md +409 -0
  25. package/.agents/skills/qk-code-review/references/languages/java8.md +586 -0
  26. package/.agents/skills/qk-code-review/references/languages/kotlin.md +1018 -0
  27. package/.agents/skills/qk-code-review/references/languages/nestjs.md +593 -0
  28. package/.agents/skills/qk-code-review/references/languages/php.md +684 -0
  29. package/.agents/skills/qk-code-review/references/languages/python.md +1073 -0
  30. package/.agents/skills/qk-code-review/references/languages/qt.md +757 -0
  31. package/.agents/skills/qk-code-review/references/languages/react.md +871 -0
  32. package/.agents/skills/qk-code-review/references/languages/ruby.md +964 -0
  33. package/.agents/skills/qk-code-review/references/languages/rust.md +846 -0
  34. package/.agents/skills/qk-code-review/references/languages/svelte.md +1064 -0
  35. package/.agents/skills/qk-code-review/references/languages/swift.md +936 -0
  36. package/.agents/skills/qk-code-review/references/languages/typescript.md +1016 -0
  37. package/.agents/skills/qk-code-review/references/languages/vue.md +924 -0
  38. package/.agents/skills/qk-code-review/references/languages/zig.md +440 -0
  39. package/README.md +3 -3
  40. package/bin/install.js +5 -2
  41. package/package.json +1 -1
@@ -0,0 +1,318 @@
1
+ # Hướng dẫn Review Bảo mật (Security Review Guide)
2
+
3
+ Checklist code review tập trung vào Bảo mật dựa trên OWASP Top 10 và các best practice của ngành.
4
+
5
+ ## Authentication & Authorization (Xác thực & Phân quyền)
6
+
7
+ ### Authentication (Xác thực)
8
+ - [ ] Passwords được băm (hash) bằng thuật toán mạnh (bcrypt, argon2)
9
+ - [ ] Bắt buộc độ phức tạp của password (Complexity requirements)
10
+ - [ ] Khóa tài khoản (Account lockout) sau nhiều lần đăng nhập sai
11
+ - [ ] Luồng reset password an toàn
12
+ - [ ] Xác thực đa yếu tố (MFA) cho các thao tác nhạy cảm
13
+ - [ ] Session tokens là chuỗi ngẫu nhiên chuẩn mật mã học (cryptographically random)
14
+ - [ ] Cài đặt timeout cho Session
15
+
16
+ ### Authorization (Phân quyền)
17
+ - [ ] Có bước kiểm tra phân quyền (Authorization checks) trên MỌI request
18
+ - [ ] Áp dụng nguyên tắc Đặc quyền tối thiểu (Principle of least privilege)
19
+ - [ ] Phân quyền dựa trên Role (RBAC) được triển khai đúng cách
20
+ - [ ] Không có lỗ hổng leo thang đặc quyền (Privilege escalation paths)
21
+ - [ ] Kiểm tra tham chiếu đối tượng trực tiếp (IDOR prevention)
22
+ - [ ] Các endpoint API được bảo vệ thích hợp
23
+
24
+ ### Bảo mật JWT
25
+ ```typescript
26
+ // ❌ Cấu hình JWT không an toàn (Secret yếu)
27
+ jwt.sign(payload, 'weak-secret');
28
+
29
+ // ✅ Cấu hình JWT an toàn
30
+ jwt.sign(payload, process.env.JWT_SECRET, {
31
+ algorithm: 'RS256',
32
+ expiresIn: '15m',
33
+ issuer: 'your-app',
34
+ audience: 'your-api'
35
+ });
36
+
37
+ // ❌ Không xác minh JWT đàng hoàng
38
+ const decoded = jwt.decode(token); // Không verify chữ ký!
39
+
40
+ // ✅ Xác minh chữ ký và các claims
41
+ const decoded = jwt.verify(token, publicKey, {
42
+ algorithms: ['RS256'],
43
+ issuer: 'your-app',
44
+ audience: 'your-api'
45
+ });
46
+ ```
47
+
48
+ ## Input Validation (Kiểm duyệt đầu vào)
49
+
50
+ ### Phòng chống SQL Injection
51
+
52
+ **Quy tắc số #1**: Luôn luôn sử dụng Parameterized queries (Truy vấn có tham số). Tuyệt đối không nối chuỗi input của user vào lệnh SQL.
53
+
54
+ Mọi ngôn ngữ và framework lớn đều hỗ trợ:
55
+ - Python: `cursor.execute("SELECT ...", params)` / ORM filter methods
56
+ - Java: `PreparedStatement` / JPA `@Query` với `@Param`
57
+ - Go: `db.Query("SELECT ...", args...)`
58
+ - Node.js: `client.query("SELECT ...", [args])` / Prisma ORM
59
+ - PHP: PDO prepared statements / Laravel Eloquent
60
+ - C#: ADO.NET `SqlParameter` / Dapper / EF Core LINQ
61
+
62
+ ### Phòng chống XSS (Cross-Site Scripting)
63
+
64
+ **Quy tắc số #1**: Phụ thuộc vào tính năng auto-escaping của Framework. Kiểm toán gắt gao mọi "lỗ hổng thoát" (escape hatch).
65
+
66
+ Các framework hiện đại đều auto-escape theo mặc định:
67
+ - React: JSX tự động escape. Kiểm toán `dangerouslySetInnerHTML`.
68
+ - Vue: `{{ }}` tự động escape. Kiểm toán `v-html`.
69
+ - Angular: Interpolation tự động escape. Kiểm toán `bypassSecurityTrustHtml`.
70
+ - Svelte: `{ }` tự động escape. Kiểm toán `{@html}`.
71
+ - C# (Razor): `@` tự động escape. Kiểm toán `@Html.Raw()`.
72
+
73
+ Để phòng thủ sâu (defense-in-depth), hãy cấu hình Content Security Policy (CSP) với `script-src` dựa trên nonce.
74
+
75
+ ### Phòng chống CSRF (Cross-Site Request Forgery)
76
+
77
+ **Triển khai CSRF Token**
78
+ ```typescript
79
+ // ✅ Server: Sinh và xác nhận CSRF token
80
+ import crypto from 'node:crypto';
81
+
82
+ function generateCsrfToken(): string {
83
+ return crypto.randomBytes(32).toString('hex');
84
+ }
85
+
86
+ // Middleware: Validate token trên các request thay đổi trạng thái
87
+ app.post('/api/data', (req, res) => {
88
+ const token = req.headers['x-csrf-token'];
89
+ const sessionToken = req.session.csrfToken;
90
+ if (!token || token !== sessionToken) {
91
+ return res.status(403).json({ error: 'Invalid CSRF token' });
92
+ }
93
+ // ...xử lý request
94
+ });
95
+ ```
96
+
97
+ **C# / ASP.NET Core**
98
+ ```csharp
99
+ // ✅ ASP.NET Core: CSRF token (Antiforgery) được tích hợp sẵn
100
+ builder.Services.AddAntiforgery(options =>
101
+ {
102
+ options.HeaderName = "X-CSRF-TOKEN";
103
+ });
104
+
105
+ // Middleware
106
+ app.UseAntiforgery();
107
+ ```
108
+
109
+ **SameSite Cookie**
110
+ ```typescript
111
+ // ✅ Set SameSite cookie làm hàng rào phòng thủ phụ
112
+ res.cookie('session', sessionId, {
113
+ httpOnly: true,
114
+ secure: true,
115
+ sameSite: 'strict', // Hoặc 'lax' nếu cho phép điều hướng GET
116
+ maxAge: 3600000,
117
+ });
118
+ ```
119
+
120
+ ### Phòng chống SSRF (Server-Side Request Forgery)
121
+
122
+ ```typescript
123
+ // ❌ Lỗ hổng SSRF: URL bị điều khiển bởi người dùng
124
+ const url = req.query.url;
125
+ const response = await fetch(url);
126
+
127
+ // ✅ Validate URL trước khi fetch
128
+ const ALLOWED_DOMAINS = ['api.internal.com'];
129
+
130
+ function isSafeUrl(url: string): boolean {
131
+ try {
132
+ const parsed = new URL(url);
133
+ // Block IP nội bộ (localhost)
134
+ if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1') {
135
+ return false;
136
+ }
137
+ // Block dải mạng Private IP
138
+ if (parsed.hostname.match(/^10\.|^172\.(1[6-9]|2\d|3[01])\.|^192\.168\./)) {
139
+ return false;
140
+ }
141
+ return ALLOWED_DOMAINS.includes(parsed.hostname);
142
+ } catch {
143
+ return false;
144
+ }
145
+ }
146
+ ```
147
+
148
+ ### IDOR (Insecure Direct Object Reference)
149
+
150
+ ```csharp
151
+ // ❌ Lỗ hổng: Không kiểm tra quyền sở hữu
152
+ [HttpGet("api/orders/{id}")]
153
+ public async Task<IActionResult> GetOrder(int id)
154
+ {
155
+ var order = await _db.Orders.FindAsync(id); // Bất kỳ ai cũng lấy được đơn hàng của người khác
156
+ return Ok(order);
157
+ }
158
+
159
+ // ✅ Validate quyền sở hữu trước khi trả dữ liệu
160
+ [HttpGet("api/orders/{id}")]
161
+ public async Task<IActionResult> GetOrder(int id)
162
+ {
163
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
164
+ var order = await _db.Orders.FirstOrDefaultAsync(o => o.Id == id && o.UserId == userId); // Giới hạn theo Current User
165
+ if (order == null) return NotFound();
166
+ return Ok(order);
167
+ }
168
+ ```
169
+
170
+ **UUID vs Tự tăng ID (Auto-increment ID)**
171
+ ```text
172
+ // ❌ ID tự tăng dễ bị cào dữ liệu (Enumeration)
173
+ // GET /api/users/1, /api/users/2, /api/users/3 ...
174
+
175
+ // ✅ UUID không thể đoán trước
176
+ // GET /api/users/550e8400-e29b-41d4-a716-446655440000
177
+
178
+ // ⚠️ UUID chỉ chống cào dữ liệu, KHÔNG PHẢI là chốt chặn bảo mật.
179
+ // VẪN PHẢI kiểm tra Current User có quyền truy cập UUID đó không!
180
+ ```
181
+
182
+ ### Phòng chống Command Injection (Tiêm lệnh)
183
+
184
+ **C#**
185
+ ```csharp
186
+ // ❌ Lỗ hổng: Nối chuỗi vào lệnh gọi Shell
187
+ Process.Start("cmd.exe", "/c convert " + filename + " output.png");
188
+
189
+ // ✅ Truyền arguments tách biệt rõ ràng
190
+ var startInfo = new ProcessStartInfo
191
+ {
192
+ FileName = "convert",
193
+ Arguments = $"{filename} output.png" // Vẫn có rủi ro, cần validate input
194
+ };
195
+ Process.Start(startInfo);
196
+
197
+ // ❌ CỰC KỲ NGUY HIỂM: Truyền input của user thẳng vào Shell
198
+ Process.Start("sh", "-c \"echo " + userInput + "\"");
199
+ ```
200
+
201
+ ## Data Protection (Bảo vệ Dữ liệu)
202
+
203
+ ### Xử lý Dữ liệu Nhạy cảm
204
+ - [ ] Không lưu trữ secret (password, API keys) trong source code.
205
+ - [ ] Secrets phải được lưu ở Environment Variables hoặc Secret Manager (Azure Key Vault, AWS Secrets Manager).
206
+ - [ ] Mã hóa dữ liệu nhạy cảm khi nghỉ (Encrypted at rest).
207
+ - [ ] Mã hóa dữ liệu khi truyền tải (HTTPS / Encrypted in transit).
208
+ - [ ] PII (Dữ liệu cá nhân) được xử lý theo chuẩn (GDPR, v.v.).
209
+ - [ ] KHÔNG log dữ liệu nhạy cảm.
210
+
211
+ ### Cấu hình Bảo mật
212
+ ```yaml
213
+ # ❌ Lưu lộ liễu trong file cấu hình
214
+ database:
215
+ password: "super-secret-password"
216
+
217
+ # ✅ Trỏ tới biến môi trường
218
+ database:
219
+ password: ${DATABASE_PASSWORD}
220
+ ```
221
+
222
+ ### Thông báo lỗi (Error Messages)
223
+ ```csharp
224
+ // ❌ Làm lộ cấu trúc hệ thống qua Exception
225
+ catch (Exception ex) {
226
+ return StatusCode(500, new {
227
+ error = ex.StackTrace, // Lộ mã nguồn bên trong
228
+ query = sqlQuery // Lộ cấu trúc DB
229
+ });
230
+ }
231
+
232
+ // ✅ Trả về thông báo lỗi chung chung (Generic)
233
+ catch (Exception ex) {
234
+ _logger.LogError(ex, "Lỗi Database"); // Log lại ở nội bộ
235
+ return StatusCode(500, new {
236
+ error = "Đã xảy ra lỗi không xác định"
237
+ });
238
+ }
239
+ ```
240
+
241
+ ## Bảo mật API
242
+
243
+ ### Rate Limiting (Giới hạn tốc độ)
244
+ - [ ] Áp dụng Rate limiting cho tất cả public endpoint.
245
+ - [ ] Giới hạn gắt gao hơn cho các endpoint Login / Đăng ký.
246
+ - [ ] Xử lý khéo léo (Graceful handling) khi người dùng vượt quá Limit.
247
+
248
+ ### Cấu hình CORS
249
+ ```csharp
250
+ // ❌ CORS mở toang cửa cho tất cả (*)
251
+ app.UseCors(builder => builder.AllowAnyOrigin().AllowAnyMethod());
252
+
253
+ // ✅ CORS giới hạn chặt chẽ
254
+ app.UseCors(builder => builder
255
+ .WithOrigins("https://your-app.com")
256
+ .WithMethods("GET", "POST")
257
+ .AllowCredentials());
258
+ ```
259
+
260
+ ## Mật mã học (Cryptography)
261
+
262
+ ### Best Practice
263
+ - [ ] Sử dụng các thuật toán chuẩn (AES-256, RSA-2048+).
264
+ - [ ] KHÔNG TỰ CHẾ thuật toán mã hóa (Not implementing custom cryptography).
265
+ - [ ] Dùng hàm sinh số ngẫu nhiên an toàn (Cryptographically secure random).
266
+ - [ ] Quản lý và luân chuyển Key (Key rotation) đàng hoàng.
267
+
268
+ ### Sai lầm phổ biến
269
+ ```csharp
270
+ // ❌ Sinh token ngẫu nhiên yếu (Dùng Random class)
271
+ var token = new Random().Next().ToString();
272
+
273
+ // ✅ Sinh ngẫu nhiên bảo mật (Cryptographically secure)
274
+ using var rng = RandomNumberGenerator.Create();
275
+ var bytes = new byte[32];
276
+ rng.GetBytes(bytes);
277
+ var token = Convert.ToBase64String(bytes);
278
+
279
+ // ❌ Dùng MD5/SHA1 để băm mật khẩu
280
+ var hash = MD5.HashData(Encoding.UTF8.GetBytes(password));
281
+
282
+ // ✅ Dùng BCrypt hoặc Argon2
283
+ var hash = BCrypt.Net.BCrypt.HashPassword(password);
284
+ ```
285
+
286
+ ## Dependency Security (Bảo mật Thư viện phụ thuộc)
287
+
288
+ ### Checklist
289
+ - [ ] Chỉ dùng thư viện từ nguồn uy tín (Trusted sources).
290
+ - [ ] Quét lỗ hổng định kỳ (`dotnet list package --vulnerable`).
291
+ - [ ] Cập nhật thư viện thường xuyên.
292
+ - [ ] Xác minh giấy phép mã nguồn mở (License compliance).
293
+
294
+ ## Logging & Monitoring
295
+
296
+ ### Ghi Log An toàn
297
+ - [ ] Không log Password, Token, PII.
298
+ - [ ] Bảo vệ file log khỏi bị giả mạo.
299
+ - [ ] Log lại các sự kiện an ninh mạng (Đăng nhập sai, đổi quyền).
300
+ - [ ] Phòng chống Log Injection.
301
+
302
+ ```csharp
303
+ // ❌ Log lộ thông tin nhạy cảm
304
+ _logger.LogInformation($"User login: {email}, password: {password}");
305
+
306
+ // ✅ Log an toàn
307
+ _logger.LogInformation("Thử đăng nhập", new { email, success = true });
308
+ ```
309
+
310
+ ## Security Review Severity Levels (Mức độ Nghiêm trọng)
311
+
312
+ | Severity | Mô tả | Hành động |
313
+ |----------|-------------|--------|
314
+ | 🔴 **Critical** | Có thể bị khai thác ngay lập tức, nguy cơ lộ data | Block merge, sửa NGAY LẬP TỨC |
315
+ | 🟡 **High** | Lỗ hổng lớn, nhưng cần điều kiện cụ thể mới khai thác được | Block merge, sửa trước khi release |
316
+ | 🟡 **Medium** | Rủi ro vừa phải | Nên sửa, có thể merge và track lại |
317
+ | 🟢 **Low** | Vấn đề nhỏ, vi phạm best practice | Nên sửa nếu có thể, không block |
318
+ | 💡 **Info** | Đề xuất tối ưu | Không bắt buộc |
@@ -0,0 +1,308 @@
1
+ # SQL Injection Prevention Guide
2
+
3
+ Language-agnostic SQL injection prevention strategies with cross-language code examples.
4
+
5
+ > **Related**: [Security Review Guide](../security-review-guide.md) for comprehensive security checklist and decision framework.
6
+
7
+ ## Attack Types
8
+
9
+ SQL injection (SQLi) is ranked #3 in the OWASP Top 10 (2021). Three common variants:
10
+
11
+ | Type | Description | Risk |
12
+ |------|-------------|------|
13
+ | **Classic (In-band)** | Attacker receives results directly in the HTTP response | Data exfiltration, authentication bypass |
14
+ | **Blind (Boolean/Time-based)** | Attacker infers data from response differences or timing | Slower but still viable for data extraction |
15
+ | **Out-of-band** | Attacker uses DNS/HTTP callbacks to exfiltrate data | Less common but harder to detect |
16
+
17
+ ## Universal Prevention Strategy
18
+
19
+ 1. **Parameterized queries** — always (the #1 defense)
20
+ 2. **ORM safe usage** — understand what your ORM escapes
21
+ 3. **Input validation** — whitelist over blacklist
22
+ 4. **Least privilege** — database user with minimal permissions
23
+ 5. **WAF** — web application firewall as defense-in-depth
24
+
25
+ ---
26
+
27
+ ## Cross-Language Examples
28
+
29
+ ### Python
30
+
31
+ ```python
32
+ # ❌ Vulnerable: string formatting
33
+ query = f"SELECT * FROM users WHERE id = {user_id}"
34
+ cursor.execute(query)
35
+
36
+ # ❌ Vulnerable: % formatting
37
+ cursor.execute("SELECT * FROM users WHERE id = %s" % user_id)
38
+
39
+ # ✅ Parameterized (DB-API)
40
+ cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
41
+
42
+ # ✅ SQLAlchemy ORM
43
+ User.query.filter(User.id == user_id).all()
44
+
45
+ # ❌ SQLAlchemy raw SQL with string interpolation
46
+ session.execute(text(f"SELECT * FROM users WHERE id = {user_id}"))
47
+
48
+ # ✅ SQLAlchemy raw SQL with bound parameters
49
+ session.execute(text("SELECT * FROM users WHERE id = :id"), {"id": user_id})
50
+
51
+ # ✅ Django ORM
52
+ User.objects.filter(id=user_id)
53
+
54
+ # ❌ Django extra() with string interpolation
55
+ User.objects.extra(where=[f"username = '{username}'"])
56
+
57
+ # ✅ Django raw() with parameters
58
+ User.objects.raw("SELECT * FROM users WHERE id = %s", [user_id])
59
+ ```
60
+
61
+ ### Java
62
+
63
+ ```java
64
+ // ❌ Vulnerable: string concatenation
65
+ String query = "SELECT * FROM users WHERE id = " + userId;
66
+ Statement stmt = connection.createStatement();
67
+ ResultSet rs = stmt.executeQuery(query);
68
+
69
+ // ✅ JDBC PreparedStatement
70
+ String query = "SELECT * FROM users WHERE id = ?";
71
+ PreparedStatement stmt = connection.prepareStatement(query);
72
+ stmt.setLong(1, userId);
73
+ ResultSet rs = stmt.executeQuery();
74
+
75
+ // ✅ JPA parameter binding
76
+ @Query("SELECT u FROM User u WHERE u.id = :id")
77
+ User findById(@Param("id") Long id);
78
+
79
+ // ✅ Spring Data JPA method naming
80
+ User findById(Long id);
81
+
82
+ // ❌ JPA native query with string concatenation
83
+ entityManager.createNativeQuery(
84
+ "SELECT * FROM users WHERE name = '" + name + "'"
85
+ );
86
+
87
+ // ✅ JPA native query with parameter binding
88
+ Query query = entityManager.createNativeQuery(
89
+ "SELECT * FROM users WHERE name = :name"
90
+ );
91
+ query.setParameter("name", name);
92
+ ```
93
+
94
+ ### Go
95
+
96
+ ```go
97
+ // ❌ Vulnerable: fmt.Sprintf
98
+ query := fmt.Sprintf("SELECT * FROM users WHERE id = %s", userID)
99
+ rows, err := db.Query(query)
100
+
101
+ // ✅ database/sql parameterized
102
+ rows, err := db.Query("SELECT * FROM users WHERE id = ?", userID)
103
+
104
+ // ✅ Named parameters (sqlx)
105
+ rows, err := db.NamedQuery(
106
+ "SELECT * FROM users WHERE id = :id",
107
+ map[string]interface{}{"id": userID},
108
+ )
109
+
110
+ // ⚠️ Dynamic identifiers (table/column names) can't use placeholders
111
+ // Must validate against whitelist
112
+ var allowedColumns = map[string]bool{
113
+ "id": true, "name": true, "email": true, "created_at": true,
114
+ }
115
+
116
+ func queryWithOrder(db *sql.DB, orderBy string) (*sql.Rows, error) {
117
+ if !allowedColumns[orderBy] {
118
+ return nil, fmt.Errorf("invalid column: %s", orderBy)
119
+ }
120
+ return db.Query(
121
+ fmt.Sprintf("SELECT * FROM users ORDER BY %s", orderBy),
122
+ )
123
+ }
124
+ ```
125
+
126
+ ### Node.js
127
+
128
+ ```typescript
129
+ // ❌ Vulnerable: template literal
130
+ const query = `SELECT * FROM users WHERE id = ${userId}`;
131
+ const result = await client.query(query);
132
+
133
+ // ✅ pg parameterized ($1, $2, ...)
134
+ const result = await client.query(
135
+ "SELECT * FROM users WHERE id = $1",
136
+ [userId]
137
+ );
138
+
139
+ // ✅ Prisma ORM (parameterized by default)
140
+ const user = await prisma.user.findUnique({
141
+ where: { id: userId },
142
+ });
143
+
144
+ // ❌ Prisma $queryRawUnsafe with string interpolation
145
+ await prisma.$queryRawUnsafe(
146
+ `SELECT * FROM users WHERE id = ${userId}`
147
+ );
148
+
149
+ // ✅ Prisma $queryRaw with tagged template (safe)
150
+ await prisma.$queryRaw`
151
+ SELECT * FROM users WHERE id = ${userId}
152
+ `;
153
+ ```
154
+
155
+ ### PHP
156
+
157
+ ```php
158
+ <?php
159
+
160
+ // ❌ Vulnerable: string concatenation
161
+ $sql = "SELECT * FROM users WHERE email = '" . $_GET['email'] . "'";
162
+ $user = $pdo->query($sql)->fetch();
163
+
164
+ // ✅ PDO prepared statements
165
+ $stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email");
166
+ $stmt->execute(['email' => $email]);
167
+ $user = $stmt->fetch(PDO::FETCH_ASSOC);
168
+
169
+ // ✅ PDO positional placeholders
170
+ $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
171
+ $stmt->execute([$id]);
172
+
173
+ // ❌ mysqli with string interpolation
174
+ $result = mysqli_query($conn,
175
+ "SELECT * FROM users WHERE id = " . $id
176
+ );
177
+
178
+ // ✅ mysqli prepared statements
179
+ $stmt = mysqli_prepare($conn, "SELECT * FROM users WHERE id = ?");
180
+ mysqli_stmt_bind_param($stmt, "i", $id);
181
+ mysqli_stmt_execute($stmt);
182
+
183
+ // ✅ Laravel Eloquent ORM
184
+ User::where('id', $id)->first();
185
+
186
+ // ❌ Laravel DB::raw with interpolation
187
+ DB::select(DB::raw("SELECT * FROM users WHERE id = {$id}"));
188
+
189
+ // ✅ Laravel parameterized raw
190
+ DB::select("SELECT * FROM users WHERE id = ?", [$id]);
191
+ ```
192
+
193
+ ### C# / .NET
194
+
195
+ ```csharp
196
+ // ❌ Vulnerable: string concatenation
197
+ var query = $"SELECT * FROM Users WHERE Id = {userId}";
198
+ using var cmd = new SqlCommand(query, connection);
199
+ var reader = cmd.ExecuteReader();
200
+
201
+ // ✅ ADO.NET parameterized
202
+ var query = "SELECT * FROM Users WHERE Id = @Id";
203
+ using var cmd = new SqlCommand(query, connection);
204
+ cmd.Parameters.AddWithValue("@Id", userId);
205
+
206
+ // ✅ Dapper parameterized
207
+ var users = connection.Query<User>(
208
+ "SELECT * FROM Users WHERE Id = @Id",
209
+ new { Id = userId }
210
+ );
211
+
212
+ // ❌ Dapper with string interpolation
213
+ var users = connection.Query<User>(
214
+ $"SELECT * FROM Users WHERE Id = {userId}"
215
+ );
216
+
217
+ // ✅ EF Core (parameterized by default)
218
+ var user = await context.Users
219
+ .Where(u => u.Id == userId)
220
+ .FirstOrDefaultAsync();
221
+
222
+ // ❌ EF Core FromSqlRaw with interpolation
223
+ var users = context.Users
224
+ .FromSqlRaw($"SELECT * FROM Users WHERE Id = {userId}")
225
+ .ToList();
226
+
227
+ // ✅ EF Core FromSql with FormattableString (parameterized)
228
+ var users = context.Users
229
+ .FromSql($"SELECT * FROM Users WHERE Id = {userId}")
230
+ .ToList();
231
+ ```
232
+
233
+ ---
234
+
235
+ ## ORM Unsafe Usage Patterns
236
+
237
+ ORMs do NOT automatically prevent SQL injection in all cases:
238
+
239
+ ```python
240
+ # ❌ SQLAlchemy: text() with f-string
241
+ session.execute(text(f"SELECT * FROM users WHERE id = {user_id}"))
242
+
243
+ # ❌ Django: extra() / RawSQL() with string interpolation
244
+ User.objects.extra(where=[f"username = '{username}'"])
245
+ User.objects.annotate(
246
+ val=RawSQL(f"SELECT col FROM other WHERE id = {user_id}")
247
+ )
248
+
249
+ # ❌ JPA: createNativeQuery with string concatenation
250
+ entityManager.createNativeQuery("SELECT * FROM users WHERE name = '" + name + "'")
251
+
252
+ # ❌ EF Core: FromSqlRaw with string interpolation
253
+ context.Users.FromSqlRaw($"SELECT * FROM Users WHERE Id = {userId}")
254
+ ```
255
+
256
+ **Rule**: Every ORM has a "raw SQL" escape hatch. String interpolation in that escape hatch = SQL injection. Always use the ORM's parameter binding mechanism.
257
+
258
+ ---
259
+
260
+ ## Dynamic Identifiers (Table/Column Names)
261
+
262
+ Placeholders can only bind **values**, not table names, column names, or SQL keywords. For dynamic identifiers:
263
+
264
+ ```python
265
+ # ✅ Whitelist validation
266
+ ALLOWED_COLUMNS = {"id", "name", "email", "created_at"}
267
+ ALLOWED_DIRECTIONS = {"ASC", "DESC"}
268
+
269
+ def get_users(order_by: str, direction: str) -> list[User]:
270
+ if order_by not in ALLOWED_COLUMNS:
271
+ raise ValueError(f"Invalid column: {order_by}")
272
+ if direction.upper() not in ALLOWED_DIRECTIONS:
273
+ raise ValueError(f"Invalid direction: {direction}")
274
+
275
+ return User.objects.order_by(
276
+ f"{'-' if direction.upper() == 'DESC' else ''}{order_by}"
277
+ )
278
+ ```
279
+
280
+ ---
281
+
282
+ ## Detection & Testing
283
+
284
+ ```bash
285
+ # Automated scanning
286
+ sqlmap -u "https://example.com/api/users?id=1" --batch
287
+
288
+ # Static analysis (Python)
289
+ bandit -r src/ -f custom
290
+
291
+ # Static analysis (Java)
292
+ spotbugs -textui build/classes
293
+
294
+ # Code review keywords to search for
295
+ grep -rn "f\".*SELECT\|f'.*SELECT\|fmt.Sprintf.*SELECT\|format.*SELECT" src/
296
+ grep -rn "query.*\+.*\|query.*&\|query.*concat" src/
297
+ ```
298
+
299
+ ---
300
+
301
+ ## Review Checklist
302
+
303
+ - [ ] All SQL queries use parameterized queries (no string interpolation)
304
+ - [ ] ORM raw SQL methods use bound parameters, not string formatting
305
+ - [ ] Dynamic identifiers (table/column names) validated against whitelist
306
+ - [ ] Database user has least privilege (no DROP/ALTER for app user)
307
+ - [ ] No SQL queries constructed from user input without parameterization
308
+ - [ ] Static analysis tools (Bandit, SpotBugs, SonarQube) run in CI