0sec-cli 0.15.0 → 0.18.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 (209) hide show
  1. package/0sec.js +327 -0
  2. package/README.md +68 -48
  3. package/attacks/data-exfiltration/pii-leakage.yaml +27 -0
  4. package/attacks/encoding-bypass/base64-encoding.yaml +24 -0
  5. package/attacks/jailbreak/dan-roleplay.yaml +27 -0
  6. package/attacks/jailbreak/hypothetical-scenario.yaml +25 -0
  7. package/attacks/jailbreak/multilingual-bypass.yaml +22 -0
  8. package/attacks/output-manipulation/harmful-content.yaml +25 -0
  9. package/attacks/prompt-injection/context-manipulation.yaml +32 -0
  10. package/attacks/prompt-injection/direct-injection.yaml +28 -0
  11. package/attacks/prompt-injection/indirect-injection.yaml +33 -0
  12. package/attacks/system-prompt-extraction/direct-ask.yaml +30 -0
  13. package/attacks/system-prompt-extraction/markdown-exfil.yaml +26 -0
  14. package/attacks/tool-misuse/ssrf-via-tools.yaml +27 -0
  15. package/chunks/adapt-loop-SMH6V4PD.js +18 -0
  16. package/chunks/adgraph-JLGA6RYI.js +54 -0
  17. package/chunks/agent/skills/frameworks/entra-id.yaml +63 -0
  18. package/chunks/agent/skills/frameworks/graphql-introspection.yaml +116 -0
  19. package/chunks/agent/skills/frameworks/nextjs.yaml +82 -0
  20. package/chunks/agent/skills/frameworks/python-web.yaml +85 -0
  21. package/chunks/agent/skills/frameworks/supabase.yaml +83 -0
  22. package/chunks/agent/skills/frameworks/wordpress-deep.yaml +122 -0
  23. package/chunks/agent/skills/techniques/ad-attack-paths.yaml +57 -0
  24. package/chunks/agent/skills/techniques/advisory-disclosure.yaml +59 -0
  25. package/chunks/agent/skills/techniques/assumption-mining.yaml +53 -0
  26. package/chunks/agent/skills/techniques/blind-exploitation.yaml +143 -0
  27. package/chunks/agent/skills/techniques/crypto-misuse.yaml +88 -0
  28. package/chunks/agent/skills/techniques/cve-poc-adaptation.yaml +82 -0
  29. package/chunks/agent/skills/techniques/entra-attack-paths.yaml +55 -0
  30. package/chunks/agent/skills/techniques/http-conformance-diff.yaml +48 -0
  31. package/chunks/agent/skills/techniques/jwt-attacks.yaml +137 -0
  32. package/chunks/agent/skills/techniques/kernel-weaponization.yaml +53 -0
  33. package/chunks/agent/skills/techniques/llm-excessive-agency.yaml +55 -0
  34. package/chunks/agent/skills/techniques/llm-insecure-output-handling.yaml +56 -0
  35. package/chunks/agent/skills/techniques/llm-prompt-injection.yaml +54 -0
  36. package/chunks/agent/skills/techniques/llm-prompt-layer-write.yaml +68 -0
  37. package/chunks/agent/skills/techniques/llm-rag-poisoning.yaml +57 -0
  38. package/chunks/agent/skills/techniques/llm-safety-eval.yaml +51 -0
  39. package/chunks/agent/skills/techniques/npm-ecosystem.yaml +51 -0
  40. package/chunks/agent/skills/techniques/poc-verification.yaml +55 -0
  41. package/chunks/agent/skills/techniques/race-condition.yaml +122 -0
  42. package/chunks/agent/skills/techniques/scoped-fix.yaml +49 -0
  43. package/chunks/agent/skills/techniques/seedless-depth-review.yaml +59 -0
  44. package/chunks/agent/skills/techniques/spec-differential.yaml +48 -0
  45. package/chunks/agent/skills/techniques/variant-hunting.yaml +55 -0
  46. package/chunks/agent/skills/vulnerabilities/cardano-eutxo-validators.yaml +72 -0
  47. package/chunks/agent/skills/vulnerabilities/command-injection.yaml +99 -0
  48. package/chunks/agent/skills/vulnerabilities/deserialization-chains.yaml +106 -0
  49. package/chunks/agent/skills/vulnerabilities/native-memory-safety.yaml +93 -0
  50. package/chunks/agent/skills/vulnerabilities/path-traversal.yaml +78 -0
  51. package/chunks/agent/skills/vulnerabilities/prototype-pollution.yaml +132 -0
  52. package/chunks/agent/skills/vulnerabilities/request-smuggling.yaml +129 -0
  53. package/chunks/agent/skills/vulnerabilities/sqli-advanced.yaml +102 -0
  54. package/chunks/agent/skills/vulnerabilities/ssrf-bypass.yaml +84 -0
  55. package/chunks/agent/skills/vulnerabilities/ssti-exploitation.yaml +112 -0
  56. package/chunks/agent/skills/vulnerabilities/structural-sqli.yaml +91 -0
  57. package/chunks/appsec-catalog-CBWHGTEL.js +24 -0
  58. package/chunks/artifact-scraper-HQ7HLP6V.js +34 -0
  59. package/chunks/assumption-mining-5SNIVVDN.js +79 -0
  60. package/chunks/chunk-242TMK6G.js +110 -0
  61. package/chunks/chunk-2JCCA2JL.js +381 -0
  62. package/chunks/chunk-2RMLOJVB.js +47 -0
  63. package/chunks/chunk-3KPWWJCI.js +1422 -0
  64. package/chunks/chunk-3MOLBTLS.js +953 -0
  65. package/chunks/chunk-53G27VPS.js +136 -0
  66. package/chunks/chunk-57ZENEX2.js +169 -0
  67. package/chunks/chunk-5G3ZXFBW.js +182 -0
  68. package/chunks/chunk-5JI7L7KV.js +74240 -0
  69. package/chunks/chunk-6L26OBWI.js +1094 -0
  70. package/chunks/chunk-6LKRLK2R.js +542 -0
  71. package/chunks/chunk-72CISA2D.js +2597 -0
  72. package/chunks/chunk-7DQEV5QI.js +287 -0
  73. package/chunks/chunk-AD7WXDH6.js +3250 -0
  74. package/chunks/chunk-ADOLI6DT.js +102 -0
  75. package/chunks/chunk-AHTBZC3E.js +596 -0
  76. package/chunks/chunk-ASUR522M.js +518 -0
  77. package/chunks/chunk-ATATQACO.js +471 -0
  78. package/chunks/chunk-BATRQBOR.js +1732 -0
  79. package/chunks/chunk-BFR2CDV5.js +5735 -0
  80. package/chunks/chunk-BFRB6HB2.js +235 -0
  81. package/chunks/chunk-BKZFDZ23.js +361 -0
  82. package/chunks/chunk-BVAZO4WA.js +193 -0
  83. package/chunks/chunk-BZNEY2ZS.js +33225 -0
  84. package/chunks/chunk-C2WXWFBB.js +1070 -0
  85. package/chunks/chunk-CMKXP5RR.js +917 -0
  86. package/chunks/chunk-DW5UWPFY.js +275 -0
  87. package/chunks/chunk-F3WBKITT.js +687 -0
  88. package/chunks/chunk-GMT2AZKM.js +2439 -0
  89. package/chunks/chunk-H2FFLZNK.js +3 -0
  90. package/chunks/chunk-IOL7D5YV.js +515 -0
  91. package/chunks/chunk-IR537GON.js +49 -0
  92. package/chunks/chunk-JQHOLLTD.js +3729 -0
  93. package/chunks/chunk-KLTNTE2Z.js +121 -0
  94. package/chunks/chunk-LP3HHYQU.js +245 -0
  95. package/chunks/chunk-MYVT64FN.js +16 -0
  96. package/chunks/chunk-N7BOUYEV.js +852 -0
  97. package/chunks/chunk-OEFNRYI2.js +150 -0
  98. package/chunks/chunk-P6WKNFWX.js +1968 -0
  99. package/chunks/chunk-QI233I24.js +333 -0
  100. package/chunks/chunk-RJWOYEOG.js +271 -0
  101. package/chunks/chunk-RRMJC3ZE.js +105 -0
  102. package/chunks/chunk-RWONANDA.js +973 -0
  103. package/chunks/chunk-SAFFWQW4.js +5921 -0
  104. package/chunks/chunk-SF4KZ4O3.js +502 -0
  105. package/chunks/chunk-SVJEDVYK.js +808 -0
  106. package/chunks/chunk-TSK2AG6J.js +5108 -0
  107. package/chunks/chunk-U5FLXGHT.js +2010 -0
  108. package/chunks/chunk-UCYRA73C.js +662 -0
  109. package/chunks/chunk-UR4ELX2Y.js +4784 -0
  110. package/chunks/chunk-VQG4FT5D.js +685 -0
  111. package/chunks/chunk-WEBMRWEG.js +9337 -0
  112. package/chunks/chunk-WU6AFRAZ.js +1167 -0
  113. package/chunks/chunk-WVTBZEQO.js +1531 -0
  114. package/chunks/chunk-WXQ6BJ6A.js +596 -0
  115. package/chunks/chunk-XM5NLHYU.js +600 -0
  116. package/chunks/chunk-YLMN3N25.js +4054 -0
  117. package/chunks/chunk-YYRBQVFE.js +2572 -0
  118. package/chunks/chunk-Z5FA2XOX.js +2798 -0
  119. package/chunks/commands-TBC2F5CE.js +16999 -0
  120. package/chunks/corpus-v1.json +403 -0
  121. package/chunks/cost-ledger-NU63MYZM.js +13 -0
  122. package/chunks/data/appsec-archetypes.json +102 -0
  123. package/chunks/data/chromium-archetypes.json +204 -0
  124. package/chunks/data/freebsd-archetypes.json +171 -0
  125. package/chunks/data/kernel-archetypes.json +611 -0
  126. package/chunks/db-KFWOAUUB.js +16 -0
  127. package/chunks/disclose-EKTWSCGW.js +132 -0
  128. package/chunks/dist-4MAW5X6L.js +74 -0
  129. package/chunks/dist-4YICS2J2.js +2776 -0
  130. package/chunks/dist-FMGSR3DW.js +159 -0
  131. package/chunks/eval-runner-7ZF472KV.js +27 -0
  132. package/chunks/example-manifest.json +95 -0
  133. package/chunks/exploit-agent-45YQOHPJ.js +98 -0
  134. package/chunks/exploit-autoclimb-UZGEMGYT.js +81 -0
  135. package/chunks/exploit-climb-SG23KB2G.js +358 -0
  136. package/chunks/fix-IG7LYXH3.js +12 -0
  137. package/chunks/github-issues-MJ6OYOOU.js +157 -0
  138. package/chunks/harness-7ODOLOWU.js +22 -0
  139. package/chunks/http-conformance-DU66MZIU.js +11 -0
  140. package/chunks/http-sender-GWH2IYEA.js +10 -0
  141. package/chunks/hunt-scan-MMKUOETU.js +38 -0
  142. package/chunks/identity-6ZAIIWOR.js +191 -0
  143. package/chunks/kernel-primitive-7U6CWHXD.js +44 -0
  144. package/chunks/kernel-vm-runner-3JAU4O6J.js +62 -0
  145. package/chunks/memsafety-scan-J7SECV5W.js +16 -0
  146. package/chunks/native-loop-RJCJPOUS.js +54 -0
  147. package/chunks/npm-detectors-KB5Y5ZFX.js +69 -0
  148. package/chunks/npm-dynamic-discovery-AHROOZDB.js +13 -0
  149. package/chunks/orchestrate-OC2VZN2X.js +62 -0
  150. package/chunks/pipeline-BZK5BLJR.js +14 -0
  151. package/chunks/pre-recon-cve-66EB6G4M.js +351 -0
  152. package/chunks/prepare-MYY743TK.js +13 -0
  153. package/chunks/process-LQRCS5OY.js +13 -0
  154. package/chunks/replay-runner-45G3VU7X.js +39 -0
  155. package/chunks/run-D56MPDKY.js +32716 -0
  156. package/chunks/runtime-G7YWIO3B.js +43 -0
  157. package/chunks/runtime-J7PLXZNM.js +12 -0
  158. package/chunks/scan-stream-FHI2FYZE.js +96 -0
  159. package/chunks/scope-BI7BF4ZY.js +18 -0
  160. package/chunks/session-store-GZ4A4KYT.js +28 -0
  161. package/chunks/source-files-PWRQR6LY.js +12 -0
  162. package/chunks/specdrift-WCLH6TTQ.js +19 -0
  163. package/chunks/variant-candidates-YERCYMPD.js +13 -0
  164. package/chunks/web-recon-prepass-JJGPEOLE.js +1148 -0
  165. package/dashboard/assets/0sec-icon-66SreztZ.gif +0 -0
  166. package/dashboard/assets/bot-yILPnRgD.js +1 -0
  167. package/dashboard/assets/chevron-down-BeDJ-Vka.js +1 -0
  168. package/dashboard/assets/circle-alert-BEnMBMT-.js +1 -0
  169. package/dashboard/assets/client-DRYsuOGl.js +9 -0
  170. package/dashboard/assets/copy-Bi5vzoF9.js +1 -0
  171. package/dashboard/assets/desktop-CXTQonNm.js +32 -0
  172. package/dashboard/assets/desktop-NOekk4QS.css +1 -0
  173. package/dashboard/assets/dist-CmX-h7a4.js +1 -0
  174. package/dashboard/assets/dist-DAwYQmf_.js +45 -0
  175. package/dashboard/assets/findings-page-CACW9nw2.js +5 -0
  176. package/dashboard/assets/format-PoISqsES.js +1 -0
  177. package/dashboard/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  178. package/dashboard/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  179. package/dashboard/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  180. package/dashboard/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  181. package/dashboard/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  182. package/dashboard/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  183. package/dashboard/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  184. package/dashboard/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  185. package/dashboard/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  186. package/dashboard/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  187. package/dashboard/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  188. package/dashboard/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  189. package/dashboard/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  190. package/dashboard/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  191. package/dashboard/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  192. package/dashboard/assets/jsx-runtime-C7oxC63R.js +1 -0
  193. package/dashboard/assets/live-page-DrgQqQK5.js +1 -0
  194. package/dashboard/assets/meta-tile-CcskIV_o.js +1 -0
  195. package/dashboard/assets/operations-BKE8T-vY.css +2 -0
  196. package/dashboard/assets/operations-D8nUP5_m.js +4 -0
  197. package/dashboard/assets/operations-app-CQQhG54n.js +2 -0
  198. package/dashboard/assets/overview-page-DTGS8dPo.js +1 -0
  199. package/dashboard/assets/page-header-MFVvEtZW.js +1 -0
  200. package/dashboard/assets/play-Dwo-9xt6.js +1 -0
  201. package/dashboard/assets/scans-page-ChVtq8Us.js +1 -0
  202. package/dashboard/assets/search-CiooTLck.js +1 -0
  203. package/dashboard/assets/siren-CCBwTSuk.js +1 -0
  204. package/dashboard/assets/table-BYS-nIVA.js +1 -0
  205. package/dashboard/assets/tabs-DivxbhQy.js +1 -0
  206. package/dashboard/desktop.html +21 -0
  207. package/dashboard/index.html +15 -0
  208. package/package.json +30 -18
  209. package/bin/0sec.cjs +0 -361
@@ -0,0 +1,333 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __0secCreateRequire } from "node:module";
3
+ const require = __0secCreateRequire(import.meta.url);
4
+
5
+ // packages/core/dist/stages/appsec-catalog.js
6
+ import { createHash } from "node:crypto";
7
+ import { existsSync, lstatSync, readFileSync } from "node:fs";
8
+ import { homedir } from "node:os";
9
+ import { join, resolve } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+
12
+ // packages/core/dist/stages/data/appsec-archetypes.json
13
+ var appsec_archetypes_default = {
14
+ provenance: "0sec-authored appsec bug-class registry, 2026-07-21. Mirrors the SCHEMA of kernel-archetypes.json but is a SEPARATE, cross-language application-security pack (domain: appsec), NOT ported from 0verse (0verse's registry is Linux-kernel-only). Grounded in OWASP Top-10 / CWE and in the concrete misses these lenses were built to close: the Swiss engagement's gever OS-command-injection CRITICAL and the sibling-route authz / template-XSS / SSO-trust / resource-exhaustion classes the four generic finder lenses (memory-safety, input-validation, auth-logic, secrets-crypto) under-weighted. Unlike the kernel pack there is NO build/boot/sanitizer lane for these \u2014 every entry is route 'appsec-source-static': a grep/read hit is a hypothesis for the skeptic + multi-lens verify quorum, never an auto-confirmed finding. The load-bearing field for the finder surface is 'challenge_hint' (the cross-language, sink-shape-citing hunt angle), which the loader maps directly onto FinderLens.challengeHint; 'detection_signature' is the human/skeptic-facing grep evidence. 'engine_lens' is null for every entry (no pre-existing engine lens implements these \u2014 this registry IS their implementation).",
15
+ archetypes: [
16
+ {
17
+ id: "os-command-injection",
18
+ name: "OS command injection via attacker-influenced process/shell exec",
19
+ cwe: "CWE-78",
20
+ domain: "appsec",
21
+ subsystem: "process / shell execution (any runtime)",
22
+ pattern: "Attacker-controlled data (a request param, header, filename, upload name, DB value, config field) is concatenated or interpolated into a command line, a shell string, or a process argument that is then handed to an OS command/shell interpreter. The sink executes with the app's privileges; even when the base command is fixed, an unescaped argument, a shell metacharacter (; | & $() ` > <), or argument-injection (a value beginning with -) subverts it. The tell is DATA (not developer-authored code) reaching an exec sink without an allow-list, tokenized argv, or a proven-safe escaper.",
23
+ detection_signature: 'Language-agnostic sink hunt. Node: child_process exec/execSync/spawn/spawnSync/execFile with a shell:true option or a string-built command, template literals inside exec(`...${x}...`). .NET: System.Diagnostics.Process.Start / ProcessStartInfo where FileName is cmd.exe/powershell/bash or Arguments is string-concatenated, UseShellExecute=true. Java: Runtime.getRuntime().exec(String), ProcessBuilder(command) built from concatenation, especially exec("sh", "-c", cmd). Python: subprocess.run/Popen/call with shell=True, os.system, os.popen, commands.getoutput. PHP: system/exec/shell_exec/passthru/proc_open/backticks. Ruby: system/`backticks`/%x/Open3 with interpolation. Perl: system/open with a pipe. Trace the taint from the entry point to the argv/shell string; a fixed argv array with no shell and no attacker-controlled flag is safe.',
24
+ challenge_hint: "Hunt OS COMMAND INJECTION only, ACROSS ALL LANGUAGES: attacker-influenced data reaching a process/shell exec sink. Look for \u2014 Node child_process exec/execSync/spawn(..., {shell:true})/execFile and backtick-built commands; .NET Process.Start / ProcessStartInfo.FileName=cmd.exe|powershell with a concatenated Arguments (or UseShellExecute=true); Java Runtime.exec(String) / ProcessBuilder built by string concatenation (esp. sh -c); Python subprocess(..., shell=True) / os.system / os.popen; PHP system/exec/shell_exec/passthru/proc_open/backticks; Ruby system/%x/backticks; any string-built shell command. Cite the exact sink (file:line) and the concrete taint path from the entry point (route param, header, filename, upload, config, DB value) to the command string or argv. A fixed argv array with NO shell and NO attacker-controlled leading-dash flag is safe \u2014 do not flag it. This is the class that missed the gever CRITICAL.",
25
+ grounding: [
26
+ "CWE-78: Improper Neutralization of Special Elements used in an OS Command",
27
+ "OWASP Top 10 A03:2021 Injection",
28
+ "0sec Swiss engagement: gever OS-command-injection CRITICAL (missed by the generic input-validation lens)"
29
+ ],
30
+ confirmable: "Source-static hypothesis. A grep/read hit needs the skeptic + multi-lens verify quorum to confirm the argument is genuinely attacker-reachable and not escaped/tokenized upstream; no build/exec lane proves it in this repo.",
31
+ uid: "appsec/os-command-injection",
32
+ engine_lens: null,
33
+ route: "appsec-source-static"
34
+ },
35
+ {
36
+ id: "method-authz-differential",
37
+ name: "Method-level authorization differential (sibling route/handler missing its siblings' authz + IDOR-by-id)",
38
+ cwe: "CWE-862",
39
+ domain: "appsec",
40
+ subsystem: "web routing / controllers / service layer (any framework)",
41
+ pattern: "Two or more handlers act on the SAME resource, but one is missing the authorization guard its siblings carry: a controller action without the [Authorize]/policy/role attribute its neighbors have; a service-layer method that mutates or reads an object WITHOUT the ownership/tenant/scope check its Update/Delete siblings enforce; a route that trusts a caller-supplied id and fetches by id alone (IDOR) instead of scoping to the authenticated principal. The vulnerability is the DIFFERENCE between siblings, not the absence of auth everywhere \u2014 the guard exists and is proven necessary on one path and simply omitted on another.",
42
+ detection_signature: "Compare sibling handlers on one resource. .NET: an action lacking [Authorize]/[Authorize(Policy=...)]/[Authorize(Roles=...)] present on peer actions in the same controller, or a controller relying on a global filter that a specific action opts out of with [AllowAnonymous]. Spring: a @GetMapping/@PostMapping without the @PreAuthorize/@Secured its siblings have, or a SecurityConfig antMatcher that omits one path. Express/Koa/Fastify: a route registered without the requireAuth/authorize middleware its siblings chain. Rails: a controller action skipped in before_action :authorize, or skip_before_action. Service layer (any language): an Update/Delete method that checks resource.ownerId == currentUser before mutating, next to a sibling (e.g. GetById, Export, a bulk op) that fetches/mutates by id with NO such check. Also flag repository calls keyed only by a request-supplied id with no tenant/owner predicate.",
43
+ challenge_hint: "Hunt METHOD-LEVEL AUTHORIZATION DIFFERENTIALS and IDOR only. Find sibling routes/handlers/methods on the SAME resource where ONE lacks the guard its siblings carry: a controller action missing the [Authorize]/@PreAuthorize/policy/role attribute or requireAuth middleware its peers have; a service method that mutates/reads by id WITHOUT the ownership/tenant/scope check its Update/Delete siblings enforce; a repository/query keyed only on a caller-supplied id with no owner/tenant predicate (IDOR-by-id). The signal is the DIFFERENCE between siblings \u2014 prove the guard is present and necessary on one path and genuinely absent or mis-scoped on the reachable one, not merely that an auth keyword appears somewhere in the file. Cite both the guarded sibling and the unguarded reachable handler (file:line each).",
44
+ grounding: [
45
+ "CWE-862: Missing Authorization",
46
+ "CWE-639: Authorization Bypass Through User-Controlled Key (IDOR)",
47
+ "OWASP Top 10 A01:2021 Broken Access Control"
48
+ ],
49
+ confirmable: "Source-static hypothesis. The skeptic must confirm the sibling truly shares the resource, the guard is genuinely absent (not enforced by a global filter/middleware or an earlier check), and the unguarded path is reachable by a lower-privileged principal.",
50
+ uid: "appsec/method-authz-differential",
51
+ engine_lens: null,
52
+ route: "appsec-source-static"
53
+ },
54
+ {
55
+ id: "template-xss-ssti",
56
+ name: "Template / HTML-render XSS and server-side template injection (SSTI)",
57
+ cwe: "CWE-79",
58
+ domain: "appsec",
59
+ subsystem: "view / template rendering (client and server frameworks)",
60
+ pattern: "User- or controller-supplied input reaches an HTML or template render context WITHOUT context-correct escaping \u2014 either interpolated into markup that the browser parses as HTML/JS (XSS), or concatenated into the TEMPLATE SOURCE itself so the engine evaluates attacker text as template code (SSTI, which can reach RCE). This is DATA crossing into a code/markup context, distinct from a template engine legitimately compiling a developer-authored template (that is by-design and NOT a finding).",
61
+ detection_signature: "Client: Angular bypassSecurityTrustHtml/bypassSecurityTrustScript/bypassSecurityTrustResourceUrl and [innerHTML]=userValue; React dangerouslySetInnerHTML={{__html: userValue}}; Vue v-html=userValue; raw DOM innerHTML/outerHTML/insertAdjacentHTML/document.write with tainted data. Server-side templates: Handlebars triple-mustache {{{x}}} / Handlebars.SafeString(userInput); Velocity/Freemarker/Thymeleaf where user input is spliced into the template STRING (not the model) \u2014 e.g. new Template(userStr) or th:utext with tainted data; JSP <%= %> without <c:out>; Jinja2/Django render_template_string(userStr) / Template(userStr); ERB/Liquid built from user input; EJS/Pug/Mustache raw-output helpers fed tainted data; string concatenation building the template passed to a compile()/render() call. Trace user input to the render/interpolation sink and check for a context-correct escaper on the path.",
62
+ challenge_hint: "Hunt TEMPLATE/HTML XSS and SERVER-SIDE TEMPLATE INJECTION (SSTI) only. Find user/controller input reaching an HTML or template render without context-correct escaping: Angular bypassSecurityTrustHtml/[innerHTML]; React dangerouslySetInnerHTML; Vue v-html; raw innerHTML/document.write; Handlebars {{{triple}}}/SafeString; Velocity/Freemarker/Thymeleaf(th:utext)/JSP(<%= %>)/Jinja2 render_template_string / Template(userStr) where DATA is spliced into the template SOURCE (SSTI \u2192 possible RCE); EJS/Pug/Mustache raw helpers fed tainted data. Trace the taint from entry point to the render sink and cite the missing/incorrect escaper (file:line). IMPORTANT: a template engine compiling a DEVELOPER-authored template is by-design \u2014 the finding is untrusted DATA reaching the markup/template-code context. Do NOT let the intended-use gate talk you out of hunting this class; hunt it and report the precondition explicitly.",
63
+ grounding: [
64
+ "CWE-79: Improper Neutralization of Input During Web Page Generation (XSS)",
65
+ "CWE-1336: Improper Neutralization of Special Elements Used in a Template Engine (SSTI)",
66
+ "OWASP Top 10 A03:2021 Injection"
67
+ ],
68
+ confirmable: "Source-static hypothesis. The skeptic must confirm the sink renders into a browser/template-code context, the escaper is absent or context-wrong, and the input is genuinely untrusted DATA rather than a developer-authored template (the intended-use gate distinguishes these).",
69
+ uid: "appsec/template-xss-ssti",
70
+ engine_lens: null,
71
+ route: "appsec-source-static"
72
+ },
73
+ {
74
+ id: "sso-trust",
75
+ name: "SSO / identity-federation trust flaw (SAML / OIDC / JWT assertion validation)",
76
+ cwe: "CWE-347",
77
+ domain: "appsec",
78
+ subsystem: "authentication / identity federation (SAML, OIDC, OAuth2, JWT)",
79
+ pattern: "A relying party accepts a federated identity assertion without fully verifying its authenticity, integrity, or provenance: an unverified or forged SAML assertion/response signature, XML signature wrapping (XSW) where the signed element and the consumed element differ, the IdP\u2194RP allowlist (issuer / audience / entityID / redirect_uri) not enforced SERVER-SIDE, RelayState/redirect_uri reflected or validated with a loose prefix/substring match, or a JWT accepted with alg:none, an unverified signature, or algorithm-confusion (RS256 verified as HS256 with the public key as the HMAC secret). The trust boundary is the assertion \u2014 any gap lets an attacker mint or replay an identity.",
80
+ detection_signature: "SAML: assertion consumed before/without signature validation, validateSignature disabled or wrapped in try/catch that continues, response parsed with an XML lib vulnerable to XSW, Assertion vs Response signature scope not pinned, audience/issuer/NotOnOrAfter/InResponseTo unchecked. OIDC/OAuth2: id_token accepted without verifying iss/aud/exp/nonce, redirect_uri matched by startsWith/contains instead of exact registered value, state/PKCE absent, IdP discovery/JWKS fetched over an attacker-influenceable URL. JWT: jwt.decode without verify, algorithms allowing 'none', a verify call that trusts the token's own alg header, or an HMAC verify keyed with a public key. Look for the MISSING or bypassable check on the assertion path, not just the presence of a SAML/OIDC library.",
81
+ challenge_hint: "Hunt SSO / IDENTITY-FEDERATION TRUST flaws only (SAML, OIDC, OAuth2, JWT). Find where a relying party trusts a federated assertion without full server-side verification: unverified/forged SAML signature or signature-validation that continues on failure; XML signature wrapping (signed element \u2260 consumed element); issuer/audience/entityID or redirect_uri allowlist NOT enforced server-side (or matched by startsWith/substring); RelayState/redirect_uri reflection; missing state/nonce/PKCE; JWT with alg:none, unverified signature, or RS256\u2194HS256 algorithm-confusion (public key used as HMAC secret). Prove the specific check on the assertion path is absent or bypassable and cite it (file:line) \u2014 the mere presence of a SAML/OIDC/JWT library is not the finding.",
82
+ grounding: [
83
+ "CWE-347: Improper Verification of Cryptographic Signature",
84
+ "CWE-287: Improper Authentication",
85
+ "CWE-290: Authentication Bypass by Spoofing",
86
+ "OWASP Top 10 A07:2021 Identification and Authentication Failures"
87
+ ],
88
+ confirmable: "Source-static hypothesis. The skeptic must confirm the assertion is consumed on a path where the specific verification (signature, issuer/audience, redirect_uri exact-match, JWT alg pinning) is genuinely missing or bypassable and reachable by an unauthenticated attacker.",
89
+ uid: "appsec/sso-trust",
90
+ engine_lens: null,
91
+ route: "appsec-source-static"
92
+ },
93
+ {
94
+ id: "resource-exhaustion-dos",
95
+ name: "Resource-exhaustion / algorithmic DoS from attacker-controlled loop, sleep, or allocation",
96
+ cwe: "CWE-400",
97
+ domain: "appsec",
98
+ subsystem: "request handling / parsing / scheduling (any runtime)",
99
+ pattern: "An attacker-controlled value drives an unbounded loop, sleep/delay, allocation, decompression, fan-out, or backtracking, with no cap, timeout, or ratio check \u2014 letting a single cheap request consume disproportionate CPU, memory, threads, or time. Includes a header/param fed into a sleep (Retry-After \u2192 Thread.sleep), a decompression ratio / zip-bomb (inflate without a size/ratio limit), catastrophic-backtracking regex (ReDoS) on user input, an unbounded fan-out (N sub-requests from a user-supplied N), and mass-assignment / unbounded collection sizing driven by request data.",
100
+ detection_signature: "A loop bound, sleep duration, allocation size, or recursion depth read from request data without a clamp: Thread.sleep/setTimeout/time.sleep/Task.Delay whose argument is a parsed header/param (e.g. Retry-After); GZIP/Zip/Inflater/gunzip/zlib reading into an unbounded buffer with no maxSize/ratio guard; new byte[n]/Array(n)/malloc(n)/list * n where n is attacker-supplied; regexes with nested quantifiers ((a+)+, (.*)*, (\\d+)+) matched against user input; a for/while whose iteration count is a request field; JSON/XML/YAML parsed with no depth/size limit; a paginated/batch endpoint taking a user-supplied count/limit with no server cap; ORM/mass-assignment binding an entire request body to a model. Trace the attacker-controlled magnitude to the unbounded consumer.",
101
+ challenge_hint: "Hunt RESOURCE-EXHAUSTION / ALGORITHMIC DoS only: an attacker-controlled value driving an unbounded loop, sleep, allocation, decompression, fan-out, or regex backtracking with no cap/timeout/ratio. Look for \u2014 a header/param fed into Thread.sleep/setTimeout/time.sleep/Task.Delay (e.g. Retry-After \u2192 sleep); decompression without a size/ratio limit (zip-bomb, Inflater/gunzip/zlib into an unbounded buffer); ReDoS (nested quantifiers on user input); new byte[n]/Array(n)/malloc(n) with attacker-supplied n; a loop whose count is a request field; JSON/XML/YAML parsed with no depth/size limit; an endpoint honoring a user-supplied count/limit with no server cap; mass-assignment binding a whole request body. Cite the attacker-controlled magnitude and the unbounded consumer (file:line) and show there is no clamp on the path.",
102
+ grounding: [
103
+ "CWE-400: Uncontrolled Resource Consumption",
104
+ "CWE-1333: Inefficient Regular Expression Complexity (ReDoS)",
105
+ "CWE-409: Improper Handling of Highly Compressed Data (Data Amplification / zip-bomb)",
106
+ "OWASP Top 10 A04:2021 Insecure Design / A05:2021 Security Misconfiguration"
107
+ ],
108
+ confirmable: "Source-static hypothesis. The skeptic must confirm the magnitude is genuinely attacker-controlled, the consumer is genuinely unbounded on the reachable path, and the impact is disproportionate (a single request \u2192 outsized CPU/memory/time), not a trivially bounded operation.",
109
+ uid: "appsec/resource-exhaustion-dos",
110
+ engine_lens: null,
111
+ route: "appsec-source-static"
112
+ }
113
+ ]
114
+ };
115
+
116
+ // packages/core/dist/stages/appsec-catalog.js
117
+ function mapRawAppsecArchetypes(raw) {
118
+ return raw.map((a) => ({
119
+ uid: a.uid,
120
+ versionDigest: appsecArchetypeDigest(a),
121
+ id: a.id,
122
+ name: a.name,
123
+ cwe: a.cwe,
124
+ domain: a.domain,
125
+ subsystem: a.subsystem,
126
+ pattern: a.pattern,
127
+ detectionSignature: a.detection_signature,
128
+ challengeHint: a.challenge_hint,
129
+ grounding: [...a.grounding],
130
+ confirmableNote: a.confirmable,
131
+ engineLens: a.engine_lens,
132
+ route: a.route,
133
+ ...a.source ? { source: a.source } : {},
134
+ ...a.validated_at ? { validatedAt: a.validated_at } : {},
135
+ ...a.miss_refs ? { missRefs: [...a.miss_refs] } : {}
136
+ }));
137
+ }
138
+ var _cache = null;
139
+ function appsecArchetypesPath() {
140
+ return fileURLToPath(new URL("./data/appsec-archetypes.json", import.meta.url));
141
+ }
142
+ function appsecUserArchetypesPath(homeDir = homedir()) {
143
+ return join(homeDir, ".0sec", "lenses", "appsec-archetypes.json");
144
+ }
145
+ function activeAppsecLensRegistryPath() {
146
+ const configured = process.env["0SEC_APPSEC_LENS_REGISTRY"]?.trim();
147
+ return configured ? resolve(configured) : appsecUserArchetypesPath();
148
+ }
149
+ function canonicalJson(value) {
150
+ if (value === null || typeof value !== "object")
151
+ return JSON.stringify(value);
152
+ if (Array.isArray(value))
153
+ return `[${value.map(canonicalJson).join(",")}]`;
154
+ const record = value;
155
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
156
+ }
157
+ function digest(value) {
158
+ return `sha256:${createHash("sha256").update(canonicalJson(value)).digest("hex")}`;
159
+ }
160
+ function appsecArchetypeDigest(archetype) {
161
+ return digest(archetype);
162
+ }
163
+ function appsecLensLedgerEntryDigest(entry) {
164
+ return digest(entry);
165
+ }
166
+ function readAppsecArchetypes() {
167
+ try {
168
+ return JSON.parse(readFileSync(appsecArchetypesPath(), "utf8"));
169
+ } catch (error) {
170
+ if (error.code !== "ENOENT")
171
+ throw error;
172
+ return appsec_archetypes_default;
173
+ }
174
+ }
175
+ function loadAppsecArchetypes() {
176
+ if (_cache)
177
+ return _cache;
178
+ const raw = readAppsecArchetypes();
179
+ _cache = mapRawAppsecArchetypes(raw.archetypes);
180
+ return _cache;
181
+ }
182
+ function appsecArchetypeToFinderLens(a) {
183
+ return { id: a.id, challengeHint: a.challengeHint, ...a.versionDigest ? { versionDigest: a.versionDigest } : {} };
184
+ }
185
+ var RUNTIME_LENSES_FLAG = "0SEC_RUNTIME_LENSES_ENABLED";
186
+ var RUNTIME_LENSES_ENV = "0SEC_RUNTIME_LENSES";
187
+ var SHA256_DIGEST = /^sha256:[a-f0-9]{64}$/;
188
+ var LENS_ID = /^[a-z0-9][a-z0-9-]{1,63}$/;
189
+ function runtimeLensesEnabled() {
190
+ return !["", "0", "false", "no"].includes((process.env[RUNTIME_LENSES_FLAG] ?? "").toLowerCase());
191
+ }
192
+ function isStringArray(value) {
193
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string");
194
+ }
195
+ function isRawAppsecArchetype(value) {
196
+ if (typeof value !== "object" || value === null)
197
+ return false;
198
+ const archetype = value;
199
+ const isNonEmptyString = (field) => typeof field === "string" && field.trim().length > 0;
200
+ return isNonEmptyString(archetype.id) && isNonEmptyString(archetype.challenge_hint) && typeof archetype.uid === "string" && typeof archetype.domain === "string" && typeof archetype.name === "string" && typeof archetype.cwe === "string" && typeof archetype.subsystem === "string" && typeof archetype.pattern === "string" && typeof archetype.detection_signature === "string" && typeof archetype.confirmable === "string" && typeof archetype.route === "string" && (archetype.engine_lens === null || typeof archetype.engine_lens === "string") && isStringArray(archetype.grounding);
201
+ }
202
+ function isDurablyValidatedArchetype(value) {
203
+ if (!isRawAppsecArchetype(value))
204
+ return false;
205
+ return LENS_ID.test(value.id) && value.uid === `appsec/${value.id}` && value.domain === "appsec" && value.engine_lens === null && value.route === "appsec-source-static" && value.source === "synthesized" && typeof value.validated_at === "string" && Number.isFinite(Date.parse(value.validated_at)) && value.grounding.length > 0 && value.grounding.every((entry) => entry.trim().length > 0) && Array.isArray(value.miss_refs) && value.miss_refs.length > 0 && value.miss_refs.every((entry) => typeof entry === "string" && entry.trim().length > 0);
206
+ }
207
+ function isAppsecLensLedgerEntry(value) {
208
+ if (typeof value !== "object" || value === null)
209
+ return false;
210
+ const entry = value;
211
+ return entry.schemaVersion === 1 && Number.isInteger(entry.sequence) && entry.sequence > 0 && typeof entry.occurredAt === "string" && Number.isFinite(Date.parse(entry.occurredAt)) && (entry.type === "promoted" || entry.type === "retired") && typeof entry.lensId === "string" && LENS_ID.test(entry.lensId) && typeof entry.archetypeDigest === "string" && SHA256_DIGEST.test(entry.archetypeDigest) && (entry.previousDigest === null || typeof entry.previousDigest === "string" && SHA256_DIGEST.test(entry.previousDigest)) && typeof entry.entryDigest === "string" && SHA256_DIGEST.test(entry.entryDigest);
212
+ }
213
+ function loadDurableAppsecArchetypes() {
214
+ const registryPath = activeAppsecLensRegistryPath();
215
+ if (!existsSync(registryPath))
216
+ return [];
217
+ try {
218
+ const metadata = lstatSync(registryPath);
219
+ if (!metadata.isFile())
220
+ throw new Error("registry must be a regular file");
221
+ if ((metadata.mode & 18) !== 0) {
222
+ throw new Error("registry must not be group- or world-writable");
223
+ }
224
+ const parsed = JSON.parse(readFileSync(registryPath, "utf8"));
225
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
226
+ throw new Error("registry must be a JSON object");
227
+ }
228
+ const registry = parsed;
229
+ if (registry.schemaVersion !== 1 || typeof registry.provenance !== "string" || registry.provenance.trim().length === 0 || !Array.isArray(registry.archetypes) || !Array.isArray(registry.ledger)) {
230
+ throw new Error("registry must carry schemaVersion 1, provenance, archetypes, and ledger");
231
+ }
232
+ const active = /* @__PURE__ */ new Map();
233
+ let previousDigest = null;
234
+ for (const [index, rawEntry] of registry.ledger.entries()) {
235
+ if (!isAppsecLensLedgerEntry(rawEntry))
236
+ throw new Error(`ledger entry ${index} is malformed`);
237
+ const entry = rawEntry;
238
+ if (entry.sequence !== index + 1 || entry.previousDigest !== previousDigest) {
239
+ throw new Error(`ledger entry ${index} breaks sequence or hash linkage`);
240
+ }
241
+ const expectedDigest = appsecLensLedgerEntryDigest({
242
+ schemaVersion: entry.schemaVersion,
243
+ sequence: entry.sequence,
244
+ occurredAt: entry.occurredAt,
245
+ type: entry.type,
246
+ lensId: entry.lensId,
247
+ archetypeDigest: entry.archetypeDigest,
248
+ previousDigest: entry.previousDigest
249
+ });
250
+ if (entry.entryDigest !== expectedDigest)
251
+ throw new Error(`ledger entry ${index} digest does not match`);
252
+ if (entry.type === "promoted") {
253
+ if (active.has(entry.lensId))
254
+ throw new Error(`ledger promotes active lens '${entry.lensId}' twice`);
255
+ active.set(entry.lensId, entry.archetypeDigest);
256
+ } else {
257
+ if (active.get(entry.lensId) !== entry.archetypeDigest) {
258
+ throw new Error(`ledger retires unknown or mismatched lens '${entry.lensId}'`);
259
+ }
260
+ active.delete(entry.lensId);
261
+ }
262
+ previousDigest = entry.entryDigest;
263
+ }
264
+ const archetypes = [];
265
+ for (const rawArchetype of registry.archetypes) {
266
+ if (!isDurablyValidatedArchetype(rawArchetype)) {
267
+ throw new Error("registry contains an unvalidated synthesized lens");
268
+ }
269
+ if (active.get(rawArchetype.id) !== appsecArchetypeDigest(rawArchetype)) {
270
+ throw new Error(`registry lens '${rawArchetype.id}' is not bound by its ledger`);
271
+ }
272
+ archetypes.push(rawArchetype);
273
+ }
274
+ if (active.size !== archetypes.length) {
275
+ throw new Error("registry ledger and active archetype set disagree");
276
+ }
277
+ return archetypes;
278
+ } catch (error) {
279
+ console.warn(`[appsec-lens-registry] ignoring ${registryPath}: ${error instanceof Error ? error.message : String(error)}`);
280
+ return [];
281
+ }
282
+ }
283
+ function loadRuntimeAppsecLenses() {
284
+ if (!runtimeLensesEnabled())
285
+ return [];
286
+ const blob = process.env[RUNTIME_LENSES_ENV];
287
+ if (!blob || blob.trim().length === 0)
288
+ return [];
289
+ let parsed;
290
+ try {
291
+ parsed = JSON.parse(blob);
292
+ } catch {
293
+ console.warn(`[appsec-runtime-lenses] ${RUNTIME_LENSES_ENV} is not valid JSON \u2014 falling back to durable lenses`);
294
+ return [];
295
+ }
296
+ if (!Array.isArray(parsed)) {
297
+ console.warn(`[appsec-runtime-lenses] ${RUNTIME_LENSES_ENV} must be a JSON array \u2014 falling back to durable lenses`);
298
+ return [];
299
+ }
300
+ const valid = [];
301
+ for (const entry of parsed) {
302
+ if (isRawAppsecArchetype(entry))
303
+ valid.push(entry);
304
+ else
305
+ console.warn("[appsec-runtime-lenses] skipping malformed runtime lens entry");
306
+ }
307
+ return mapRawAppsecArchetypes(valid).map(appsecArchetypeToFinderLens);
308
+ }
309
+ function loadAppsecFinderLenses() {
310
+ const baked = loadAppsecArchetypes().map(appsecArchetypeToFinderLens);
311
+ const durable = mapRawAppsecArchetypes(loadDurableAppsecArchetypes()).map(appsecArchetypeToFinderLens);
312
+ const runtime = loadRuntimeAppsecLenses();
313
+ const seen = new Set(baked.map((lens) => lens.id));
314
+ const merged = [...baked];
315
+ for (const lens of [...durable, ...runtime]) {
316
+ if (seen.has(lens.id))
317
+ continue;
318
+ seen.add(lens.id);
319
+ merged.push(lens);
320
+ }
321
+ return merged;
322
+ }
323
+
324
+ export {
325
+ appsecArchetypesPath,
326
+ appsecUserArchetypesPath,
327
+ activeAppsecLensRegistryPath,
328
+ appsecArchetypeDigest,
329
+ appsecLensLedgerEntryDigest,
330
+ loadAppsecArchetypes,
331
+ appsecArchetypeToFinderLens,
332
+ loadAppsecFinderLenses
333
+ };
@@ -0,0 +1,271 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __0secCreateRequire } from "node:module";
3
+ const require = __0secCreateRequire(import.meta.url);
4
+
5
+ // packages/core/dist/kernel/exploit/recipes.js
6
+ var RECIPE_LIBRARY = [
7
+ {
8
+ id: "dirtyCred",
9
+ title: "DirtyCred \u2014 privileged-object swap via heap reuse (data-only)",
10
+ // A UAF (or a refcount bug collapsing to one) on a file/cred object is the
11
+ // canonical DirtyCred entry; a page-UAF can also be funnelled here.
12
+ appliesTo: ["uaf", "refcount"],
13
+ ceiling: "root",
14
+ preconditions: [
15
+ "UAF (or refcount-to-UAF) on a `struct file` or `struct cred` \u2014 or any freeable object that can be made to alias one via same-cache reuse",
16
+ "ability to free the victim object while a dangling reference is still usable (the swap window)",
17
+ "an unprivileged path that allocates the privileged replacement (e.g. open(/etc/passwd) for a write-capable file, or a setuid exec for a privileged cred)"
18
+ ],
19
+ sprayObjects: ["file", "cred"],
20
+ steps: [
21
+ "free the unprivileged victim object through the bug, keeping the dangling reference",
22
+ "spray the privileged counterpart into the freed slot via same-cache reuse (open a write-protected file / trigger a privileged cred alloc)",
23
+ "use the dangling reference to operate on the now-privileged object (write through the privileged file / run with the swapped cred)"
24
+ ],
25
+ finalStep: "file-swap",
26
+ reliability: "high",
27
+ // Data-only: no leak, no KASLR/SMEP/SMAP dependence — the whole point.
28
+ caveats: [
29
+ "defeated by cred/file slab isolation (vendor DirtyCred mitigation, SLAB_VIRTUAL-era dedicated caches) \u2014 verify the victim shares a cache with the privileged object",
30
+ "needs a same-cache privileged allocation primitive reachable unprivileged"
31
+ ],
32
+ citation: "Lin, Wu, Xing \u2014 DirtyCred (CCS 2022 / Black Hat USA 2022); CVE-2021-4154, CVE-2022-2588"
33
+ },
34
+ {
35
+ id: "dirtyPagetable",
36
+ title: "Dirty Pagetable \u2014 page-UAF \u2192 PTE poison \u2192 arbitrary physical R/W",
37
+ appliesTo: ["page-uaf", "uaf"],
38
+ ceiling: "root",
39
+ preconditions: [
40
+ "page-granularity UAF: a freed page (order-0+) returned to the page allocator and reusable across caches",
41
+ "a dangling write into the freed page after it is reclaimed",
42
+ "ability to fault in a fresh page-table page (mmap + touch) so the freed page is reused as a PTE-bearing page table"
43
+ ],
44
+ sprayObjects: ["pte"],
45
+ steps: [
46
+ "free the victim page through the bug (cross-cache it back to the buddy allocator if the bug is slab-granularity \u2014 see crossCacheSlubStick)",
47
+ "fault in a user page table so the kernel reclaims the freed page as a page-table page",
48
+ "use the dangling write to forge a PTE pointing at an attacker-chosen physical frame \u2192 arbitrary physical read/write",
49
+ "locate kernel physmap and overwrite `core_pattern` (or `modprobe_path`) via the physical R/W"
50
+ ],
51
+ finalStep: "core_pattern",
52
+ reliability: "high",
53
+ caveats: [
54
+ "needs page-granularity reuse \u2014 a pure slab UAF must be widened with a cross-cache free first (crossCacheSlubStick)",
55
+ "physmap base discovery still wants a coarse KASLR defeat (EntryBleed prefetch in kctf.h) unless KASLR is off"
56
+ ],
57
+ citation: "Nicolas Wu \u2014 Dirty Pagetable writeup; kernelCTF LTS page-UAF winners"
58
+ },
59
+ {
60
+ id: "crossCacheSlubStick",
61
+ title: "Cross-cache SLUB stick \u2014 slab-UAF \u2192 page reuse bridge",
62
+ // The bridge: turns a slab-granularity bug into page-granularity reuse so a
63
+ // downstream recipe (Dirty Pagetable, or an unlike-cache reclaim) applies.
64
+ appliesTo: ["uaf", "oob-write", "page-uaf"],
65
+ ceiling: "arb-write",
66
+ preconditions: [
67
+ "a slab-cache UAF/OOB whose victim slab can be drained so the whole slab page is freed back to the buddy allocator",
68
+ "control over allocation/free timing to empty the victim slab"
69
+ ],
70
+ sprayObjects: ["msg_msg", "pipe_buffer"],
71
+ steps: [
72
+ "groom the victim slab so the bug's object is the last live one",
73
+ "free the remaining objects so the empty slab page returns to the buddy allocator (the cross-cache free)",
74
+ "immediately allocate the target page in a DIFFERENT cache (or a page-table page) so it reclaims the freed slab page",
75
+ "drive the dangling slab-relative write into the reclaimed cross-cache object \u2014 hand off to dirtyPagetable / dirtyCred for the root tail"
76
+ ],
77
+ finalStep: "modprobe_path",
78
+ reliability: "medium",
79
+ caveats: [
80
+ "RANDOM_KMALLOC_CACHES and freelist randomization raise the grooming cost (more sprays, lower landing rate)",
81
+ "a bridge, not a complete chain \u2014 pair it with a root-tail recipe"
82
+ ],
83
+ citation: "kernelCTF LTS cross-cache (SLUB) technique; kctf.h sprays"
84
+ },
85
+ {
86
+ id: "msgMsgArbRW",
87
+ title: "msg_msg / pipe_buffer spray \u2192 arbitrary R/W \u2192 modprobe_path",
88
+ appliesTo: ["uaf", "oob-write"],
89
+ ceiling: "root",
90
+ preconditions: [
91
+ "UAF or OOB-write in a generic `kmalloc-N` bucket reachable unprivileged",
92
+ "the victim cache is not bucket-isolated from the spray (SLAB_BUCKETS off, or an accounted spray for a `-cg-` victim)"
93
+ ],
94
+ sprayObjects: ["msg_msg", "pipe_buffer", "setxattr"],
95
+ steps: [
96
+ "spray msg_msg (or pipe_buffer) to reclaim the freed/adjacent slot",
97
+ "corrupt the reclaiming object's length/next field to widen into an out-of-bounds read (leak) and then an arbitrary write",
98
+ "overwrite `modprobe_path` (or `core_pattern`) and trigger it for root"
99
+ ],
100
+ finalStep: "modprobe_path",
101
+ reliability: "medium",
102
+ caveats: [
103
+ "SLAB_BUCKETS isolates msg_msg from a plain victim \u2014 see spray-selector.ts",
104
+ "setxattr is a transient (race) reclaim window; prefer the persistent sprays"
105
+ ],
106
+ citation: "kernelCTF LTS msg_msg/pipe_buffer winners; kctf.h sprays"
107
+ },
108
+ {
109
+ id: "fdTableSpray",
110
+ title: "fd-table spray \u2014 open()-flood grooms `struct file *` fd-arrays",
111
+ // The bridge/grooming vector: an open() flood expands a task's fdtable so
112
+ // the kernel allocates fresh `struct file *[]` arrays in a target kmalloc
113
+ // bucket — a controllable, persistent reclaim object for a slab UAF/OOB, and
114
+ // the layout target that feeds the fileOpsVtableConfusion tail.
115
+ appliesTo: ["uaf", "oob-write"],
116
+ ceiling: "arb-write",
117
+ preconditions: [
118
+ "a slab UAF/OOB in a `kmalloc-N` bucket the `struct fdtable` fd-array (`struct file *[]`) can be steered to reclaim",
119
+ "an unprivileged path to open() many descriptors so `files_struct` grows its `fdtable` and reallocates the fd-array into the victim bucket"
120
+ ],
121
+ sprayObjects: ["fdtable", "files_struct", "file"],
122
+ steps: [
123
+ "open() a flood of descriptors so the task's `files_struct` expands its `fdtable` and allocates a fresh `struct file *[]` fd-array in the target kmalloc bucket (dup2/dup to size the array precisely)",
124
+ "free the victim object through the bug, then trigger another fdtable grow so the `struct file *` fd-array reclaims the freed slot",
125
+ "drive the dangling/OOB write to corrupt a `struct file *` entry in the reclaimed fd-array so a live fd aliases an attacker-shaped `struct file`",
126
+ "operate through the confused fd \u2014 hand off to fileOpsVtableConfusion for the control-flow / privileged-object tail"
127
+ ],
128
+ finalStep: "file-swap",
129
+ reliability: "medium",
130
+ caveats: [
131
+ "the fd-array shares generic `kmalloc-N` \u2014 SLAB_BUCKETS / RANDOM_KMALLOC_CACHES isolate it from a plain victim (groom same-call-site instead)",
132
+ "a bridge, not a complete chain \u2014 pair it with fileOpsVtableConfusion or a DirtyCred file swap for the root tail"
133
+ ],
134
+ citation: "kernelCTF fd-array (files_struct/fdtable) grooming; classic `struct file *` reclaim vector"
135
+ },
136
+ {
137
+ id: "fakeCredOneShot",
138
+ title: "fake-cred one-shot \u2014 timer/ops UAF \u2192 forged cred + commit_creds hijack (data-only root)",
139
+ // A UAF (or a refcount bug collapsing to one) on an object that owns an
140
+ // embedded `timer_list` / callback the kernel re-arms and fires after free.
141
+ appliesTo: ["uaf", "refcount"],
142
+ ceiling: "root",
143
+ preconditions: [
144
+ "UAF (or refcount-to-UAF) on an object that owns an embedded `timer_list` (or a work/tasklet/ops callback) which the kernel re-arms (mod_timer) and fires AFTER the free",
145
+ "a same-cache spray that lands in the freed object's kmalloc bucket with fully attacker-controlled bytes (msg_msg via msgsnd is the canonical one \u2014 persists until msgrcv, header 48B)",
146
+ "the freed object's layout overlaps a forgeable `struct cred`: the callback pointer aliases `cred.euid/egid` (== the indirect-call target) and the object's slack covers cred caps + user/user_ns/ucounts/group_info",
147
+ "an unprivileged child that can busy-spin so the timer softirq fires in ITS task context (commit_creds escalates `current`)"
148
+ ],
149
+ sprayObjects: ["msg_msg"],
150
+ steps: [
151
+ "free the victim object through the bug while its timer is still re-armable",
152
+ "spray a forged `msg_msg` into the freed slot: inject ONLY the `timer_list.function = commit_creds` (leave entry.next/pprev/expires zeroed so detach is safe and timer_pending()==false), and lay a forged cred in the slack (caps=~0, user/user_ns/ucounts/group_info=&init_*)",
153
+ "let the kernel re-arm the timer (mod_timer on the reclaimed object) \u2014 the re-arm leaves the timer base/flags intact and takes .function from the forged bytes",
154
+ "the busy-spinning unprivileged child receives the timer softirq: call_timer_fn runs commit_creds(&obj) \u2192 the child is root; normalize euid/fsuid via CAP_SETUID from the forged cap_effective and drop a shell"
155
+ ],
156
+ finalStep: "cred-forge",
157
+ reliability: "high",
158
+ caveats: [
159
+ "CFI_CLANG / FineIBT rejects an indirect call to commit_creds from call_timer_fn \u2014 the forward-edge check requires a type-matching target; this one-shot assumes CFI is OFF (as on the proven hid-sony target)",
160
+ "hardened / KASLR-ON root needs a companion kernel-TEXT infoleak: the physmap leak (MODE_LEAK) recovers page_offset_base (defeats RANDOMIZE_MEMORY) but NOT .text, so commit_creds's slid address is still unknown without a paired text leak",
161
+ "needs the spray to share a cache with the victim (MEMCG off, or an accounted spray for a -cg- victim) \u2014 verify the bucket match"
162
+ ],
163
+ citation: "0sec hid-sony ghl_poke_timer UAF \u2192 LPE (fake-cred one-shot, 9/9 root); DirtyCred-family credential forge without arbitrary R/W"
164
+ },
165
+ {
166
+ id: "workqueueBounceRoot",
167
+ title: "timer-UAF \u2192 forge-pwq \u2192 kworker process-context bounce \u2192 WWW \u2192 modprobe_path",
168
+ // The GuitarZero full clean chain (0sec#1215): the poison-tolerant, no-arb-
169
+ // read sibling of fakeCredOneShot. Where the one-shot fires commit_creds in a
170
+ // softirq (fatal-fault on any miss), this bounces into PROCESS context via a
171
+ // real kworker, so a survivable WWW gadget can overwrite modprobe_path and a
172
+ // miss leaves the box ALIVE. See guitarzero-primitives.ts for the primitives.
173
+ appliesTo: ["uaf", "refcount"],
174
+ ceiling: "root",
175
+ preconditions: [
176
+ "UAF on an object owning an embedded `timer_list` the kernel re-arms after free (as fakeCredOneShot), reclaimed via a self-address-leakable object (user_key_payload \u2014 keyctl_read exposes the reclaimed bytes)",
177
+ "a self-address leak (N-object same-bucket timer linkage \u2192 chunk = leaked\u2212344) and the leaked timer-bucket per-CPU pointer",
178
+ "a kernel-text base (EntryBleed unprivileged, or a companion leak on a virtualized-TSC box) to slide cpu_worker_pools/delayed_work_timer_fn/gadget",
179
+ "CFI off: the forged work->func indirect call is not forward-edge-checked"
180
+ ],
181
+ sprayObjects: ["user_key", "msg_msg"],
182
+ steps: [
183
+ "free the timer-owning victim; reclaim with a user_key_payload carrying the forged timer/work layout (leak-then-reoccupy bakes the self-address in)",
184
+ "leak the chunk self-address + timer-bucket pointer; recover __per_cpu_offset[0] from the bucket + read-back expires",
185
+ "forge workqueue_struct+pool_workqueue at chunk+256 (256-aligned; get_work_pwq masks the low 8 bits) and point pwq->pool at the computed real cpu_worker_pools[0][0] \u2014 no arbitrary read",
186
+ "timer.function=delayed_work_timer_fn \u2192 __queue_work \u2192 process_one_work runs work->func in a real kworker (PROCESS context); the self-address-baked work->entry passes the workqueue.c list_empty gate",
187
+ 'work->func = perf_swevent_del (survivable unlink-WWW) writes *(modprobe_path)="/tmp/x"; socket() over an unregistered family fires request_module \u2192 /tmp/x runs as root'
188
+ ],
189
+ finalStep: "modprobe_path",
190
+ reliability: "high",
191
+ caveats: [
192
+ "CFI_CLANG / FineIBT forward-edge-checks the work->func indirect call \u2014 the WWW gadget must be a type-matching signed target; the proven chain assumes CFI OFF (kCFI \u21D2 a data-only tail is needed)",
193
+ "EntryBleed (CVE-2022-4543) supplies the text base bare-metal; it is marginal on a virtualized-TSC (KVM) box (rdtscp buries the prefetch delta) \u2014 supply the base via a companion leak there",
194
+ "one probabilistic reoccupy race per boot (~2/3; ~96% within 3 boots); a miss bakes clockevents_handle_noop (endbr64;ret) so the box stays ALIVE for retry"
195
+ ],
196
+ citation: "0sec hid-sony ghl_poke_timer UAF \u2192 clean unprivileged LPE (/root/ghl-clean/ghl_lpe_clean.c, uid=0 5/7 boots; /root/ghl-full FULL-ROOT); EntryBleed CVE-2022-4543"
197
+ },
198
+ {
199
+ id: "fileOpsVtableConfusion",
200
+ title: "pipe_buffer/file \u2192 fake file_operations vtable \u2192 fileops hijack (control transfer)",
201
+ // Type-confusion tail: with an arb-write or UAF over a `struct file` (or a
202
+ // `pipe_buffer` aliased onto one), overwrite `f_op` / the fd-array's
203
+ // `fde_file[0]` to point at an attacker-shaped `file_operations`, then invoke
204
+ // a fileop (->unlocked_ioctl / ->release) to hijack the slot and transfer
205
+ // control (commit_creds(prepare_kernel_cred(0)) or a stack pivot).
206
+ appliesTo: ["uaf", "oob-write"],
207
+ ceiling: "root",
208
+ preconditions: [
209
+ "an arbitrary write, or a UAF/OOB, over a `struct file` (or a `pipe_buffer` that can be aliased onto one \u2014 see fdTableSpray)",
210
+ "a kernel-readable location holding an attacker-shaped `file_operations` (sprayed pipe_buffer/msg_msg payload, or a known writable global)",
211
+ "an unprivileged path to invoke a fileop on the confused fd (ioctl/read/close) so the hijacked slot is called"
212
+ ],
213
+ sprayObjects: ["file", "pipe_buffer", "fdtable"],
214
+ steps: [
215
+ "overwrite the victim `struct file`'s `f_op` (or the fd-array's `fde_file[0]` \u2192 its `f_op`) with a pointer to a fake `file_operations`",
216
+ "populate the fake `file_operations` so the targeted slot (`->unlocked_ioctl` / `->release`) points at the control-transfer gadget",
217
+ "invoke the fileop on the confused fd (ioctl / close) to call the hijacked slot \u2014 transfer control to the gadget",
218
+ "run `commit_creds(prepare_kernel_cred(0))` (or pivot) from the hijacked fileop to overwrite the current task's creds \u2192 root"
219
+ ],
220
+ finalStep: "cred-overwrite",
221
+ reliability: "medium",
222
+ caveats: [
223
+ "CFI / FineIBT rejects an arbitrary indirect call target \u2014 the fake `->ioctl` must land on a type-matching signed gadget, not raw shellcode",
224
+ "KASLR requires a text/heap leak first to place the fake vtable + gadget (pair with a leak primitive in library.ts)",
225
+ "read-only `struct file.f_op` in some builds \u2014 confuse via a controllable `pipe_buffer`/fd-array alias rather than an in-place `f_op` write"
226
+ ],
227
+ citation: "classic fake-file_operations / vtable hijack; pipe_buffer & fd-array type-confusion (kernelCTF fileops slot hijack)"
228
+ }
229
+ ];
230
+ function getRecipe(id) {
231
+ return RECIPE_LIBRARY.find((r) => r.id === id);
232
+ }
233
+
234
+ // packages/core/dist/kernel/exploit/strategy.js
235
+ var ESCALATION_LADDER = [
236
+ "none",
237
+ "attempted",
238
+ "triggered",
239
+ "reclaim",
240
+ "arb-read",
241
+ "arb-write",
242
+ "root"
243
+ ];
244
+ function rungRank(rung) {
245
+ const rank = ESCALATION_LADDER.indexOf(rung);
246
+ return rank < 0 ? 0 : rank;
247
+ }
248
+ function maxRung(a, b) {
249
+ return rungRank(a) >= rungRank(b) ? a : b;
250
+ }
251
+ function ratchet(current, observed) {
252
+ return maxRung(current, observed);
253
+ }
254
+ function rungAtLeast(rung, floor) {
255
+ return rungRank(rung) >= rungRank(floor);
256
+ }
257
+ function ladderUpTo(ceiling) {
258
+ const ceil = rungRank(ceiling);
259
+ return ESCALATION_LADDER.filter((rung) => rung !== "none" && rungRank(rung) <= ceil);
260
+ }
261
+
262
+ export {
263
+ ESCALATION_LADDER,
264
+ rungRank,
265
+ maxRung,
266
+ ratchet,
267
+ rungAtLeast,
268
+ ladderUpTo,
269
+ RECIPE_LIBRARY,
270
+ getRecipe
271
+ };