@xenos1996/usa 2.0.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 (163) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +335 -0
  3. package/USA.md +597 -0
  4. package/dist/bootstrap/catalog.d.ts +46 -0
  5. package/dist/bootstrap/catalog.d.ts.map +1 -0
  6. package/dist/bootstrap/catalog.js +312 -0
  7. package/dist/bootstrap/catalog.js.map +1 -0
  8. package/dist/bootstrap/index.d.ts +36 -0
  9. package/dist/bootstrap/index.d.ts.map +1 -0
  10. package/dist/bootstrap/index.js +177 -0
  11. package/dist/bootstrap/index.js.map +1 -0
  12. package/dist/cli.d.ts +5 -0
  13. package/dist/cli.d.ts.map +1 -0
  14. package/dist/cli.js +706 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/config.d.ts +7 -0
  17. package/dist/config.d.ts.map +1 -0
  18. package/dist/config.js +88 -0
  19. package/dist/config.js.map +1 -0
  20. package/dist/detect/index.d.ts +58 -0
  21. package/dist/detect/index.d.ts.map +1 -0
  22. package/dist/detect/index.js +329 -0
  23. package/dist/detect/index.js.map +1 -0
  24. package/dist/engine/audit.d.ts +32 -0
  25. package/dist/engine/audit.d.ts.map +1 -0
  26. package/dist/engine/audit.js +366 -0
  27. package/dist/engine/audit.js.map +1 -0
  28. package/dist/engine/diff.d.ts +24 -0
  29. package/dist/engine/diff.d.ts.map +1 -0
  30. package/dist/engine/diff.js +136 -0
  31. package/dist/engine/diff.js.map +1 -0
  32. package/dist/engine/evaluate.d.ts +19 -0
  33. package/dist/engine/evaluate.d.ts.map +1 -0
  34. package/dist/engine/evaluate.js +377 -0
  35. package/dist/engine/evaluate.js.map +1 -0
  36. package/dist/engine/gate.d.ts +19 -0
  37. package/dist/engine/gate.d.ts.map +1 -0
  38. package/dist/engine/gate.js +47 -0
  39. package/dist/engine/gate.js.map +1 -0
  40. package/dist/engine/loader.d.ts +37 -0
  41. package/dist/engine/loader.d.ts.map +1 -0
  42. package/dist/engine/loader.js +388 -0
  43. package/dist/engine/loader.js.map +1 -0
  44. package/dist/engine/maturity.d.ts +20 -0
  45. package/dist/engine/maturity.d.ts.map +1 -0
  46. package/dist/engine/maturity.js +157 -0
  47. package/dist/engine/maturity.js.map +1 -0
  48. package/dist/engine/score.d.ts +19 -0
  49. package/dist/engine/score.d.ts.map +1 -0
  50. package/dist/engine/score.js +141 -0
  51. package/dist/engine/score.js.map +1 -0
  52. package/dist/engine/sections.d.ts +14 -0
  53. package/dist/engine/sections.d.ts.map +1 -0
  54. package/dist/engine/sections.js +50 -0
  55. package/dist/engine/sections.js.map +1 -0
  56. package/dist/evolution/benchmark.d.ts +12 -0
  57. package/dist/evolution/benchmark.d.ts.map +1 -0
  58. package/dist/evolution/benchmark.js +107 -0
  59. package/dist/evolution/benchmark.js.map +1 -0
  60. package/dist/evolution/capability.d.ts +22 -0
  61. package/dist/evolution/capability.d.ts.map +1 -0
  62. package/dist/evolution/capability.js +69 -0
  63. package/dist/evolution/capability.js.map +1 -0
  64. package/dist/evolution/coverage.d.ts +15 -0
  65. package/dist/evolution/coverage.d.ts.map +1 -0
  66. package/dist/evolution/coverage.js +65 -0
  67. package/dist/evolution/coverage.js.map +1 -0
  68. package/dist/evolution/gap.d.ts +10 -0
  69. package/dist/evolution/gap.d.ts.map +1 -0
  70. package/dist/evolution/gap.js +52 -0
  71. package/dist/evolution/gap.js.map +1 -0
  72. package/dist/evolution/propose.d.ts +50 -0
  73. package/dist/evolution/propose.d.ts.map +1 -0
  74. package/dist/evolution/propose.js +188 -0
  75. package/dist/evolution/propose.js.map +1 -0
  76. package/dist/evolution/queue.d.ts +64 -0
  77. package/dist/evolution/queue.d.ts.map +1 -0
  78. package/dist/evolution/queue.js +192 -0
  79. package/dist/evolution/queue.js.map +1 -0
  80. package/dist/evolution/release.d.ts +11 -0
  81. package/dist/evolution/release.d.ts.map +1 -0
  82. package/dist/evolution/release.js +55 -0
  83. package/dist/evolution/release.js.map +1 -0
  84. package/dist/evolution/run.d.ts +88 -0
  85. package/dist/evolution/run.d.ts.map +1 -0
  86. package/dist/evolution/run.js +258 -0
  87. package/dist/evolution/run.js.map +1 -0
  88. package/dist/evolution/schedule.d.ts +59 -0
  89. package/dist/evolution/schedule.d.ts.map +1 -0
  90. package/dist/evolution/schedule.js +77 -0
  91. package/dist/evolution/schedule.js.map +1 -0
  92. package/dist/evolution/types.d.ts +174 -0
  93. package/dist/evolution/types.d.ts.map +1 -0
  94. package/dist/evolution/types.js +8 -0
  95. package/dist/evolution/types.js.map +1 -0
  96. package/dist/index.d.ts +36 -0
  97. package/dist/index.d.ts.map +1 -0
  98. package/dist/index.js +30 -0
  99. package/dist/index.js.map +1 -0
  100. package/dist/learn/index.d.ts +27 -0
  101. package/dist/learn/index.d.ts.map +1 -0
  102. package/dist/learn/index.js +196 -0
  103. package/dist/learn/index.js.map +1 -0
  104. package/dist/report/markdown.d.ts +16 -0
  105. package/dist/report/markdown.d.ts.map +1 -0
  106. package/dist/report/markdown.js +488 -0
  107. package/dist/report/markdown.js.map +1 -0
  108. package/dist/snapshot/index.d.ts +55 -0
  109. package/dist/snapshot/index.d.ts.map +1 -0
  110. package/dist/snapshot/index.js +76 -0
  111. package/dist/snapshot/index.js.map +1 -0
  112. package/dist/store/index.d.ts +31 -0
  113. package/dist/store/index.d.ts.map +1 -0
  114. package/dist/store/index.js +69 -0
  115. package/dist/store/index.js.map +1 -0
  116. package/dist/types.d.ts +287 -0
  117. package/dist/types.d.ts.map +1 -0
  118. package/dist/types.js +16 -0
  119. package/dist/types.js.map +1 -0
  120. package/dist/util/glob.d.ts +11 -0
  121. package/dist/util/glob.d.ts.map +1 -0
  122. package/dist/util/glob.js +86 -0
  123. package/dist/util/glob.js.map +1 -0
  124. package/dist/util/project.d.ts +90 -0
  125. package/dist/util/project.d.ts.map +1 -0
  126. package/dist/util/project.js +636 -0
  127. package/dist/util/project.js.map +1 -0
  128. package/dist/util/yaml.d.ts +28 -0
  129. package/dist/util/yaml.d.ts.map +1 -0
  130. package/dist/util/yaml.js +48 -0
  131. package/dist/util/yaml.js.map +1 -0
  132. package/package.json +94 -0
  133. package/rules/core/architecture.yaml +190 -0
  134. package/rules/core/cicd.yaml +212 -0
  135. package/rules/core/code-quality.yaml +261 -0
  136. package/rules/core/dependencies.yaml +132 -0
  137. package/rules/core/documentation.yaml +119 -0
  138. package/rules/core/future-readiness.yaml +91 -0
  139. package/rules/core/release.yaml +131 -0
  140. package/rules/core/repo.yaml +313 -0
  141. package/rules/core/security.yaml +496 -0
  142. package/rules/core/supply-chain.yaml +357 -0
  143. package/rules/core/testing.yaml +266 -0
  144. package/rules/detectors.yaml +2028 -0
  145. package/rules/index.yaml +42 -0
  146. package/rules/profiles/maturity.yaml +135 -0
  147. package/rules/stacks/ai-era.yaml +193 -0
  148. package/rules/stacks/api-backend.yaml +121 -0
  149. package/rules/stacks/cli.yaml +105 -0
  150. package/rules/stacks/compliance.yaml +157 -0
  151. package/rules/stacks/containers.yaml +124 -0
  152. package/rules/stacks/data.yaml +165 -0
  153. package/rules/stacks/go.yaml +134 -0
  154. package/rules/stacks/iac.yaml +128 -0
  155. package/rules/stacks/jvm.yaml +150 -0
  156. package/rules/stacks/ml-ai.yaml +140 -0
  157. package/rules/stacks/mobile.yaml +121 -0
  158. package/rules/stacks/node-typescript.yaml +166 -0
  159. package/rules/stacks/python.yaml +175 -0
  160. package/rules/stacks/rust.yaml +129 -0
  161. package/rules/stacks/solidity.yaml +139 -0
  162. package/rules/stacks/swift.yaml +100 -0
  163. package/rules/stacks/web-frontend.yaml +159 -0
@@ -0,0 +1,496 @@
1
+ id: core/security
2
+ title: Security
3
+ section: S2
4
+ section_title: Security
5
+ description: >
6
+ Universal security baseline. Severity is never dampened here below CRITICAL,
7
+ and security-class rules carry the heaviest multiplier in the score.
8
+ USA is not a penetration test — it finds the doors that were left open.
9
+ version: '1.0'
10
+
11
+ rules:
12
+ # ---------------------------------------------------------------- secrets
13
+ - id: SEC-001
14
+ title: No hardcoded credentials in source
15
+ section: S2
16
+ section_title: Security
17
+ severity: CRITICAL
18
+ class: security
19
+ check:
20
+ kind: grep_absent
21
+ pattern: '(api[_-]?key|apikey|secret|password|passwd|access[_-]?token|private[_-]?key|client[_-]?secret)\s*[:=]\s*[''"`][A-Za-z0-9._/+_-]{12,}[''"`]'
22
+ include:
23
+ [
24
+ '**/*.{ts,tsx,js,jsx,mjs,cjs,py,go,rs,java,kt,swift,rb,php,c,cpp,cs,scala,ex,exs,dart,sh,yml,yaml,toml,ini,conf,env}',
25
+ ]
26
+ exclude:
27
+ [
28
+ '**/*.test.*',
29
+ '**/*.spec.*',
30
+ '**/test/**',
31
+ '**/tests/**',
32
+ '**/fixtures/**',
33
+ '**/*.example',
34
+ '**/*.md',
35
+ '**/docs/**',
36
+ ]
37
+ flags: i
38
+ why: 'Hardcoded credentials leak through every clone, fork, and CI log. Rotation after exposure is mandatory, not optional.'
39
+ remediation: 'Move to environment variables backed by a secret manager; rotate anything that was ever committed.'
40
+ references: ['CWE-798', 'ASVS-2.10.4', 'OWASP-A02:2021']
41
+
42
+ - id: SEC-002
43
+ title: Sensitive data is not written to logs
44
+ section: S2
45
+ section_title: Security
46
+ severity: HIGH
47
+ class: security
48
+ check:
49
+ kind: grep_wrong
50
+ # The sensitive word must be followed by a separator and a *value* —
51
+ # `token:`, `password=`, `api_key: "${k}"`. Matching the bare word flags
52
+ # every log line that merely talks about secrets, including the honest
53
+ # ones ("No unexpected secrets found.").
54
+ pattern: '(console\.(log|info|debug|warn|error)|print|logger\.(info|debug|warn|error)|log\.(Info|Debug|Warn|Error))\s*\(.*(password|passwd|secret|token|api[_-]?key|authorization|ssn|credit[_-]?card)\b\s*[''"`]?\s*[:=]'
55
+ include: ['**/*.{ts,tsx,js,jsx,mjs,cjs,py,go,rs,java,kt,rb,php}']
56
+ exclude: ['**/*.test.*', '**/*.spec.*', '**/test/**', '**/tests/**']
57
+ flags: i
58
+ why: 'Logs are replicated, shipped, indexed, and retained longer than the data itself. A password in a log is a password in five systems.'
59
+ remediation: 'Log identifiers, not values. Add a redaction filter on the logging pipeline for known sensitive keys.'
60
+ references: ['CWE-532', 'ASVS-7.1.1', 'OWASP-A09:2021']
61
+
62
+ # ------------------------------------------------------------ transport
63
+ - id: SEC-003
64
+ title: No plaintext HTTP endpoints in configuration
65
+ section: S2
66
+ section_title: Security
67
+ severity: HIGH
68
+ class: security
69
+ applies_when:
70
+ any:
71
+ - { fact: 'platform:server' }
72
+ - { fact: 'platform:web' }
73
+ - { fact: 'has:containers' }
74
+ check:
75
+ kind: grep_absent
76
+ # NOTE: `http://` only — an earlier revision used `https?://` and flagged
77
+ # every compliant https:// URL (including this repo's own package.json)
78
+ # as a plaintext-HTTP FAIL. The `s?` must never come back.
79
+ pattern: 'http://(?!localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])[a-z0-9.-]+'
80
+ include:
81
+ ['**/*.{ts,tsx,js,jsx,mjs,cjs,py,go,rs,java,kt,rb,php,yml,yaml,json,toml,env,conf,nginx}']
82
+ exclude: ['**/*.test.*', '**/*.spec.*', '**/README*', '**/*.md', '**/docs/**']
83
+ flags: i
84
+ why: 'An http:// URL to a real host means credentials and user data cross the network in the clear.'
85
+ remediation: 'Move every non-localhost URL to https://. Terminate TLS at the edge and keep it on internally too.'
86
+ references: ['CWE-319', 'ASVS-9.1.1']
87
+
88
+ - id: SEC-004
89
+ title: TLS verification is not disabled
90
+ section: S2
91
+ section_title: Security
92
+ severity: HIGH
93
+ class: security
94
+ check:
95
+ kind: grep_absent
96
+ pattern: '(rejectUnauthorized\s*:\s*false|NODE_TLS_REJECT_UNAUTHORIZED\s*=\s*[''"]?0|verify\s*=\s*False|InsecureSkipVerify\s*:\s*true|curl\s+.*-k\b|GIT_SSL_NO_VERIFY)'
97
+ include: ['**/*.{ts,tsx,js,jsx,mjs,cjs,py,go,rs,java,kt,rb,php,sh,yml,yaml,json}']
98
+ exclude: ['**/*.test.*', '**/*.spec.*', '**/test/**', '**/tests/**']
99
+ flags: i
100
+ why: 'Disabling certificate verification turns every TLS connection into plaintext with extra steps.'
101
+ remediation: 'Fix the certificate chain instead. If a proxy needs a custom CA, install that CA — do not disable verification.'
102
+ references: ['CWE-295', 'ASVS-9.2.3']
103
+
104
+ # -------------------------------------------------------------- injection
105
+ - id: SEC-005
106
+ title: SQL is not built by string concatenation
107
+ section: S2
108
+ section_title: Security
109
+ severity: CRITICAL
110
+ class: security
111
+ applies_when: { fact: 'has:database' }
112
+ check:
113
+ kind: grep_wrong
114
+ # NOTE: the printf alternative matches the `%` *operator* (`"..." % x`),
115
+ # never a bare `%s` placeholder: `%s` inside a string is the safe DB-API
116
+ # parameter style (`execute("... WHERE id = %s", (id,))`), and flagging
117
+ # it failed every correctly parameterised query. Bare `%s` must never
118
+ # come back as an alternative.
119
+ pattern: '(SELECT|INSERT INTO|UPDATE|DELETE FROM)\s+.*(\+\s*\w+|\$\{[^}]*\}|"\s*%\s*[^''"\s]|"\s*\+|\.format\(|f[''"])'
120
+ include: ['**/*.{ts,tsx,js,jsx,py,go,rs,java,kt,rb,php}']
121
+ exclude: ['**/*.test.*', '**/*.spec.*', '**/migrations/**', '**/*.sql']
122
+ flags: i
123
+ why: 'String-built SQL is the oldest and most reliable way to lose a database. Parameterise or use a query builder.'
124
+ remediation: 'Use parameterised queries ($1, ?, :name) or the ORM query API. Never interpolate user input into SQL text.'
125
+ references: ['CWE-89', 'ASVS-5.3.4', 'OWASP-A03:2021']
126
+
127
+ - id: SEC-006
128
+ title: No unsafe dynamic code execution
129
+ section: S2
130
+ section_title: Security
131
+ severity: HIGH
132
+ class: security
133
+ check:
134
+ kind: grep_wrong
135
+ # `exec(` is only dangerous as child_process.exec with user input. The
136
+ # bare alternative also matched `regex.exec(body)`, and a bare `body`
137
+ # matched document.body / res.body — both flagged ordinary code. The
138
+ # lookbehind drops the method-call form; req./params/input keep signal.
139
+ # The quote lookbehind on `eval` drops prose mentions ('eval() does X'
140
+ # in docs and rule descriptions); real calls are never quote-prefixed.
141
+ pattern: '\b((?<![''"`])eval\s*\(|new Function\s*\(|execSync\s*\(\s*[^\n)]*\$\{|(?<!\.)exec\s*\(\s*[^\n)]*(req\.|params|input)|setTimeout\s*\(\s*[''"`])'
142
+ include: ['**/*.{ts,tsx,js,jsx,mjs,cjs,py,php,rs}']
143
+ exclude: ['**/*.test.*', '**/*.spec.*', '**/test/**', '**/tests/**', '**/scripts/**']
144
+ why: 'eval and friends turn untrusted strings into executable code. Almost every RCE starts here.'
145
+ remediation: 'Replace with explicit parsing, a lookup table, or a sandboxed expression evaluator with a capability allowlist.'
146
+ references: ['CWE-95', 'ASVS-5.2.4']
147
+
148
+ - id: SEC-007
149
+ title: Shell commands are not built from user input
150
+ section: S2
151
+ section_title: Security
152
+ severity: CRITICAL
153
+ class: security
154
+ check:
155
+ kind: grep_wrong
156
+ pattern: '(execSync|exec|spawn|system|subprocess\.(run|call|Popen)|os\.system|Runtime\.getRuntime\(\)\.exec)\s*\(.*(\$\{|%s|\+\s*\w+|f[''"]|\.format\()'
157
+ include: ['**/*.{ts,tsx,js,jsx,mjs,cjs,py,go,rs,java,kt,rb,php,sh}']
158
+ exclude: ['**/*.test.*', '**/*.spec.*', '**/scripts/**']
159
+ why: 'Command injection with user-controlled input is full remote code execution, usually unauthenticated.'
160
+ remediation: 'Use execFile with an argument array (no shell), or a library call. If unavoidable, allowlist the inputs.'
161
+ references: ['CWE-78', 'ASVS-5.3.8']
162
+
163
+ - id: SEC-008
164
+ title: User input is validated at a trust boundary
165
+ section: S2
166
+ section_title: Security
167
+ severity: HIGH
168
+ class: security
169
+ check:
170
+ kind: grep_present
171
+ pattern: '(zod|joi|yup|valibot|superstruct|pydantic|marshmallow|cerberus|class-validator|go-playground/validator|validator\.js|ajv|jakarta\.validation|javax\.validation|FluentValidation|HibernateValidator|express-validator|celebrate|attrs|msgspec)'
172
+ include: ['**/*.{ts,tsx,js,jsx,py,go,rs,java,kt}']
173
+ exclude: ['**/*.test.*', '**/*.spec.*']
174
+ flags: i
175
+ why: 'Validation that lives in the UI is a suggestion. Server-side schema validation is the actual control.'
176
+ remediation: 'Define schemas at the edge (HTTP handler, queue consumer, CLI arg parser) and parse before use.'
177
+ references: ['CWE-20', 'ASVS-5.1.1', 'OWASP-A03:2021']
178
+
179
+ - id: SEC-009
180
+ title: Output is encoded rather than interpolated as HTML
181
+ section: S2
182
+ section_title: Security
183
+ severity: HIGH
184
+ class: security
185
+ applies_when:
186
+ any:
187
+ - { fact: 'platform:web' }
188
+ - { fact: 'fw:react' }
189
+ - { fact: 'fw:vue' }
190
+ - { fact: 'fw:svelte' }
191
+ - { fact: 'fw:next' }
192
+ check:
193
+ kind: grep_absent
194
+ pattern: '(dangerouslySetInnerHTML|innerHTML\s*=|v-html|\$\{.*\}\s*.*<script|document\.write\s*\()'
195
+ include: ['**/*.{ts,tsx,js,jsx,vue,svelte,html,php,rb}']
196
+ exclude: ['**/*.test.*', '**/*.spec.*']
197
+ why: 'Rendering untrusted strings as HTML is stored XSS. Frameworks do not save you if you opt out.'
198
+ remediation: 'Render as text. If HTML is genuinely required, sanitise with DOMPurify/server-side sanitiser first.'
199
+ references: ['CWE-79', 'ASVS-5.3.1', 'OWASP-A03:2021']
200
+
201
+ - id: SEC-010
202
+ title: Path traversal is prevented on filesystem access
203
+ section: S2
204
+ section_title: Security
205
+ severity: HIGH
206
+ class: security
207
+ check:
208
+ kind: grep_wrong
209
+ pattern: '(fs\.(read|write|createReadStream|createWriteStream)|open\(|os\.path\.join|path\.join)\s*\(.*(req\.|params\.|query\.|body\.|input|\$\{)'
210
+ include: ['**/*.{ts,tsx,js,jsx,mjs,cjs,py,go,rs,java,kt,rb,php}']
211
+ exclude: ['**/*.test.*', '**/*.spec.*', '**/scripts/**', '**/tools/**']
212
+ why: '`../../etc/passwd` is still the single most reliable bug class in file-serving code.'
213
+ remediation: 'Resolve the path, then verify it is inside the intended base directory before touching the disk.'
214
+ references: ['CWE-22', 'ASVS-12.5.1']
215
+
216
+ # ------------------------------------------------------------------ auth
217
+ - id: SEC-011
218
+ title: Passwords use a modern adaptive hash
219
+ section: S2
220
+ section_title: Security
221
+ severity: CRITICAL
222
+ class: security
223
+ applies_when: { fact: 'has:crypto' }
224
+ check:
225
+ kind: grep_absent
226
+ pattern: '(createHash\s*\(\s*[''"]?(md5|sha1|sha256)|hashlib\.(md5|sha1|sha256)\s*\(|MessageDigest\.getInstance\(\s*[''"]?(MD5|SHA-1|SHA-256)|Digest::(MD5|SHA1)\b)'
227
+ include: ['**/*.{ts,tsx,js,jsx,mjs,cjs,py,go,rs,java,kt,rb,php}']
228
+ exclude: ['**/*.test.*', '**/*.spec.*']
229
+ flags: i
230
+ why: 'A fast hash is a slow password check and a fast offline cracking job. GPUs do billions of SHA-256 per second.'
231
+ remediation: 'Use bcrypt (cost ≥12), scrypt, or argon2id. Never a plain digest, never reversible encryption.'
232
+ references: ['CWE-916', 'ASVS-2.4.1', 'OWASP-Password-Storage-Cheat-Sheet']
233
+
234
+ - id: SEC-012
235
+ title: Tokens and sessions expire
236
+ section: S2
237
+ section_title: Security
238
+ severity: MEDIUM
239
+ class: security
240
+ applies_when:
241
+ any:
242
+ - { fact: 'auth:jwt' }
243
+ - { fact: 'auth:session' }
244
+ check:
245
+ kind: grep_present
246
+ pattern: '(expiresIn|expire|maxAge|ttl|EXPIR|expires_at|refresh[_-]?token)'
247
+ include: ['**/*.{ts,tsx,js,jsx,mjs,cjs,py,go,rs,java,kt}']
248
+ exclude: ['**/*.test.*', '**/*.spec.*']
249
+ flags: i
250
+ why: 'A token that never expires is a permanent credential the moment it leaks.'
251
+ remediation: 'Short-lived access tokens (≤15 min) plus refresh tokens with rotation and server-side revocation.'
252
+ references: ['ASVS-3.3.1', 'ASVS-3.3.4']
253
+
254
+ - id: SEC-013
255
+ title: JWTs are not accepted with alg:none or unverified
256
+ section: S2
257
+ section_title: Security
258
+ severity: HIGH
259
+ class: security
260
+ applies_when: { fact: 'auth:jwt' }
261
+ check:
262
+ kind: grep_absent
263
+ # NOTE: the final alternative requires the quote after the comma: it
264
+ # flags `verify(token, "hardcoded-secret")` but must NOT flag
265
+ # `verify(token, SECRET)` — an earlier revision had `[''"]?` (optional)
266
+ # and failed every correct two-argument verify call. The `?` must never
267
+ # come back.
268
+ pattern: '(algorithms?\s*:\s*\[?\s*[''"]none|alg\s*:\s*[''"]none|jwt\.decode\s*\((?!.*verify)|verify\s*:\s*false|jwt\.verify\s*\(\s*[^,)]+\s*,\s*[''"])'
269
+ include: ['**/*.{ts,tsx,js,jsx,mjs,cjs,py,go,rs,java,kt}']
270
+ exclude: ['**/*.test.*', '**/*.spec.*']
271
+ flags: i
272
+ why: 'alg:none and unverified decode let anyone mint a token for any user.'
273
+ remediation: 'Always verify with an explicit algorithm allowlist: jwt.verify(token, secret, { algorithms: ["RS256"] }).'
274
+ references: ['CWE-347', 'ASVS-3.2.3']
275
+
276
+ - id: SEC-014
277
+ title: Failed authentication is rate limited
278
+ section: S2
279
+ section_title: Security
280
+ severity: HIGH
281
+ class: security
282
+ applies_when:
283
+ any:
284
+ - { fact: 'auth:jwt' }
285
+ - { fact: 'auth:session' }
286
+ - { fact: 'auth:oauth' }
287
+ - { fact: 'auth:apikey' }
288
+ check:
289
+ kind: grep_present
290
+ pattern: '(rate[_-]?limit|rateLimit|throttle|slowapi|express-rate-limit|@nestjs/throttler|tower.*limit|429)'
291
+ include: ['**/*.{ts,tsx,js,jsx,mjs,cjs,py,go,rs,java,kt,yml,yaml,conf,nginx}']
292
+ exclude: ['**/*.test.*', '**/*.spec.*']
293
+ flags: i
294
+ why: 'Without throttling, credential stuffing is just a for-loop with a wordlist.'
295
+ remediation: 'Rate-limit by IP and by account on all auth endpoints; add progressive backoff and lockout after N failures.'
296
+ references: ['CWE-307', 'ASVS-2.2.1']
297
+
298
+ - id: SEC-015
299
+ title: Authorization is enforced per request, not per route
300
+ section: S2
301
+ section_title: Security
302
+ severity: HIGH
303
+ class: security
304
+ applies_when:
305
+ any:
306
+ - { fact: 'auth:jwt' }
307
+ - { fact: 'auth:session' }
308
+ - { fact: 'auth:oauth' }
309
+ - { fact: 'has:database' }
310
+ check: { kind: manual }
311
+ why: >
312
+ Checking "is logged in" is not checking "may access THIS record". IDOR is
313
+ the most common authorization bug and no linter finds it.
314
+ evidence: 'For each data endpoint: file:line showing the ownership/permission check on the requested resource ID.'
315
+ remediation: "Centralise authorisation in middleware or a policy layer; test with a second user attempting to read the first user's records."
316
+ references: ['CWE-639', 'ASVS-4.2.1', 'OWASP-A01:2021']
317
+
318
+ - id: SEC-016
319
+ title: Secrets come from a manager in production
320
+ section: S2
321
+ section_title: Security
322
+ severity: HIGH
323
+ class: security
324
+ applies_when: { fact: 'maturity:production' }
325
+ check: { kind: manual }
326
+ why: >-
327
+ Env files and CI variables are readable by everyone who can deploy. A secret manager adds rotation, audit, and revocation.
328
+ evidence: 'Deployment configuration showing secret injection (Vault, AWS/GCP Secrets Manager, Doppler, platform secrets).'
329
+ remediation: 'Move runtime secrets out of env files into a managed secret store with audit logging and rotation.'
330
+ references: ['ASVS-14.3.3', 'NIST-SSDF-PO.3']
331
+
332
+ # ------------------------------------------------------------- transports
333
+ - id: SEC-017
334
+ title: CORS is not a wildcard
335
+ section: S2
336
+ section_title: Security
337
+ severity: HIGH
338
+ class: security
339
+ applies_when:
340
+ any:
341
+ - { fact: 'project:api' }
342
+ - { fact: 'platform:server' }
343
+ check:
344
+ kind: grep_absent
345
+ pattern: '(?:"|\s)(Access-Control-Allow-Origin|origin)\s*[:=]\s*[''"]?\*[''"]?|cors\s*\(\s*\{\s*origin\s*:\s*[''"]\*'
346
+ include: ['**/*.{ts,tsx,js,jsx,mjs,cjs,py,go,rs,java,kt,yml,yaml,json,conf,nginx}']
347
+ exclude: ['**/*.test.*', '**/*.spec.*']
348
+ flags: i
349
+ why: '`Access-Control-Allow-Origin: *` lets any site read authenticated responses from your API.'
350
+ remediation: 'Allowlist exact origins; only use * for genuinely public, unauthenticated assets.'
351
+ references: ['CWE-942', 'ASVS-14.5.3']
352
+
353
+ - id: SEC-018
354
+ title: Security headers are set
355
+ section: S2
356
+ section_title: Security
357
+ severity: MEDIUM
358
+ class: security
359
+ applies_when:
360
+ any:
361
+ - { fact: 'platform:web' }
362
+ - { fact: 'platform:server' }
363
+ check:
364
+ kind: grep_present
365
+ pattern: '(helmet|Content-Security-Policy|Strict-Transport-Security|X-Frame-Options|X-Content-Type-Options|Referrer-Policy|Permissions-Policy)'
366
+ include: ['**/*.{ts,tsx,js,jsx,mjs,cjs,py,go,rs,java,kt,yml,yaml,json,conf,nginx,toml}']
367
+ flags: i
368
+ why: 'Four headers turn whole classes of browser attack off. They cost about six lines.'
369
+ remediation: 'Use helmet (Node) or equivalent; add HSTS (max-age ≥31536000; includeSubDomains), X-Content-Type-Options: nosniff, Referrer-Policy, and a CSP.'
370
+ references: ['ASVS-14.4.3', 'OWASP-Secure-Headers']
371
+
372
+ - id: SEC-019
373
+ title: CSRF protection for cookie-based sessions
374
+ section: S2
375
+ section_title: Security
376
+ severity: MEDIUM
377
+ class: security
378
+ applies_when: { fact: 'auth:session' }
379
+ check: { kind: manual }
380
+ why: >-
381
+ Without SameSite and a token, any site on the internet can submit forms as your logged-in users.
382
+ evidence: 'SameSite cookie attribute plus a CSRF token or double-submit check on every state-changing request.'
383
+ remediation: 'Set cookies SameSite=Lax (or Strict) and require a CSRF token on all non-GET requests.'
384
+ references: ['CWE-352', 'ASVS-4.2.2']
385
+
386
+ - id: SEC-020
387
+ title: Errors do not leak internals to clients
388
+ section: S2
389
+ section_title: Security
390
+ severity: MEDIUM
391
+ class: security
392
+ applies_when: { fact: 'platform:server' }
393
+ check:
394
+ kind: grep_wrong
395
+ pattern: '(res\.(send|json|status)\s*\(.*\berr\.(stack|message)|\.stack\b.*(res|response)\.(send|json)|app\.use\(\s*\(err, req, res.*res\.send\(\s*err\.stack)'
396
+ include: ['**/*.{ts,tsx,js,jsx,mjs,cjs,py,go,rs,java,kt}']
397
+ exclude: ['**/*.test.*', '**/*.spec.*']
398
+ flags: i
399
+ why: 'Stack traces hand an attacker your file layout, framework versions, and sometimes connection strings.'
400
+ remediation: 'Return a generic error plus a correlation ID; log the detail server-side only.'
401
+ references: ['CWE-209', 'ASVS-7.4.1']
402
+
403
+ - id: SEC-021
404
+ title: Cryptographic randomness for tokens and IDs
405
+ section: S2
406
+ section_title: Security
407
+ severity: MEDIUM
408
+ class: security
409
+ check:
410
+ kind: grep_wrong
411
+ pattern: 'Math\.random\s*\(\s*\)|random\.random\s*\(\s*\)|rand\.Intn\s*\(|new Random\s*\(\s*\)'
412
+ include: ['**/*.{ts,tsx,js,jsx,mjs,cjs,py,go,rs,java,kt}']
413
+ exclude:
414
+ [
415
+ '**/*.test.*',
416
+ '**/*.spec.*',
417
+ '**/test/**',
418
+ '**/tests/**',
419
+ '**/benchmarks/**',
420
+ '**/__mocks__/**',
421
+ ]
422
+ why: 'Math.random and friends are predictable. Session tokens built from them are guessable.'
423
+ remediation: 'Use crypto.randomBytes / secrets.token_urlsafe / crypto/rand / SecureRandom for anything security-relevant.'
424
+ references: ['CWE-338', 'ASVS-6.3.1']
425
+
426
+ - id: SEC-022
427
+ title: Uploads are validated for type, size, and content
428
+ section: S2
429
+ section_title: Security
430
+ severity: HIGH
431
+ class: security
432
+ applies_when: { fact: 'has:file-upload' }
433
+ check: { kind: manual }
434
+ why: >-
435
+ An upload endpoint that trusts the client's Content-Type is a remote code execution endpoint with extra steps.
436
+ evidence: 'Upload handler: allowed MIME allowlist, max size, and where the file is written (outside the web root).'
437
+ remediation: 'Allowlist extensions AND sniff content; cap size; store outside the served directory; serve from a separate origin.'
438
+ references: ['CWE-434', 'ASVS-12.5.2']
439
+
440
+ - id: SEC-023
441
+ title: PII is identified and protected at rest
442
+ section: S2
443
+ section_title: Security
444
+ severity: HIGH
445
+ class: compliance
446
+ applies_when: { fact: 'has:pii' }
447
+ check: { kind: manual }
448
+ why: >-
449
+ You cannot protect data you have not inventoried. And regulators start from the inventory, not the intent.
450
+ evidence: 'Data inventory listing PII fields, their storage location, encryption status, and retention period.'
451
+ remediation: 'Encrypt PII columns/tables at rest, restrict access by role, and set a retention/erasure schedule.'
452
+ references: ['GDPR-Art.32', 'ASVS-8.1.1', 'CWE-311']
453
+
454
+ - id: SEC-024
455
+ title: Secrets and PII are redacted in logs and telemetry
456
+ section: S2
457
+ section_title: Security
458
+ severity: MEDIUM
459
+ class: compliance
460
+ applies_when:
461
+ any:
462
+ - { fact: 'has:pii' }
463
+ - { fact: 'auth:jwt' }
464
+ check: { kind: manual }
465
+ why: >-
466
+ Logs outlive the systems that wrote them, get shipped to vendors, and are usually the least protected copy of your data.
467
+ evidence: 'Sample log lines showing that tokens, emails, and identifiers are masked or hashed.'
468
+ remediation: 'Add a serialiser that redacts a denylist of keys before writing; never log whole request bodies.'
469
+ references: ['CWE-532', 'GDPR-Art.5']
470
+
471
+ - id: SEC-025
472
+ title: Adaptive password hashing is present where auth exists
473
+ section: S2
474
+ section_title: Security
475
+ severity: HIGH
476
+ class: security
477
+ applies_when:
478
+ any:
479
+ - { fact: 'auth:jwt' }
480
+ - { fact: 'auth:session' }
481
+ - { fact: 'auth:oauth' }
482
+ - { fact: 'auth:apikey' }
483
+ check:
484
+ kind: grep_present
485
+ # NOTE: companion to SEC-011, which only proves weak hashes are absent.
486
+ # Absence of md5 is not presence of bcrypt: plaintext password storage
487
+ # passed SEC-011 cleanly. This rule demands positive evidence of an
488
+ # adaptive function. HIGH, not CRITICAL: presence-grep cannot prove the
489
+ # hash actually guards the password path (that is judgement-queue work).
490
+ pattern: '(bcrypt|argon2(id)?|scrypt|pbkdf2|password_hash|BCryptPasswordEncoder|passlib|generate_password_hash|hashpw)'
491
+ include: ['**/*.{ts,tsx,js,jsx,mjs,cjs,py,go,rs,java,kt,rb,php}']
492
+ exclude: ['**/*.test.*', '**/*.spec.*']
493
+ flags: i
494
+ why: 'No adaptive hash found anywhere auth exists. Either passwords are stored weakly (or plainly), or hashing lives outside the scanned tree — both need a human to confirm.'
495
+ remediation: 'Hash passwords with bcrypt (cost ≥12), scrypt, or argon2id at registration and verify on login. Never a plain digest, never reversible encryption.'
496
+ references: ['CWE-916', 'ASVS-2.4.1', 'OWASP-Password-Storage-Cheat-Sheet']