hullproof 0.0.0-stage → 0.5.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 (363) hide show
  1. package/LICENSE +222 -0
  2. package/NOTICE +32 -0
  3. package/README.md +313 -2
  4. package/checks/db.firebase-rules-not-open.json +24 -0
  5. package/checks/db.rls-every-table.json +25 -0
  6. package/checks/log.no-credential-in-logs.json +30 -0
  7. package/checks/secrets.no-env-file-committed.json +31 -0
  8. package/checks/secrets.no-secret-in-public-env.json +37 -0
  9. package/checks/storage.firebase-rules-not-open.json +22 -0
  10. package/checks/supply.no-untrusted-event-in-run.json +23 -0
  11. package/checks/web.cors-no-reflected-origin.json +31 -0
  12. package/detectors/ai.json +25 -0
  13. package/detectors/auth.json +27 -0
  14. package/detectors/ci.json +19 -0
  15. package/detectors/containers.json +8 -0
  16. package/detectors/data_stores.json +38 -0
  17. package/detectors/frameworks.json +94 -0
  18. package/detectors/hosting.json +44 -0
  19. package/detectors/iac.json +43 -0
  20. package/detectors/languages.json +48 -0
  21. package/detectors/mobile.json +22 -0
  22. package/detectors/package_managers.json +39 -0
  23. package/detectors/payments.json +19 -0
  24. package/detectors/runtimes.json +17 -0
  25. package/dist/checks/code/admin-role.js +46 -0
  26. package/dist/checks/code/owner-filter.js +91 -0
  27. package/dist/checks/code/shared.js +84 -0
  28. package/dist/checks/code/webhook-verify.js +77 -0
  29. package/dist/checks/context.js +64 -0
  30. package/dist/checks/map/config-checks.js +74 -0
  31. package/dist/checks/map/entry-checks.js +97 -0
  32. package/dist/checks/map/shared.js +44 -0
  33. package/dist/checks/ops.js +307 -0
  34. package/dist/checks/pro-map.js +1 -0
  35. package/dist/checks/run.js +92 -0
  36. package/dist/checks/types.js +1 -0
  37. package/dist/ci/comment.js +44 -0
  38. package/dist/ci/sarif-check.js +157 -0
  39. package/dist/ci/sarif.js +61 -0
  40. package/dist/cli.js +358 -0
  41. package/dist/commands/ci.js +115 -0
  42. package/dist/commands/git.js +58 -0
  43. package/dist/commands/hooks.js +30 -0
  44. package/dist/commands/hunt-pro.js +1 -0
  45. package/dist/commands/hunt.js +86 -0
  46. package/dist/commands/map.js +33 -0
  47. package/dist/commands/mcp.js +17 -0
  48. package/dist/commands/pack.js +48 -0
  49. package/dist/commands/prove-pro.js +1 -0
  50. package/dist/commands/prove.js +173 -0
  51. package/dist/commands/report-gate.js +92 -0
  52. package/dist/commands/scan.js +158 -0
  53. package/dist/confirm/agreement.js +18 -0
  54. package/dist/confirm/chain.js +84 -0
  55. package/dist/confirm/confirm.js +112 -0
  56. package/dist/confirm/honesty.js +75 -0
  57. package/dist/confirm/lead.js +92 -0
  58. package/dist/confirm/merge.js +88 -0
  59. package/dist/confirm/query.js +145 -0
  60. package/dist/confirm/rule.js +86 -0
  61. package/dist/confirm/store.js +42 -0
  62. package/dist/confirm/summary.js +6 -0
  63. package/dist/confirm/synthesis.js +28 -0
  64. package/dist/discover/detectors.js +50 -0
  65. package/dist/discover/discover.js +270 -0
  66. package/dist/discover/extract.js +282 -0
  67. package/dist/discover/git.js +40 -0
  68. package/dist/discover/kit-files.js +47 -0
  69. package/dist/discover/walk.js +110 -0
  70. package/dist/env.js +57 -0
  71. package/dist/gate/gate.js +173 -0
  72. package/dist/hooks/install.js +153 -0
  73. package/dist/hooks/run.js +112 -0
  74. package/dist/map/analyze.js +201 -0
  75. package/dist/map/build.js +253 -0
  76. package/dist/map/candidates.js +49 -0
  77. package/dist/map/cblocks.js +77 -0
  78. package/dist/map/config.js +291 -0
  79. package/dist/map/dotnet.js +242 -0
  80. package/dist/map/entry.js +78 -0
  81. package/dist/map/expo.js +190 -0
  82. package/dist/map/go.js +292 -0
  83. package/dist/map/js-express.js +143 -0
  84. package/dist/map/js-misc.js +295 -0
  85. package/dist/map/js-next.js +292 -0
  86. package/dist/map/laravel.js +389 -0
  87. package/dist/map/lex-more.js +305 -0
  88. package/dist/map/lex.js +425 -0
  89. package/dist/map/light-index.js +191 -0
  90. package/dist/map/light-profiles.js +324 -0
  91. package/dist/map/light.js +574 -0
  92. package/dist/map/local-vocab.js +27 -0
  93. package/dist/map/mobile.js +326 -0
  94. package/dist/map/py-django.js +281 -0
  95. package/dist/map/py.js +391 -0
  96. package/dist/map/rails.js +391 -0
  97. package/dist/map/repo.js +178 -0
  98. package/dist/map/rust.js +312 -0
  99. package/dist/map/spring.js +207 -0
  100. package/dist/map/taint.js +96 -0
  101. package/dist/map/touches.js +355 -0
  102. package/dist/map/vocab.js +88 -0
  103. package/dist/mapping/controls.js +34 -0
  104. package/dist/mapping/dependency-policy.js +44 -0
  105. package/dist/mapping/map.js +44 -0
  106. package/dist/mapping/maps/gitleaks.json +12 -0
  107. package/dist/mapping/maps/osv-scanner.json +7 -0
  108. package/dist/mapping/maps/trivy.json +13 -0
  109. package/dist/mcp/confine.js +59 -0
  110. package/dist/mcp/protocol.js +140 -0
  111. package/dist/mcp/schema.js +43 -0
  112. package/dist/mcp/server.js +37 -0
  113. package/dist/mcp/tools.js +238 -0
  114. package/dist/packs/coverage.js +79 -0
  115. package/dist/packs/manifest.js +36 -0
  116. package/dist/packs/validate.js +271 -0
  117. package/dist/prove/container.js +276 -0
  118. package/dist/prove/exec-main.js +20 -0
  119. package/dist/prove/executor.js +149 -0
  120. package/dist/prove/local.js +131 -0
  121. package/dist/prove/probes.js +65 -0
  122. package/dist/prove/recipe.js +278 -0
  123. package/dist/prove/run.js +136 -0
  124. package/dist/prove/runner.js +1 -0
  125. package/dist/prove/safety.js +126 -0
  126. package/dist/prove/spec.js +357 -0
  127. package/dist/prove/store.js +65 -0
  128. package/dist/report/build.js +297 -0
  129. package/dist/report/entry-points.js +75 -0
  130. package/dist/report/hunt-section.js +71 -0
  131. package/dist/report/kit.js +41 -0
  132. package/dist/report/load.js +35 -0
  133. package/dist/report/messages.js +273 -0
  134. package/dist/report/prove-section.js +65 -0
  135. package/dist/report/report.js +156 -0
  136. package/dist/report/surface.js +21 -0
  137. package/dist/report/test-code.js +51 -0
  138. package/dist/report/tiers.js +44 -0
  139. package/dist/scanners/common.js +162 -0
  140. package/dist/scanners/gitleaks.js +90 -0
  141. package/dist/scanners/index.js +113 -0
  142. package/dist/scanners/osv-scanner.js +77 -0
  143. package/dist/scanners/run-targets.js +34 -0
  144. package/dist/scanners/secret-noise.js +89 -0
  145. package/dist/scanners/semgrep.js +142 -0
  146. package/dist/scanners/trivy.js +65 -0
  147. package/dist/scanners/types.js +1 -0
  148. package/dist/types/check.js +1 -0
  149. package/dist/types/codemap.js +1 -0
  150. package/dist/types/findings.js +5 -0
  151. package/dist/types/hunt.js +1 -0
  152. package/dist/types/index.js +1 -0
  153. package/dist/types/prove.js +1 -0
  154. package/dist/types/results.js +2 -0
  155. package/dist/types/scan.js +1 -0
  156. package/dist/types/stack.js +5 -0
  157. package/dist/util.js +71 -0
  158. package/dist/version.js +2 -0
  159. package/package.json +46 -4
  160. package/packs/android/fixture-result.json +43 -0
  161. package/packs/android/pack.json +56 -0
  162. package/packs/dotnet/fixture-result.json +62 -0
  163. package/packs/dotnet/pack.json +88 -0
  164. package/packs/expo/answer-key.json +51 -0
  165. package/packs/expo/fixture-result.json +62 -0
  166. package/packs/expo/pack.json +79 -0
  167. package/packs/firebase/answer-key.json +27 -0
  168. package/packs/firebase/fixture-result.json +43 -0
  169. package/packs/firebase/pack.json +58 -0
  170. package/packs/go/answer-key.json +101 -0
  171. package/packs/go/fixture-result.json +64 -0
  172. package/packs/go/pack.json +99 -0
  173. package/packs/ios/fixture-result.json +43 -0
  174. package/packs/ios/pack.json +55 -0
  175. package/packs/laravel/answer-key.json +84 -0
  176. package/packs/laravel/fixture-result.json +60 -0
  177. package/packs/laravel/pack.json +91 -0
  178. package/packs/node-ts/answer-key.json +220 -0
  179. package/packs/node-ts/fixture-result.json +114 -0
  180. package/packs/node-ts/pack.json +134 -0
  181. package/packs/python/answer-key.json +195 -0
  182. package/packs/python/fixture-result.json +119 -0
  183. package/packs/python/pack.json +121 -0
  184. package/packs/rails/answer-key.json +84 -0
  185. package/packs/rails/fixture-result.json +60 -0
  186. package/packs/rails/pack.json +78 -0
  187. package/packs/rust/fixture-result.json +43 -0
  188. package/packs/rust/pack.json +69 -0
  189. package/packs/spring/fixture-result.json +63 -0
  190. package/packs/spring/pack.json +103 -0
  191. package/packs/supabase/answer-key.json +52 -0
  192. package/packs/supabase/fixture-result.json +58 -0
  193. package/packs/supabase/pack.json +60 -0
  194. package/rules/gitleaks.toml +256 -0
  195. package/rules/semgrep/api-route-no-auth.yaml +31 -0
  196. package/rules/semgrep/cors-reflect-origin.yaml +48 -0
  197. package/rules/semgrep/eval-or-new-function.yaml +185 -0
  198. package/rules/semgrep/log-session-token.yaml +48 -0
  199. package/rules/semgrep/math-random-tokens.yaml +24 -0
  200. package/rules/semgrep/next-public-secret-name.yaml +32 -0
  201. package/rules/semgrep/postgrest-filter-injection.yaml +78 -0
  202. package/rules/semgrep/raw-sql-concat.yaml +42 -0
  203. package/rules/semgrep/read-by-id-no-owner.yaml +49 -0
  204. package/rules/semgrep/server-action-no-auth.yaml +26 -0
  205. package/rules/semgrep/sql-variants.yaml +99 -0
  206. package/rules/semgrep/ssrf-unguarded-url.yaml +137 -0
  207. package/rules/semgrep/subprocess-injection.yaml +163 -0
  208. package/rules/semgrep/taint-go.yaml +333 -0
  209. package/rules/semgrep/taint-java.yaml +317 -0
  210. package/rules/semgrep/taint-javascript.yaml +412 -0
  211. package/rules/semgrep/taint-php.yaml +361 -0
  212. package/rules/semgrep/taint-python.yaml +414 -0
  213. package/rules/semgrep/tool-endpoint-admin-client.yaml +86 -0
  214. package/rules/semgrep/user-metadata-authz.yaml +22 -0
  215. package/rules/semgrep/verify-result-unused.yaml +62 -0
  216. package/rules/semgrep/webhook-parse-before-verify.yaml +29 -0
  217. package/rules/test-artifact-ids.json +4 -0
  218. package/rules/tiers.json +427 -0
  219. package/schemas/check-results.schema.json +55 -0
  220. package/schemas/check.schema.json +41 -0
  221. package/schemas/codemap.schema.json +216 -0
  222. package/schemas/findings.schema.json +1048 -0
  223. package/schemas/hunt-output.schema.json +46 -0
  224. package/schemas/hunt-result.schema.json +58 -0
  225. package/schemas/results.schema.json +17 -0
  226. package/schemas/scan-hits.schema.json +32 -0
  227. package/schemas/scan-meta.schema.json +25 -0
  228. package/schemas/stack.schema.json +93 -0
  229. package/src/checks/README.md +32 -0
  230. package/src/checks/code/admin-role.ts +49 -0
  231. package/src/checks/code/owner-filter.ts +95 -0
  232. package/src/checks/code/shared.ts +66 -0
  233. package/src/checks/code/webhook-verify.ts +75 -0
  234. package/src/checks/context.ts +72 -0
  235. package/src/checks/map/config-checks.ts +79 -0
  236. package/src/checks/map/entry-checks.ts +116 -0
  237. package/src/checks/map/shared.ts +63 -0
  238. package/src/checks/ops.ts +280 -0
  239. package/src/checks/pro-map.ts +4 -0
  240. package/src/checks/run.ts +80 -0
  241. package/src/checks/types.ts +12 -0
  242. package/src/ci/comment.ts +52 -0
  243. package/src/ci/sarif-check.ts +100 -0
  244. package/src/ci/sarif.ts +72 -0
  245. package/src/cli.ts +273 -0
  246. package/src/commands/ci.ts +104 -0
  247. package/src/commands/git.ts +59 -0
  248. package/src/commands/hooks.ts +27 -0
  249. package/src/commands/hunt-pro.ts +4 -0
  250. package/src/commands/hunt.ts +68 -0
  251. package/src/commands/map.ts +27 -0
  252. package/src/commands/mcp.ts +18 -0
  253. package/src/commands/pack.ts +34 -0
  254. package/src/commands/prove-pro.ts +4 -0
  255. package/src/commands/prove.ts +126 -0
  256. package/src/commands/report-gate.ts +75 -0
  257. package/src/commands/scan.ts +156 -0
  258. package/src/confirm/agreement.ts +23 -0
  259. package/src/confirm/chain.ts +67 -0
  260. package/src/confirm/confirm.ts +107 -0
  261. package/src/confirm/honesty.ts +65 -0
  262. package/src/confirm/lead.ts +76 -0
  263. package/src/confirm/merge.ts +89 -0
  264. package/src/confirm/query.ts +126 -0
  265. package/src/confirm/rule.ts +76 -0
  266. package/src/confirm/store.ts +42 -0
  267. package/src/confirm/summary.ts +9 -0
  268. package/src/confirm/synthesis.ts +33 -0
  269. package/src/discover/detectors.ts +71 -0
  270. package/src/discover/discover.ts +253 -0
  271. package/src/discover/extract.ts +235 -0
  272. package/src/discover/git.ts +31 -0
  273. package/src/discover/kit-files.ts +37 -0
  274. package/src/discover/walk.ts +88 -0
  275. package/src/env.ts +51 -0
  276. package/src/gate/gate.ts +169 -0
  277. package/src/hooks/install.ts +142 -0
  278. package/src/hooks/run.ts +121 -0
  279. package/src/map/analyze.ts +219 -0
  280. package/src/map/build.ts +206 -0
  281. package/src/map/candidates.ts +47 -0
  282. package/src/map/cblocks.ts +55 -0
  283. package/src/map/config.ts +207 -0
  284. package/src/map/dotnet.ts +205 -0
  285. package/src/map/entry.ts +80 -0
  286. package/src/map/expo.ts +171 -0
  287. package/src/map/go.ts +271 -0
  288. package/src/map/js-express.ts +135 -0
  289. package/src/map/js-misc.ts +221 -0
  290. package/src/map/js-next.ts +282 -0
  291. package/src/map/laravel.ts +327 -0
  292. package/src/map/lex-more.ts +152 -0
  293. package/src/map/lex.ts +274 -0
  294. package/src/map/light-index.ts +134 -0
  295. package/src/map/light-profiles.ts +363 -0
  296. package/src/map/light.ts +484 -0
  297. package/src/map/local-vocab.ts +30 -0
  298. package/src/map/mobile.ts +263 -0
  299. package/src/map/py-django.ts +239 -0
  300. package/src/map/py.ts +329 -0
  301. package/src/map/rails.ts +291 -0
  302. package/src/map/repo.ts +150 -0
  303. package/src/map/rust.ts +256 -0
  304. package/src/map/spring.ts +185 -0
  305. package/src/map/taint.ts +86 -0
  306. package/src/map/touches.ts +296 -0
  307. package/src/map/vocab.ts +106 -0
  308. package/src/mapping/controls.ts +47 -0
  309. package/src/mapping/dependency-policy.ts +55 -0
  310. package/src/mapping/map.ts +59 -0
  311. package/src/mapping/maps/gitleaks.json +12 -0
  312. package/src/mapping/maps/osv-scanner.json +7 -0
  313. package/src/mapping/maps/trivy.json +13 -0
  314. package/src/mcp/confine.ts +44 -0
  315. package/src/mcp/protocol.ts +144 -0
  316. package/src/mcp/schema.ts +35 -0
  317. package/src/mcp/server.ts +32 -0
  318. package/src/mcp/tools.ts +235 -0
  319. package/src/packs/coverage.ts +98 -0
  320. package/src/packs/manifest.ts +97 -0
  321. package/src/packs/validate.ts +204 -0
  322. package/src/prove/container.ts +237 -0
  323. package/src/prove/exec-main.ts +16 -0
  324. package/src/prove/executor.ts +130 -0
  325. package/src/prove/local.ts +100 -0
  326. package/src/prove/probes.ts +68 -0
  327. package/src/prove/recipe.ts +215 -0
  328. package/src/prove/run.ts +124 -0
  329. package/src/prove/runner.ts +28 -0
  330. package/src/prove/safety.ts +98 -0
  331. package/src/prove/spec.ts +256 -0
  332. package/src/prove/store.ts +48 -0
  333. package/src/report/build.ts +304 -0
  334. package/src/report/entry-points.ts +78 -0
  335. package/src/report/hunt-section.ts +67 -0
  336. package/src/report/kit.ts +59 -0
  337. package/src/report/load.ts +49 -0
  338. package/src/report/messages.ts +270 -0
  339. package/src/report/prove-section.ts +61 -0
  340. package/src/report/report.ts +160 -0
  341. package/src/report/surface.ts +32 -0
  342. package/src/report/test-code.ts +56 -0
  343. package/src/report/tiers.ts +50 -0
  344. package/src/scanners/common.ts +149 -0
  345. package/src/scanners/gitleaks.ts +89 -0
  346. package/src/scanners/index.ts +103 -0
  347. package/src/scanners/osv-scanner.ts +90 -0
  348. package/src/scanners/run-targets.ts +35 -0
  349. package/src/scanners/secret-noise.ts +80 -0
  350. package/src/scanners/semgrep.ts +139 -0
  351. package/src/scanners/trivy.ts +66 -0
  352. package/src/scanners/types.ts +98 -0
  353. package/src/types/check.ts +52 -0
  354. package/src/types/codemap.ts +142 -0
  355. package/src/types/findings.ts +107 -0
  356. package/src/types/hunt.ts +97 -0
  357. package/src/types/index.ts +10 -0
  358. package/src/types/prove.ts +83 -0
  359. package/src/types/results.ts +15 -0
  360. package/src/types/scan.ts +63 -0
  361. package/src/types/stack.ts +61 -0
  362. package/src/util.ts +59 -0
  363. package/src/version.ts +2 -0
@@ -0,0 +1,31 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2026 Rare Phronesis Limited
3
+ # Copied from the Hullproof kit (kit/tools/hullproof/rules/api-route-no-auth.yaml).
4
+ #
5
+ rules:
6
+ - id: hullproof-api-route-no-auth
7
+ languages: [typescript, javascript]
8
+ severity: ERROR
9
+ message: >-
10
+ An API route handler (GET, POST, PUT, PATCH or DELETE) reads from a database or data client and nothing in the handler
11
+ reads a session, user, token, signature or secret. If the route is not meant to be public, any caller gets the data. Read
12
+ the session first and return 401 before any query, or confirm that the route is public on purpose.
13
+ patterns:
14
+ - pattern-either:
15
+ - pattern: export async function $M(...) { ... $DB.$READ(...); ... }
16
+ - pattern: export const $M = async (...) => { ... $DB.$READ(...); ... }
17
+ - metavariable-regex:
18
+ metavariable: $M
19
+ regex: ^(GET|POST|PUT|PATCH|DELETE)$
20
+ - metavariable-regex:
21
+ metavariable: $READ
22
+ regex: ^(select|findMany|findUnique|findFirst|findUniqueOrThrow|findFirstOrThrow|query|rpc|execute|single|maybeSingle|aggregate|download|list)$
23
+ - pattern-not-regex: (?i)(auth|getUser|getSession|getClaims|session|requireUser|currentUser|verify|signature|constructEvent|jwt|bearer|api_?key|cookies\s*\(|token|clerk|guard|permission|secret)
24
+ metadata:
25
+ sec_ids: [SEC-API-001, SEC-AUTHZ-002]
26
+ severity_hint: HIGH
27
+ confidence: LOW
28
+ note: >-
29
+ The handler text is searched for any of the words above, so one mention anywhere, even in a comment, counts as an auth
30
+ read. A deliberately public route is reported. Route handlers that call a shared wrapper with a name outside the list are
31
+ reported, add the wrapper name to the regex.
@@ -0,0 +1,48 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2026 Rare Phronesis Limited
3
+ # Copied from the Hullproof kit (kit/tools/hullproof/rules/cors-reflect-origin.yaml).
4
+ #
5
+ rules:
6
+ - id: hullproof-cors-reflect-origin
7
+ mode: taint
8
+ languages: [typescript, javascript]
9
+ severity: ERROR
10
+ message: >-
11
+ The request Origin header is copied into Access-Control-Allow-Origin. Compare it to an exact
12
+ allowlist and return the matched value, never the raw header.
13
+ pattern-sources:
14
+ - pattern: $REQ.headers.origin
15
+ - pattern: $REQ.headers["origin"]
16
+ - pattern: $REQ.headers.get("origin")
17
+ - pattern: $REQ.header("origin")
18
+ pattern-sinks:
19
+ - patterns:
20
+ - pattern-either:
21
+ - pattern: $RES.setHeader("Access-Control-Allow-Origin", $SINK)
22
+ - pattern: $RES.set("Access-Control-Allow-Origin", $SINK)
23
+ - pattern: $RES.header("Access-Control-Allow-Origin", $SINK)
24
+ - pattern: $RES.append("Access-Control-Allow-Origin", $SINK)
25
+ - pattern: $H.set("Access-Control-Allow-Origin", $SINK)
26
+ - pattern: '{..., "Access-Control-Allow-Origin": $SINK, ...}'
27
+ - focus-metavariable: $SINK
28
+ - pattern-not-inside: |
29
+ if (<... $L.includes(...) ...>) { ... }
30
+ - pattern-not-inside: |
31
+ if (<... $L.has(...) ...>) { ... }
32
+ metadata:
33
+ sec_ids: [SEC-WEB-022]
34
+ severity_hint: HIGH
35
+ confidence: MEDIUM
36
+ - id: hullproof-cors-origin-true
37
+ languages: [typescript, javascript]
38
+ severity: ERROR
39
+ message: >-
40
+ CORS is configured to accept any origin. Use an exact allowlist.
41
+ pattern-either:
42
+ - pattern: 'cors({..., origin: true, ...})'
43
+ - pattern: 'cors({..., origin: "*", credentials: true, ...})'
44
+ - pattern: 'cors({..., origin: (...) => true, ...})'
45
+ metadata:
46
+ sec_ids: [SEC-WEB-022]
47
+ severity_hint: HIGH
48
+ confidence: HIGH
@@ -0,0 +1,185 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2026 Rare Phronesis Limited
3
+ # Copied from the Hullproof kit (kit/tools/hullproof/rules/eval-or-new-function.yaml).
4
+ #
5
+ rules:
6
+ - id: hullproof-eval-or-new-function
7
+ languages: [typescript, javascript]
8
+ severity: ERROR
9
+ message: >-
10
+ Dynamic code evaluation. Remove it, or prove the input is a constant. Never evaluate request
11
+ data or model output.
12
+ patterns:
13
+ - pattern-either:
14
+ - pattern: eval(...)
15
+ - pattern: (0, eval)(...)
16
+ - pattern: (0, $G.eval)(...)
17
+ - pattern: $G.eval(...)
18
+ - pattern: new Function(...)
19
+ - pattern: Function(...)
20
+ - pattern: $G.Function(...)
21
+ - pattern: new $G.Function(...)
22
+ - pattern: Function.prototype.constructor(...)
23
+ - pattern: Reflect.construct(Function, ...)
24
+ - pattern: Reflect.apply(eval, ...)
25
+ - pattern: Reflect.apply(Function, ...)
26
+ - pattern: vm.runInNewContext(...)
27
+ - pattern: vm.runInThisContext(...)
28
+ - pattern: vm.runInContext(...)
29
+ - pattern: vm.compileFunction(...)
30
+ - pattern: new vm.Script(...)
31
+ - pattern: new Script(...)
32
+ - pattern: new vm.SourceTextModule(...)
33
+ - pattern: $VM.runInNewContext(...)
34
+ - pattern: $VM.runInThisContext(...)
35
+ - pattern: $VM.runInContext(...)
36
+ - pattern: $VM.compileFunction(...)
37
+ - pattern: new $VM.Script(...)
38
+ - pattern: setTimeout("...", ...)
39
+ - pattern: setInterval("...", ...)
40
+ - pattern: setTimeout($S + $T, ...)
41
+ - pattern: setInterval($S + $T, ...)
42
+ - pattern: setTimeout(`...${$X}...`, ...)
43
+ - pattern: setInterval(`...${$X}...`, ...)
44
+ - pattern: setTimeout($S as any, ...)
45
+ - pattern: setInterval($S as any, ...)
46
+ - pattern: setTimeout($S as string, ...)
47
+ - pattern: setInterval($S as string, ...)
48
+ - pattern: setTimeout(String(...), ...)
49
+ - pattern: setTimeout($S.toString(), ...)
50
+ - pattern-not: eval("...")
51
+ - pattern-not: new Function(..., "...")
52
+ - pattern-not: Function("...")
53
+ - pattern-not: $G.eval("...")
54
+ - pattern-not: new vm.Script("...")
55
+ - pattern-not: new Script("...")
56
+ - pattern-not: new $VM.Script("...")
57
+ - pattern-not: vm.compileFunction("...")
58
+ - pattern-not: $VM.compileFunction("...")
59
+ - pattern-not: vm.runInContext("...", ...)
60
+ - pattern-not: vm.runInNewContext("...", ...)
61
+ - pattern-not: vm.runInThisContext("...")
62
+ metadata:
63
+ sec_ids: [SEC-API-021, SEC-WEB-031]
64
+ severity_hint: HIGH
65
+ confidence: HIGH
66
+ note: >-
67
+ Covers direct and indirect eval, the Function constructor (also through Reflect and a
68
+ global object), vm run and compile calls, and string timers. The global receiver match
69
+ ($G.eval) is broad and also fires on any object with an eval method.
70
+ - id: hullproof-eval-through-alias
71
+ languages: [typescript, javascript]
72
+ severity: ERROR
73
+ message: >-
74
+ eval or the Function constructor is copied to another name and then called. This hides a
75
+ dynamic code call from name based review. Remove it.
76
+ patterns:
77
+ - pattern-either:
78
+ - pattern: |
79
+ $F = eval;
80
+ ...
81
+ $F($X, ...);
82
+ - pattern: |
83
+ $F = Function;
84
+ ...
85
+ $F($X, ...);
86
+ - pattern: |
87
+ $F = Function;
88
+ ...
89
+ new $F($X, ...);
90
+ - pattern: |
91
+ $F = $G.eval;
92
+ ...
93
+ $F($X, ...);
94
+ - pattern: $A.constructor.constructor($X)
95
+ - pattern: (() => {}).constructor($X)
96
+ - pattern: (function () {}).constructor($X)
97
+ - pattern: (async () => {}).constructor($X)
98
+ - pattern-not: $A.constructor.constructor("...")
99
+ metadata:
100
+ sec_ids: [SEC-API-021]
101
+ severity_hint: HIGH
102
+ confidence: MEDIUM
103
+ note: Same block alias only. An alias passed through a function parameter or an import is not followed.
104
+ - id: hullproof-expression-library-eval
105
+ languages: [typescript, javascript]
106
+ severity: ERROR
107
+ message: >-
108
+ A library that interprets text as code or as a template is called with a non constant value.
109
+ Expression and template engines run attacker text with the process's powers. Compile only
110
+ reviewed constants, or evaluate with a sandbox that has no access to globals.
111
+ patterns:
112
+ - pattern-either:
113
+ - patterns:
114
+ - pattern-either:
115
+ - pattern: $M.evaluate($X, ...)
116
+ - pattern: $M.parse($X).evaluate(...)
117
+ - pattern: $M.compile($X)
118
+ - pattern: $M.template($X, ...)
119
+ - pattern: $M.render($X, ...)
120
+ - pattern: $M.renderString($X, ...)
121
+ - pattern: $M.renderToString($X, ...)
122
+ - pattern: $M.run($X, ...)
123
+ - metavariable-regex:
124
+ metavariable: $M
125
+ regex: ^(math|mathjs|parser|expr|jexl|jsonata|_|lodash|ejs|pug|jade|Handlebars|handlebars|hbs|nunjucks|env|Mustache|mustache|doT|dot|liquid|engine|vm2|sandbox|VM)$
126
+ - pattern: jsonata($X)
127
+ - pattern: new Function($X)
128
+ - pattern: new VM(...).run($X)
129
+ - pattern: new NodeVM(...).run($X)
130
+ - pattern: new Parser().evaluate($X)
131
+ - pattern: new Parser().parse($X)
132
+ - pattern: new Jexl().eval($X)
133
+ - patterns:
134
+ - pattern-inside: |
135
+ import {..., evaluate, ...} from "mathjs";
136
+ ...
137
+ - pattern: evaluate($X, ...)
138
+ - patterns:
139
+ - pattern-inside: |
140
+ import {..., compile, ...} from "handlebars";
141
+ ...
142
+ - pattern: compile($X, ...)
143
+ - metavariable-pattern:
144
+ metavariable: $X
145
+ patterns:
146
+ - pattern-not: '"..."'
147
+ - pattern-not: "`...`"
148
+ metadata:
149
+ sec_ids: [SEC-API-021]
150
+ severity_hint: HIGH
151
+ confidence: MEDIUM
152
+ note: >-
153
+ Server template and expression engines (SEC-API-021).
154
+ Receiver names are matched by a regex over common import names. Rename the alias or edit the regex.
155
+ - id: hullproof-dynamic-module-path
156
+ languages: [typescript, javascript]
157
+ severity: WARNING
158
+ message: >-
159
+ A module is loaded from a computed path. If any part comes from a request, a database row or a
160
+ file, the caller chooses which code runs. Load from a fixed map of allowed names instead.
161
+ patterns:
162
+ - pattern-either:
163
+ - pattern: require($X)
164
+ - pattern: import($X)
165
+ - pattern: createRequire(...)($X)
166
+ - pattern: module.require($X)
167
+ - metavariable-pattern:
168
+ metavariable: $X
169
+ patterns:
170
+ - pattern-not: '"..."'
171
+ - pattern-not: "`...`"
172
+ - pattern-not: path.join(__dirname, "...")
173
+ - pattern-not: path.resolve(__dirname, "...")
174
+ - pattern-not: |
175
+ `./...${$Y}...`
176
+ - pattern-not: |
177
+ `../...${$Y}...`
178
+ - pattern-not: require.resolve(...)
179
+ metadata:
180
+ sec_ids: [SEC-API-021]
181
+ severity_hint: MEDIUM
182
+ confidence: LOW
183
+ note: >-
184
+ Relative template paths with a fixed directory prefix are skipped because a bundler limits
185
+ them to that folder. Confirm the variable part cannot contain dot dot segments.
@@ -0,0 +1,48 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2026 Rare Phronesis Limited
3
+ # Copied from the Hullproof kit (kit/tools/hullproof/rules/log-session-token.yaml).
4
+ #
5
+ rules:
6
+ - id: hullproof-log-session-token
7
+ languages: [typescript, javascript]
8
+ severity: WARNING
9
+ message: >-
10
+ A log call receives a value whose name says it is a session token, access token, authorization header, cookie, password or
11
+ secret. Logs are copied to vendors and read by more people than the database. Log a user id or a request id instead,
12
+ and never the credential.
13
+ patterns:
14
+ - pattern-either:
15
+ - patterns:
16
+ - pattern-either:
17
+ - pattern: $L.$M(<... $ARG ...>)
18
+ - pattern: $L.$M(..., <... $ARG ...>, ...)
19
+ - metavariable-regex:
20
+ metavariable: $ARG
21
+ regex: (?i)^(?:[\w$]+\??\.)*[\w$]*(?:accesstoken|refreshtoken|idtoken|sessiontoken|session_token|access_token|refresh_token|id_token|authorization|bearer|jwt|cookie|password|passwd|secret|apikey|api_key)[\w$]*$
22
+ - patterns:
23
+ - pattern-either:
24
+ - pattern: $L.$M($ARG)
25
+ - pattern: $L.$M(..., $ARG, ...)
26
+ - metavariable-regex:
27
+ metavariable: $ARG
28
+ regex: (?i)^(?:[\w$]+\??\.)*(?:token|session|headers)$
29
+ - patterns:
30
+ - pattern-either:
31
+ - pattern: $L.$M(<... $H.get($K) ...>)
32
+ - pattern: $L.$M(..., <... $H.get($K) ...>, ...)
33
+ - metavariable-regex:
34
+ metavariable: $K
35
+ regex: (?i)^["'`](authorization|cookie|x-api-key)["'`]$
36
+ - metavariable-regex:
37
+ metavariable: $L
38
+ regex: ^(console|logger|log|Logger|winston|pino|bunyan|Sentry|this\.logger)$
39
+ - metavariable-regex:
40
+ metavariable: $M
41
+ regex: ^(log|info|warn|error|debug|trace|fatal|captureMessage|addBreadcrumb)$
42
+ metadata:
43
+ sec_ids: [SEC-LOG-001]
44
+ severity_hint: HIGH
45
+ confidence: MEDIUM
46
+ note: >-
47
+ Matches names only. A string literal is not a match. Logging a whole session object or the whole headers object is
48
+ reported because both hold the token. A variable with a neutral name that holds a token is not found.
@@ -0,0 +1,24 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2026 Rare Phronesis Limited
3
+ # Copied from the Hullproof kit (kit/tools/hullproof/rules/math-random-tokens.yaml).
4
+ #
5
+ rules:
6
+ - id: hullproof-math-random-token
7
+ languages: [typescript, javascript]
8
+ severity: ERROR
9
+ message: >-
10
+ Math.random is not a secure random source. Use crypto.randomUUID or crypto.getRandomValues
11
+ for tokens, codes, secrets and identifiers that must be unguessable.
12
+ patterns:
13
+ - pattern-either:
14
+ - pattern: $N = <... Math.random() ...>
15
+ - pattern: function $N(...) { ... Math.random(); ... }
16
+ - pattern: function $N(...) { ... return <... Math.random() ...>; }
17
+ - pattern: '{..., $N: <... Math.random() ...>, ...}'
18
+ - metavariable-regex:
19
+ metavariable: $N
20
+ regex: (?i)^(.*(token|secret|invite|reset|nonce|otp|password|apikey|api_key|session|share|code|salt).*|id|.*_id|[a-z]*Id)$
21
+ metadata:
22
+ sec_ids: [SEC-DATA-001, SEC-AUTH-001]
23
+ severity_hint: HIGH
24
+ confidence: MEDIUM
@@ -0,0 +1,32 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2026 Rare Phronesis Limited
3
+ # Copied from the Hullproof kit (kit/tools/hullproof/rules/next-public-secret-name.yaml).
4
+ #
5
+ rules:
6
+ - id: hullproof-next-public-secret-name
7
+ languages: [typescript, javascript]
8
+ severity: ERROR
9
+ message: >-
10
+ A variable with the NEXT_PUBLIC_ prefix is named like a service role key or a secret. Next.js copies every NEXT_PUBLIC_
11
+ variable into the browser bundle, so the value is public to everyone who loads the site. Rename it without the prefix,
12
+ read it only in server code, and rotate the key if it was ever deployed with the prefix.
13
+ patterns:
14
+ - pattern-either:
15
+ - pattern: process.env.$V
16
+ - pattern: process.env["$V"]
17
+ - pattern: process.env['$V']
18
+ - pattern: import.meta.env.$V
19
+ - pattern: '{..., $V: $X, ...}'
20
+ - pattern: '{..., "$V": $X, ...}'
21
+ - metavariable-regex:
22
+ metavariable: $V
23
+ regex: (?i)^NEXT_PUBLIC_.*(SERVICE_?ROLE|SERVICE_?KEY|SECRET|PRIVATE|ADMIN_?KEY|WEBHOOK_?SIGNING)
24
+ - focus-metavariable: $V
25
+ metadata:
26
+ sec_ids: [SEC-SECRETS-001, SEC-SECRETS-003]
27
+ severity_hint: CRITICAL
28
+ confidence: MEDIUM
29
+ note: >-
30
+ Matches the variable name only, never the value. It also fires on a safe use, for example a name that holds a public
31
+ identifier such as NEXT_PUBLIC_SECRET_SANTA_URL. Read the line. Env files are not scanned by this rule, see the
32
+ gitleaks configuration and the SEC-SECRETS-001 recipe for them.
@@ -0,0 +1,78 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2026 Rare Phronesis Limited
3
+ # Copied from the Hullproof kit (kit/tools/hullproof/rules/postgrest-filter-injection.yaml).
4
+ #
5
+ rules:
6
+ - id: hullproof-postgrest-filter-string
7
+ languages: [typescript, javascript]
8
+ severity: ERROR
9
+ message: >-
10
+ A PostgREST filter string, column or embed list is built from a value that is not a constant.
11
+ Filter strings are parsed as a small language, so a comma, a dot or a parenthesis in the value
12
+ adds conditions (for example an extra or branch that matches every row). Use the builder
13
+ methods with one column and one value each (eq, ilike, in), and take column names and sort
14
+ keys from a fixed allowlist.
15
+ patterns:
16
+ - pattern-either:
17
+ - pattern: $Q.or($F, ...)
18
+ - pattern: $Q.and($F, ...)
19
+ - pattern: $Q.filter($F, $OP, $V)
20
+ - pattern: $Q.not($F, $OP, $V)
21
+ - pattern: $Q.order($F, ...)
22
+ - pattern: $Q.eq($F, $V)
23
+ - pattern: $Q.neq($F, $V)
24
+ - pattern: $Q.ilike($F, $V)
25
+ - pattern: $Q.like($F, $V)
26
+ - pattern: $Q.in($F, $V)
27
+ - pattern: $Q.contains($F, $V)
28
+ - pattern: $Q.textSearch($F, $V, ...)
29
+ - pattern: $Q.from(...).select($F, ...)
30
+ - metavariable-pattern:
31
+ metavariable: $F
32
+ patterns:
33
+ - pattern: $Z
34
+ - pattern-not: '"..."'
35
+ - pattern-not: "`...`"
36
+ - pattern-not: '[...]'
37
+ - pattern-not: '{...}'
38
+ - metavariable-regex:
39
+ metavariable: $F
40
+ regex: (?s)^(?![A-Z][A-Z0-9_]*$)(?!\s*[\[{])(?!(?:z|zod|v|yup|Joi|t|S|Type|type)\.)
41
+ metadata:
42
+ sec_ids: [SEC-API-018, SEC-API-017]
43
+ severity_hint: HIGH
44
+ confidence: MEDIUM
45
+ note: >-
46
+ Fires on any non constant column name, filter string or sort key passed to a query builder
47
+ method. Value arguments of eq, ilike and in are bound by the client and are not flagged
48
+ unless the first argument is also dynamic. A constant written in upper case is skipped, and so is an argument that starts with a schema library name (z, zod, yup, Joi), because zod and similar libraries have or and and methods too.
49
+ Mongoose and similar libraries also have or and and methods: array and object arguments are skipped.
50
+ - id: hullproof-postgrest-interpolated-filter
51
+ languages: [typescript, javascript]
52
+ severity: ERROR
53
+ message: >-
54
+ A value is interpolated into a PostgREST filter string. A comma, dot or parenthesis in the value
55
+ changes the filter. Build the condition with one builder call per column and bind each value.
56
+ patterns:
57
+ - pattern-either:
58
+ - pattern: $Q.or(`...${$V}...`, ...)
59
+ - pattern: $Q.and(`...${$V}...`, ...)
60
+ - pattern: $Q.or("..." + $V, ...)
61
+ - pattern: $Q.and("..." + $V, ...)
62
+ - pattern: $Q.or($V + "...", ...)
63
+ - pattern: $Q.not($C, $O, `...${$V}...`)
64
+ - pattern: $Q.filter($C, $O, `...${$V}...`)
65
+ - pattern: $Q.from(...).select(`...${$V}...`, ...)
66
+ - pattern: $Q.select(`...${$V}...`, ...)
67
+ - metavariable-regex:
68
+ metavariable: $Q
69
+ regex: (?s).*(from|supabase|client|admin|db|query|builder|rest|table)\b.*
70
+ metadata:
71
+ sec_ids: [SEC-API-018, SEC-API-017]
72
+ severity_hint: CRITICAL
73
+ confidence: HIGH
74
+ note: >-
75
+ Template or concatenated string in or, and, filter, not or a select list on a Supabase or
76
+ PostgREST builder. The receiver name must mention from, supabase, client, admin, db, query,
77
+ builder, rest or table, which keeps unrelated or methods out. Single column calls such as
78
+ ilike with an interpolated pattern are bound by the client and are not matched.
@@ -0,0 +1,42 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2026 Rare Phronesis Limited
3
+ # Copied from the Hullproof kit (kit/tools/hullproof/rules/raw-sql-concat.yaml).
4
+ #
5
+ rules:
6
+ - id: hullproof-raw-sql-concat
7
+ languages: [typescript, javascript]
8
+ severity: ERROR
9
+ message: >-
10
+ A query sink receives a string built from an interpolated or concatenated value. Use
11
+ parameters or a tagged template that binds values.
12
+ patterns:
13
+ - pattern-either:
14
+ - pattern: $X.$M(`...${$Y}...`, ...)
15
+ - pattern: $X.$M("..." + $Y, ...)
16
+ - pattern: $X.$M($Y + "...", ...)
17
+ - pattern: $X.$M("...".concat(...), ...)
18
+ - pattern: $X.$M([...].join(...), ...)
19
+ - pattern: $X.$M(`...${$Y}...`.concat(...), ...)
20
+ - metavariable-regex:
21
+ metavariable: $M
22
+ regex: ^(query|execute|raw|unsafe|\$queryRawUnsafe|\$executeRawUnsafe|queryRawUnsafe|executeRawUnsafe|whereRaw|andWhereRaw|orWhereRaw|havingRaw|orderByRaw|groupByRaw|joinRaw|selectRaw|fromRaw|rawQuery|literal)$
23
+ metadata:
24
+ sec_ids: [SEC-API-017]
25
+ severity_hint: CRITICAL
26
+ confidence: HIGH
27
+ note: >-
28
+ Unambiguous query method names. Tagged template calls bind values and are not matched. The
29
+ queryRaw and executeRaw plain string misuse is in sql-variants.yaml.
30
+ - id: hullproof-sql-string-built-in-variable
31
+ languages: [generic]
32
+ severity: WARNING
33
+ message: >-
34
+ A template literal that looks like SQL contains an interpolated value. If it reaches a query
35
+ call, bind the value as a parameter instead.
36
+ paths:
37
+ include: ["*.ts", "*.tsx", "*.js", "*.jsx", "*.mjs"]
38
+ pattern-regex: '(?is)(?<![\w$])`[^`]*\b(?:select\s[^`]*\sfrom|insert\s+into|update\s+\w+\s+set|delete\s+from)\b[^`]*\$\{'
39
+ metadata:
40
+ sec_ids: [SEC-API-017]
41
+ severity_hint: HIGH
42
+ confidence: LOW
@@ -0,0 +1,49 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2026 Rare Phronesis Limited
3
+ # Copied from the Hullproof kit (kit/tools/hullproof/rules/read-by-id-no-owner.yaml).
4
+ #
5
+ rules:
6
+ - id: hullproof-read-by-id-no-owner
7
+ languages: [typescript, javascript]
8
+ severity: ERROR
9
+ message: >-
10
+ A record is read by its id alone. The query holds no filter on the owner, user, organisation or tenant, so any caller who
11
+ knows or guesses another id gets that record. Add the owner filter to the same query (or rely on a row level security policy
12
+ that you have tested) and return 404 for a record that is not the caller's.
13
+ patterns:
14
+ - pattern-either:
15
+ - patterns:
16
+ - pattern-either:
17
+ - pattern: $C.from(...).select(...).eq("id", $X)
18
+ - pattern: $C.from(...).select().eq("id", $X)
19
+ - pattern-not-inside:
20
+ patterns:
21
+ - pattern: <... $Q.eq($K, ...) ...>
22
+ - metavariable-regex:
23
+ metavariable: $K
24
+ regex: (?i)^["'`].*(user|owner|org|tenant|team|account|created_?by|author|member|workspace|profile|customer).*["'`]$
25
+ - patterns:
26
+ - pattern-either:
27
+ - pattern: '$P.$T.$FIND({ where: { id: $X } })'
28
+ - pattern: '$P.$T.$FIND({ where: { id: $X }, ... })'
29
+ - pattern-not:
30
+ patterns:
31
+ - pattern: '$P.$T.$FIND({ where: { ..., $K: $V, ... } })'
32
+ - metavariable-regex:
33
+ metavariable: $K
34
+ regex: (?i)^["'`]?.*(user|owner|org|tenant|team|account|created_?by|author|member|workspace|profile|customer).*["'`]?$
35
+ - metavariable-regex:
36
+ metavariable: $FIND
37
+ regex: ^(findUnique|findUniqueOrThrow|findFirst|findFirstOrThrow)$
38
+ - pattern: $DB.select(...).from($T).where(eq($T2.id, $X))
39
+ - metavariable-regex:
40
+ metavariable: $X
41
+ regex: (?i)^(?!.*(user|session|auth|owner|me\b)).*$
42
+ metadata:
43
+ sec_ids: [SEC-AUTHZ-003]
44
+ severity_hint: HIGH
45
+ confidence: LOW
46
+ note: >-
47
+ Covers a Supabase client chain, a Prisma find call and a Drizzle select with one id condition. It cannot see a row level
48
+ security policy, a wrapper that adds the owner filter, or an owner check made on the next line. A value whose text names
49
+ user, session, auth or owner is treated as the caller's own id and skipped. Read the query and the policy before you rate it.
@@ -0,0 +1,26 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2026 Rare Phronesis Limited
3
+ # Copied from the Hullproof kit (kit/tools/hullproof/rules/server-action-no-auth.yaml).
4
+ #
5
+ rules:
6
+ - id: hullproof-server-action-no-auth
7
+ languages: [typescript, javascript]
8
+ severity: ERROR
9
+ message: >-
10
+ Exported function in a file with a top level "use server" directive has no call to a
11
+ session, auth or permission helper anywhere in its body. Every export is a public endpoint. Call the shared auth helper first.
12
+ patterns:
13
+ - pattern-inside: |
14
+ "use server";
15
+ ...
16
+ - pattern-either:
17
+ - pattern: export async function $F(...) { ... }
18
+ - pattern: export const $F = async (...) => { ... }
19
+ - pattern-not-regex: (?i)\b(?:[\w$]*(?:getUser|getSession|getClaims|requireUser|currentUser|session|assertAdmin|requireAdmin|authorize|authenticate|requireRole|checkPermission|validateRequest|requireAuth|withAuth|checkAuth|assertAuth|verifyAuth)[\w$]*|auth)\s*\(
20
+ metadata:
21
+ sec_ids: [SEC-AUTHZ-002, SEC-API-001]
22
+ severity_hint: HIGH
23
+ confidence: MEDIUM
24
+ note: >-
25
+ The whole function text is searched, so a session read inside an if, try or nested block counts. A helper name in a comment also
26
+ counts. The helper name regex is a convention guess. Adjust it to the project's auth helper.
@@ -0,0 +1,99 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2026 Rare Phronesis Limited
3
+ # Copied from the Hullproof kit (kit/tools/hullproof/rules/sql-variants.yaml).
4
+ #
5
+ rules:
6
+ - id: hullproof-sql-raw-non-literal
7
+ languages: [typescript, javascript]
8
+ severity: ERROR
9
+ message: >-
10
+ A raw SQL helper receives a value that is not a constant string. Raw helpers send the text
11
+ to the database as code. Bind values as parameters, and take identifiers from a fixed
12
+ allowlist.
13
+ patterns:
14
+ - pattern-either:
15
+ - pattern: sql.raw($Y)
16
+ - pattern: sql.unsafe($Y, ...)
17
+ - pattern: Prisma.raw($Y)
18
+ - patterns:
19
+ - pattern-either:
20
+ - pattern: $DB.raw($Y, ...)
21
+ - pattern: $DB.unsafe($Y, ...)
22
+ - pattern: $DB.literal($Y)
23
+ - metavariable-regex:
24
+ metavariable: $DB
25
+ regex: (?i)^(?:.*\.)?(?:\w*knex\w*|\w*sequelize\w*|\w*db|db\w*|trx|tx|pool|database|conn|connection|kx|sql|pg|client|query)$
26
+ - patterns:
27
+ - pattern: $DB.$M($Y, ...)
28
+ - metavariable-regex:
29
+ metavariable: $M
30
+ regex: ^(whereRaw|andWhereRaw|orWhereRaw|havingRaw|orderByRaw|groupByRaw|joinRaw|selectRaw|fromRaw|\$queryRawUnsafe|\$executeRawUnsafe|queryRawUnsafe|executeRawUnsafe)$
31
+ - metavariable-pattern:
32
+ metavariable: $Y
33
+ patterns:
34
+ - pattern: $Z
35
+ - pattern-not: '"..."'
36
+ - pattern-not: "`...`"
37
+ metadata:
38
+ sec_ids: [SEC-API-017]
39
+ severity_hint: HIGH
40
+ confidence: MEDIUM
41
+ note: Fires on any non constant argument, including a variable that holds a reviewed constant. Read the variable.
42
+ - id: hullproof-sql-tagged-template-misuse
43
+ languages: [typescript, javascript]
44
+ severity: ERROR
45
+ message: >-
46
+ A function that is built for tagged templates is called with an ordinary string. The
47
+ interpolated value is pasted into the SQL text and is not bound. Write sql`...${value}...`
48
+ without the parentheses so the library binds it, or call the unsafe variant on purpose with
49
+ an allowlisted value.
50
+ patterns:
51
+ - pattern-either:
52
+ - pattern: sql(`...${$Y}...`)
53
+ - pattern: sql("..." + $Y)
54
+ - pattern: $DB.sql(`...${$Y}...`)
55
+ - pattern: $DB.$queryRaw(`...${$Y}...`)
56
+ - pattern: $DB.$executeRaw(`...${$Y}...`)
57
+ - pattern: Prisma.sql(`...${$Y}...`)
58
+ - pattern: $T`...${sql.raw($Y)}...`
59
+ - pattern: $T`...${Prisma.raw($Y)}...`
60
+ - pattern: $T`...${$DB.unsafe($Y)}...`
61
+ metadata:
62
+ sec_ids: [SEC-API-017]
63
+ severity_hint: CRITICAL
64
+ confidence: MEDIUM
65
+ note: Covers postgres.js, drizzle and Prisma call shapes. The raw helper inside a tagged template is also flagged.
66
+ - id: hullproof-sql-built-string-reaches-query
67
+ languages: [typescript, javascript]
68
+ mode: taint
69
+ severity: ERROR
70
+ message: >-
71
+ A string that starts like a SQL statement and holds a concatenated or interpolated value
72
+ reaches a query call. Bind the value as a parameter.
73
+ pattern-sources:
74
+ - pattern-regex: '(?is)(?<![\w$])`\s*(?:select|insert\s+into|update|delete\s+from|with\s+\w+\s+as|merge\s+into|create|alter|drop|truncate|call|exec|execute)\b[^`]*\$\{[^`]*`'
75
+ - patterns:
76
+ - pattern-either:
77
+ - pattern: '$L + $Y'
78
+ - pattern: '[$L, ...].join(...)'
79
+ - pattern: '$L.concat(...)'
80
+ - metavariable-regex:
81
+ metavariable: $L
82
+ regex: '(?is)^["''`]\s*(?:select|insert|update|delete|with|merge|create|alter|drop|truncate|call|exec|execute)\b'
83
+ pattern-sinks:
84
+ - patterns:
85
+ - pattern-either:
86
+ - pattern: $X.$M($Q, ...)
87
+ - pattern: $M($Q, ...)
88
+ - focus-metavariable: $Q
89
+ - metavariable-regex:
90
+ metavariable: $M
91
+ regex: ^(query|execute|exec|run|get|all|prepare|any|one|many|none|oneOrNone|manyOrNone|result|raw|unsafe|each|map|task|tx|\$queryRawUnsafe|\$executeRawUnsafe|queryRawUnsafe|executeRawUnsafe|whereRaw|havingRaw|orderByRaw|selectRaw|joinRaw|runSql|runQuery|rawQuery|literal)$
92
+ metadata:
93
+ sec_ids: [SEC-API-017]
94
+ severity_hint: CRITICAL
95
+ confidence: MEDIUM
96
+ note: >-
97
+ Follows a built string through a variable inside one function or file. The source must
98
+ start with a SQL keyword, which keeps get and run style method names from firing on
99
+ unrelated strings.