ai-developer-skill-os 10.1.0 → 10.2.0

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 (98) hide show
  1. package/.agents/AGENTS.md +13 -0
  2. package/.agents/LICENSE +21 -21
  3. package/.agents/docs/ARCHITECTURE.md +56 -120
  4. package/.agents/docs/GOVERNANCE.md +3 -3
  5. package/.agents/docs/SPEC.md +137 -60
  6. package/.agents/docs/VERSIONING.md +25 -57
  7. package/.agents/docs/adr/0005-v10-platform-consolidation.md +50 -0
  8. package/.agents/docs/schemas/learning.schema.yml +22 -57
  9. package/.agents/docs/schemas/skill.schema.yml +116 -161
  10. package/.agents/docs/schemas/workflow.schema.yml +51 -26
  11. package/.agents/docs/skill-classification.md +1 -1
  12. package/.agents/registry/graph.json +171 -77
  13. package/.agents/registry/index.yaml +68 -35
  14. package/.agents/rules/coding.md +1 -1
  15. package/.agents/rules/command-safety.md +1 -1
  16. package/.agents/rules/global.md +1 -1
  17. package/.agents/rules/prompt-compiler.md +1 -1
  18. package/.agents/rules/safety.md +1 -1
  19. package/.agents/rules/security.md +1 -1
  20. package/.agents/rules/skill-quality.md +18 -3
  21. package/.agents/skills/_template/SKILL.md +238 -88
  22. package/.agents/skills/qk-api-data-discovery/SKILL.md +176 -190
  23. package/.agents/skills/qk-api-data-discovery/references/bronze-record-format.md +47 -0
  24. package/.agents/skills/qk-api-data-discovery/references/data-contract-yaml.md +72 -0
  25. package/.agents/skills/qk-api-data-discovery/references/discovery-report-template.md +110 -0
  26. package/.agents/skills/qk-backend-data/SKILL.md +150 -1
  27. package/.agents/skills/qk-bug-resolution/SKILL.md +153 -1
  28. package/.agents/skills/qk-bug-resolution/evals/scorecard.yaml +1 -1
  29. package/.agents/skills/qk-code-cleaner/SKILL.md +150 -1
  30. package/.agents/skills/qk-code-review/SKILL.md +195 -1
  31. package/.agents/skills/qk-code-review/evals/scorecard.yaml +1 -1
  32. package/.agents/skills/qk-code-review/references/ai/{v8-schema-validation.md → schema-validation.md} +2 -2
  33. package/.agents/skills/qk-code-review/references/cross-cutting/async-concurrency-patterns.md +515 -515
  34. package/.agents/skills/qk-code-review/references/cross-cutting/error-handling-principles.md +492 -492
  35. package/.agents/skills/qk-code-review/references/cross-cutting/n-plus-one-queries.md +309 -309
  36. package/.agents/skills/qk-code-review/references/cross-cutting/sql-injection-prevention.md +307 -307
  37. package/.agents/skills/qk-code-review/references/cross-cutting/xss-prevention.md +263 -263
  38. package/.agents/skills/qk-code-review/references/languages/angular.md +768 -768
  39. package/.agents/skills/qk-code-review/references/languages/c.md +890 -890
  40. package/.agents/skills/qk-code-review/references/languages/cpp.md +893 -893
  41. package/.agents/skills/qk-code-review/references/languages/css-less-sass.md +661 -661
  42. package/.agents/skills/qk-code-review/references/languages/django.md +985 -985
  43. package/.agents/skills/qk-code-review/references/languages/fastapi.md +580 -580
  44. package/.agents/skills/qk-code-review/references/languages/go.md +993 -993
  45. package/.agents/skills/qk-code-review/references/languages/java.md +409 -409
  46. package/.agents/skills/qk-code-review/references/languages/java8.md +586 -586
  47. package/.agents/skills/qk-code-review/references/languages/kotlin.md +1018 -1018
  48. package/.agents/skills/qk-code-review/references/languages/nestjs.md +593 -593
  49. package/.agents/skills/qk-code-review/references/languages/php.md +684 -684
  50. package/.agents/skills/qk-code-review/references/languages/python.md +1073 -1073
  51. package/.agents/skills/qk-code-review/references/languages/qt.md +757 -757
  52. package/.agents/skills/qk-code-review/references/languages/react.md +871 -871
  53. package/.agents/skills/qk-code-review/references/languages/ruby.md +964 -964
  54. package/.agents/skills/qk-code-review/references/languages/rust.md +846 -846
  55. package/.agents/skills/qk-code-review/references/languages/svelte.md +1064 -1064
  56. package/.agents/skills/qk-code-review/references/languages/swift.md +936 -936
  57. package/.agents/skills/qk-code-review/references/languages/typescript.md +1016 -1016
  58. package/.agents/skills/qk-code-review/references/languages/vue.md +924 -924
  59. package/.agents/skills/qk-code-review/references/languages/zig.md +440 -440
  60. package/.agents/skills/qk-devops-release/SKILL.md +150 -1
  61. package/.agents/skills/qk-feature-delivery/SKILL.md +153 -1
  62. package/.agents/skills/qk-feature-delivery/evals/scorecard.yaml +1 -1
  63. package/.agents/skills/qk-orchestrator/SKILL.md +187 -2
  64. package/.agents/skills/qk-orchestrator/evals/scorecard.yaml +1 -1
  65. package/.agents/skills/qk-orchestrator/references/routing-table.md +1 -1
  66. package/.agents/skills/qk-product-spec/SKILL.md +150 -1
  67. package/.agents/skills/qk-prompt-compiler/SKILL.md +188 -65
  68. package/.agents/skills/qk-ui-engineer/SKILL.md +150 -1
  69. package/.agents/workflows/_schema.yml +146 -146
  70. package/.agents/workflows/bug-resolution.yml +155 -121
  71. package/.agents/workflows/code-review.yml +127 -93
  72. package/.agents/workflows/context-discovery.yml +128 -94
  73. package/.agents/workflows/documentation.yml +124 -90
  74. package/.agents/workflows/feature-delivery.yml +158 -124
  75. package/.agents/workflows/production-release.yml +207 -173
  76. package/.agents/workflows/prompt-compilation.yml +126 -0
  77. package/.agents/workflows/refactor.yml +136 -102
  78. package/.agents/workflows/security-audit.yml +149 -115
  79. package/.agents/workflows/shared/quality-gate.yml +3 -1
  80. package/.agents/workflows/skin-governance.yml +149 -115
  81. package/.agents/workflows/spec-driven-development.yml +78 -71
  82. package/CHANGELOG.md +61 -0
  83. package/README.md +3 -3
  84. package/bin/install.js +38 -51
  85. package/package.json +68 -74
  86. package/tooling/build-registry.js +226 -208
  87. package/tooling/run-aar.js +55 -126
  88. package/tooling/sync-versions.js +2 -2
  89. package/tooling/validate-graph.js +100 -87
  90. package/tooling/validate-skills.js +32 -14
  91. package/.agents/docs/decisions/ADR-001-v8-migration.md +0 -58
  92. package/.agents/docs/decisions/ADR-002-workflow-separation.md +0 -50
  93. package/.agents/docs/decisions/ADR-003-registry-generated.md +0 -54
  94. package/.agents/docs/decisions/ADR-008-skill-boundary-review.md +0 -27
  95. package/.agents/registry/capability-graph.yml +0 -390
  96. package/.agents/registry/skills-index.yml +0 -305
  97. package/.agents/skills/_template/capability.yaml +0 -34
  98. package/.agents/skills/_template/evals/scorecard.yaml +0 -19
@@ -1,308 +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
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
308
  - [ ] Static analysis tools (Bandit, SpotBugs, SonarQube) run in CI