@vigolium/piolium 0.0.1

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 (271) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +117 -0
  3. package/agents/access-auditor.md +300 -0
  4. package/agents/assumption-breaker.md +154 -0
  5. package/agents/attack-designer.md +116 -0
  6. package/agents/code-scanner.md +139 -0
  7. package/agents/concurrency-auditor.md +238 -0
  8. package/agents/confirm-writer.md +257 -0
  9. package/agents/context-reviewer.md +274 -0
  10. package/agents/cross-verifier.md +165 -0
  11. package/agents/cve-scout.md +381 -0
  12. package/agents/env-builder.md +282 -0
  13. package/agents/env-profiler.md +205 -0
  14. package/agents/evidence-collector.md +140 -0
  15. package/agents/finding-grader.md +142 -0
  16. package/agents/finding-writer.md +148 -0
  17. package/agents/flow-tracer.md +106 -0
  18. package/agents/goal-backtracer.md +146 -0
  19. package/agents/history-miner.md +467 -0
  20. package/agents/independent-verifier.md +118 -0
  21. package/agents/intent-mapper.md +183 -0
  22. package/agents/longshot-collector.md +128 -0
  23. package/agents/longshot-prober.md +126 -0
  24. package/agents/patch-auditor.md +73 -0
  25. package/agents/poc-author.md +124 -0
  26. package/agents/poc-runner.md +194 -0
  27. package/agents/probe-lead.md +269 -0
  28. package/agents/red-challenger.md +101 -0
  29. package/agents/report-composer.md +208 -0
  30. package/agents/review-adjudicator.md +216 -0
  31. package/agents/spec-auditor.md +155 -0
  32. package/agents/taint-tracer.md +265 -0
  33. package/agents/test-locator.md +209 -0
  34. package/agents/threat-modeler.md +132 -0
  35. package/agents/variant-scanner.md +108 -0
  36. package/agents/variant-spotter.md +110 -0
  37. package/bin/piolium.mjs +376 -0
  38. package/extensions/piolium/_vendor/yaml.bundle.d.mts +6 -0
  39. package/extensions/piolium/_vendor/yaml.bundle.mjs +139 -0
  40. package/extensions/piolium/agent-runner.ts +322 -0
  41. package/extensions/piolium/agents.ts +266 -0
  42. package/extensions/piolium/audit-state.ts +522 -0
  43. package/extensions/piolium/bundled-resources.ts +97 -0
  44. package/extensions/piolium/candidate-scan.ts +966 -0
  45. package/extensions/piolium/command-target.ts +177 -0
  46. package/extensions/piolium/console-stream.ts +57 -0
  47. package/extensions/piolium/export-results.ts +380 -0
  48. package/extensions/piolium/findings.ts +448 -0
  49. package/extensions/piolium/heartbeat.ts +182 -0
  50. package/extensions/piolium/help.ts +234 -0
  51. package/extensions/piolium/index.ts +1865 -0
  52. package/extensions/piolium/longshot.ts +530 -0
  53. package/extensions/piolium/matcher-suggestions.ts +196 -0
  54. package/extensions/piolium/matcher-utils.ts +83 -0
  55. package/extensions/piolium/modes/balanced.ts +750 -0
  56. package/extensions/piolium/modes/confirm-bootstrap.ts +186 -0
  57. package/extensions/piolium/modes/confirm.ts +697 -0
  58. package/extensions/piolium/modes/deep.ts +917 -0
  59. package/extensions/piolium/modes/diff.ts +177 -0
  60. package/extensions/piolium/modes/lite.ts +540 -0
  61. package/extensions/piolium/modes/longshot.ts +595 -0
  62. package/extensions/piolium/modes/merge.ts +204 -0
  63. package/extensions/piolium/modes/phase-runner.ts +267 -0
  64. package/extensions/piolium/modes/reinvest.ts +546 -0
  65. package/extensions/piolium/modes/revisit.ts +279 -0
  66. package/extensions/piolium/modes.ts +48 -0
  67. package/extensions/piolium/phase-labels.ts +123 -0
  68. package/extensions/piolium/phase-status-strip.ts +92 -0
  69. package/extensions/piolium/prompt-prefix-editor.ts +39 -0
  70. package/extensions/piolium/providers/anthropic-vertex.ts +836 -0
  71. package/extensions/piolium/recon.ts +409 -0
  72. package/extensions/piolium/result-stats.ts +105 -0
  73. package/extensions/piolium/retry.ts +120 -0
  74. package/extensions/piolium/scheduler.ts +212 -0
  75. package/extensions/piolium/secrets.ts +368 -0
  76. package/extensions/piolium/tools/web-tools.ts +148 -0
  77. package/package.json +77 -0
  78. package/skills/agentic-actions-auditor/SKILL.md +327 -0
  79. package/skills/agentic-actions-auditor/references/action-profiles.md +186 -0
  80. package/skills/agentic-actions-auditor/references/cross-file-resolution.md +209 -0
  81. package/skills/agentic-actions-auditor/references/foundations.md +94 -0
  82. package/skills/agentic-actions-auditor/references/vector-a-env-var-intermediary.md +77 -0
  83. package/skills/agentic-actions-auditor/references/vector-b-direct-expression-injection.md +83 -0
  84. package/skills/agentic-actions-auditor/references/vector-c-cli-data-fetch.md +83 -0
  85. package/skills/agentic-actions-auditor/references/vector-d-pr-target-checkout.md +88 -0
  86. package/skills/agentic-actions-auditor/references/vector-e-error-log-injection.md +88 -0
  87. package/skills/agentic-actions-auditor/references/vector-f-subshell-expansion.md +82 -0
  88. package/skills/agentic-actions-auditor/references/vector-g-eval-of-ai-output.md +91 -0
  89. package/skills/agentic-actions-auditor/references/vector-h-dangerous-sandbox-configs.md +102 -0
  90. package/skills/agentic-actions-auditor/references/vector-i-wildcard-allowlists.md +88 -0
  91. package/skills/audit/SKILL.md +562 -0
  92. package/skills/audit/assets/icon.svg +7 -0
  93. package/skills/audit/hooks/scripts/validate_phase_output.py +550 -0
  94. package/skills/audit/references/adversarial-review.md +148 -0
  95. package/skills/audit/references/architecture-aware-sast.md +306 -0
  96. package/skills/audit/references/audit-workflow.md +737 -0
  97. package/skills/audit/references/chamber-protocol.md +384 -0
  98. package/skills/audit/references/creative-attack-modes.md +221 -0
  99. package/skills/audit/references/deep-analysis.md +273 -0
  100. package/skills/audit/references/domain-attack-playbooks.md +1129 -0
  101. package/skills/audit/references/knowledge-base-template.md +513 -0
  102. package/skills/audit/references/real-env-validation.md +191 -0
  103. package/skills/audit/references/report-templates.md +417 -0
  104. package/skills/audit/references/triage-and-prereqs.md +134 -0
  105. package/skills/audit/scripts/consolidate_drafts.py +554 -0
  106. package/skills/audit/scripts/partition_findings.py +152 -0
  107. package/skills/audit/scripts/rg-hotspots.sh +121 -0
  108. package/skills/audit/scripts/stamp_file_state.py +349 -0
  109. package/skills/code-reviewer/SKILL.md +65 -0
  110. package/skills/codeql/SKILL.md +281 -0
  111. package/skills/codeql/references/build-fixes.md +90 -0
  112. package/skills/codeql/references/diagnostic-query-templates.md +339 -0
  113. package/skills/codeql/references/extension-yaml-format.md +209 -0
  114. package/skills/codeql/references/important-only-suite.md +153 -0
  115. package/skills/codeql/references/language-details.md +207 -0
  116. package/skills/codeql/references/macos-arm64e-workaround.md +179 -0
  117. package/skills/codeql/references/performance-tuning.md +111 -0
  118. package/skills/codeql/references/quality-assessment.md +172 -0
  119. package/skills/codeql/references/ruleset-catalog.md +63 -0
  120. package/skills/codeql/references/run-all-suite.md +92 -0
  121. package/skills/codeql/references/sarif-processing.md +79 -0
  122. package/skills/codeql/references/threat-models.md +51 -0
  123. package/skills/codeql/workflows/build-database.md +280 -0
  124. package/skills/codeql/workflows/create-data-extensions.md +261 -0
  125. package/skills/codeql/workflows/run-analysis.md +301 -0
  126. package/skills/differential-review/SKILL.md +220 -0
  127. package/skills/differential-review/adversarial.md +203 -0
  128. package/skills/differential-review/methodology.md +234 -0
  129. package/skills/differential-review/patterns.md +300 -0
  130. package/skills/differential-review/reporting.md +369 -0
  131. package/skills/fp-check/SKILL.md +125 -0
  132. package/skills/fp-check/references/bug-class-verification.md +114 -0
  133. package/skills/fp-check/references/deep-verification.md +143 -0
  134. package/skills/fp-check/references/evidence-templates.md +91 -0
  135. package/skills/fp-check/references/false-positive-patterns.md +115 -0
  136. package/skills/fp-check/references/gate-reviews.md +27 -0
  137. package/skills/fp-check/references/standard-verification.md +78 -0
  138. package/skills/insecure-defaults/SKILL.md +117 -0
  139. package/skills/insecure-defaults/references/examples.md +409 -0
  140. package/skills/last30days/SKILL.md +444 -0
  141. package/skills/sarif-parsing/SKILL.md +483 -0
  142. package/skills/sarif-parsing/resources/jq-queries.md +162 -0
  143. package/skills/sarif-parsing/resources/sarif_helpers.py +331 -0
  144. package/skills/security-threat-model/LICENSE.txt +201 -0
  145. package/skills/security-threat-model/SKILL.md +81 -0
  146. package/skills/security-threat-model/agents/openai.yaml +4 -0
  147. package/skills/security-threat-model/references/prompt-template.md +255 -0
  148. package/skills/security-threat-model/references/security-controls-and-assets.md +32 -0
  149. package/skills/semgrep/SKILL.md +212 -0
  150. package/skills/semgrep/references/rulesets.md +162 -0
  151. package/skills/semgrep/references/scan-modes.md +110 -0
  152. package/skills/semgrep/references/scanner-task-prompt.md +140 -0
  153. package/skills/semgrep/scripts/merge_sarif.py +203 -0
  154. package/skills/semgrep/workflows/scan-workflow.md +311 -0
  155. package/skills/semgrep-rule-creator/SKILL.md +168 -0
  156. package/skills/semgrep-rule-creator/references/quick-reference.md +202 -0
  157. package/skills/semgrep-rule-creator/references/workflow.md +240 -0
  158. package/skills/semgrep-rule-variant-creator/SKILL.md +205 -0
  159. package/skills/semgrep-rule-variant-creator/references/applicability-analysis.md +250 -0
  160. package/skills/semgrep-rule-variant-creator/references/language-syntax-guide.md +324 -0
  161. package/skills/semgrep-rule-variant-creator/references/workflow.md +518 -0
  162. package/skills/sharp-edges/SKILL.md +292 -0
  163. package/skills/sharp-edges/references/auth-patterns.md +252 -0
  164. package/skills/sharp-edges/references/case-studies.md +274 -0
  165. package/skills/sharp-edges/references/config-patterns.md +333 -0
  166. package/skills/sharp-edges/references/crypto-apis.md +190 -0
  167. package/skills/sharp-edges/references/lang-c.md +205 -0
  168. package/skills/sharp-edges/references/lang-csharp.md +285 -0
  169. package/skills/sharp-edges/references/lang-go.md +270 -0
  170. package/skills/sharp-edges/references/lang-java.md +263 -0
  171. package/skills/sharp-edges/references/lang-javascript.md +269 -0
  172. package/skills/sharp-edges/references/lang-kotlin.md +265 -0
  173. package/skills/sharp-edges/references/lang-php.md +245 -0
  174. package/skills/sharp-edges/references/lang-python.md +274 -0
  175. package/skills/sharp-edges/references/lang-ruby.md +273 -0
  176. package/skills/sharp-edges/references/lang-rust.md +272 -0
  177. package/skills/sharp-edges/references/lang-swift.md +287 -0
  178. package/skills/sharp-edges/references/language-specific.md +588 -0
  179. package/skills/spec-to-code-compliance/SKILL.md +357 -0
  180. package/skills/spec-to-code-compliance/resources/COMPLETENESS_CHECKLIST.md +69 -0
  181. package/skills/spec-to-code-compliance/resources/IR_EXAMPLES.md +417 -0
  182. package/skills/spec-to-code-compliance/resources/OUTPUT_REQUIREMENTS.md +105 -0
  183. package/skills/supply-chain-risk-auditor/SKILL.md +67 -0
  184. package/skills/supply-chain-risk-auditor/resources/results-template.md +41 -0
  185. package/skills/variant-analysis/METHODOLOGY.md +327 -0
  186. package/skills/variant-analysis/SKILL.md +142 -0
  187. package/skills/variant-analysis/resources/codeql/cpp.ql +119 -0
  188. package/skills/variant-analysis/resources/codeql/go.ql +69 -0
  189. package/skills/variant-analysis/resources/codeql/java.ql +71 -0
  190. package/skills/variant-analysis/resources/codeql/javascript.ql +63 -0
  191. package/skills/variant-analysis/resources/codeql/python.ql +80 -0
  192. package/skills/variant-analysis/resources/semgrep/cpp.yaml +98 -0
  193. package/skills/variant-analysis/resources/semgrep/go.yaml +63 -0
  194. package/skills/variant-analysis/resources/semgrep/java.yaml +61 -0
  195. package/skills/variant-analysis/resources/semgrep/javascript.yaml +60 -0
  196. package/skills/variant-analysis/resources/semgrep/python.yaml +72 -0
  197. package/skills/variant-analysis/resources/variant-report-template.md +75 -0
  198. package/skills/vuln-report/SKILL.md +137 -0
  199. package/skills/vuln-report/agents/openai.yaml +4 -0
  200. package/skills/vuln-report/references/report-template.md +135 -0
  201. package/skills/wooyun-legacy/SKILL.md +367 -0
  202. package/skills/wooyun-legacy/references/bank-penetration.md +222 -0
  203. package/skills/wooyun-legacy/references/checklists/command-execution-checklist.md +119 -0
  204. package/skills/wooyun-legacy/references/checklists/csrf-checklist.md +74 -0
  205. package/skills/wooyun-legacy/references/checklists/file-upload-checklist.md +108 -0
  206. package/skills/wooyun-legacy/references/checklists/info-disclosure-checklist.md +114 -0
  207. package/skills/wooyun-legacy/references/checklists/logic-flaws-checklist.md +95 -0
  208. package/skills/wooyun-legacy/references/checklists/misconfig-checklist.md +124 -0
  209. package/skills/wooyun-legacy/references/checklists/path-traversal-checklist.md +87 -0
  210. package/skills/wooyun-legacy/references/checklists/rce-checklist.md +93 -0
  211. package/skills/wooyun-legacy/references/checklists/sql-injection-checklist.md +97 -0
  212. package/skills/wooyun-legacy/references/checklists/ssrf-checklist.md +99 -0
  213. package/skills/wooyun-legacy/references/checklists/unauthorized-access-checklist.md +89 -0
  214. package/skills/wooyun-legacy/references/checklists/weak-password-checklist.md +115 -0
  215. package/skills/wooyun-legacy/references/checklists/xss-checklist.md +103 -0
  216. package/skills/wooyun-legacy/references/checklists/xxe-checklist.md +130 -0
  217. package/skills/wooyun-legacy/references/info-disclosure.md +975 -0
  218. package/skills/wooyun-legacy/references/logic-flaws.md +721 -0
  219. package/skills/wooyun-legacy/references/path-traversal.md +1191 -0
  220. package/skills/wooyun-legacy/references/telecom-penetration.md +156 -0
  221. package/skills/wooyun-legacy/references/unauthorized-access.md +980 -0
  222. package/skills/wooyun-legacy/references/xss.md +746 -0
  223. package/skills/zeroize-audit/SKILL.md +371 -0
  224. package/skills/zeroize-audit/configs/c.yaml +21 -0
  225. package/skills/zeroize-audit/configs/default.yaml +128 -0
  226. package/skills/zeroize-audit/configs/rust.yaml +83 -0
  227. package/skills/zeroize-audit/prompts/report_template.md +238 -0
  228. package/skills/zeroize-audit/prompts/system.md +163 -0
  229. package/skills/zeroize-audit/prompts/task.md +97 -0
  230. package/skills/zeroize-audit/references/compile-commands.md +231 -0
  231. package/skills/zeroize-audit/references/detection-strategy.md +191 -0
  232. package/skills/zeroize-audit/references/ir-analysis.md +252 -0
  233. package/skills/zeroize-audit/references/mcp-analysis.md +221 -0
  234. package/skills/zeroize-audit/references/poc-generation.md +470 -0
  235. package/skills/zeroize-audit/references/rust-zeroization-patterns.md +867 -0
  236. package/skills/zeroize-audit/schemas/input.json +83 -0
  237. package/skills/zeroize-audit/schemas/output.json +140 -0
  238. package/skills/zeroize-audit/tools/analyze_asm.sh +202 -0
  239. package/skills/zeroize-audit/tools/analyze_cfg.py +381 -0
  240. package/skills/zeroize-audit/tools/analyze_heap.sh +211 -0
  241. package/skills/zeroize-audit/tools/analyze_ir_semantic.py +429 -0
  242. package/skills/zeroize-audit/tools/diff_ir.sh +135 -0
  243. package/skills/zeroize-audit/tools/diff_rust_mir.sh +189 -0
  244. package/skills/zeroize-audit/tools/emit_asm.sh +67 -0
  245. package/skills/zeroize-audit/tools/emit_ir.sh +77 -0
  246. package/skills/zeroize-audit/tools/emit_rust_asm.sh +178 -0
  247. package/skills/zeroize-audit/tools/emit_rust_ir.sh +150 -0
  248. package/skills/zeroize-audit/tools/emit_rust_mir.sh +158 -0
  249. package/skills/zeroize-audit/tools/extract_compile_flags.py +284 -0
  250. package/skills/zeroize-audit/tools/generate_poc.py +1329 -0
  251. package/skills/zeroize-audit/tools/mcp/apply_confidence_gates.py +113 -0
  252. package/skills/zeroize-audit/tools/mcp/check_mcp.sh +68 -0
  253. package/skills/zeroize-audit/tools/mcp/normalize_mcp_evidence.py +125 -0
  254. package/skills/zeroize-audit/tools/scripts/check_llvm_patterns.py +481 -0
  255. package/skills/zeroize-audit/tools/scripts/check_mir_patterns.py +554 -0
  256. package/skills/zeroize-audit/tools/scripts/check_rust_asm.py +424 -0
  257. package/skills/zeroize-audit/tools/scripts/check_rust_asm_aarch64.py +300 -0
  258. package/skills/zeroize-audit/tools/scripts/check_rust_asm_x86.py +283 -0
  259. package/skills/zeroize-audit/tools/scripts/find_dangerous_apis.py +375 -0
  260. package/skills/zeroize-audit/tools/scripts/semantic_audit.py +923 -0
  261. package/skills/zeroize-audit/tools/track_dataflow.sh +196 -0
  262. package/skills/zeroize-audit/tools/validate_rust_toolchain.sh +298 -0
  263. package/skills/zeroize-audit/workflows/phase-0-preflight.md +150 -0
  264. package/skills/zeroize-audit/workflows/phase-1-source-analysis.md +144 -0
  265. package/skills/zeroize-audit/workflows/phase-2-compiler-analysis.md +139 -0
  266. package/skills/zeroize-audit/workflows/phase-3-interim-report.md +46 -0
  267. package/skills/zeroize-audit/workflows/phase-4-poc-generation.md +46 -0
  268. package/skills/zeroize-audit/workflows/phase-5-poc-validation.md +136 -0
  269. package/skills/zeroize-audit/workflows/phase-6-final-report.md +44 -0
  270. package/skills/zeroize-audit/workflows/phase-7-test-generation.md +42 -0
  271. package/themes/piolium-srcery.json +94 -0
@@ -0,0 +1,481 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # requires-python = ">=3.11"
4
+ # dependencies = []
5
+ # ///
6
+ """
7
+ check_llvm_patterns.py — LLVM IR comparison for Rust dead-store-elimination findings.
8
+
9
+ Reads LLVM IR files emitted by emit_rust_ir.sh (required: O0 and O2; optional: O1/O3) and detects:
10
+ - Volatile store count drop O0→O2 (OPTIMIZED_AWAY_ZEROIZE)
11
+ - Non-volatile llvm.memset on secret-sized range (OPTIMIZED_AWAY_ZEROIZE)
12
+ - alloca with @llvm.lifetime.end but no store volatile (STACK_RETENTION)
13
+ - Secret alloca present at O0 but absent at O2 (SROA/mem2reg) (OPTIMIZED_AWAY_ZEROIZE)
14
+ - Secret value in argument registers at call site (REGISTER_SPILL)
15
+
16
+ Usage:
17
+ uv run check_llvm_patterns.py --o0 <file.O0.ll> --o2 <file.O2.ll> --out <findings.json>
18
+ uv run check_llvm_patterns.py \
19
+ --o0 <file.O0.ll> --o1 <file.O1.ll> --o2 <file.O2.ll> --o3 <file.O3.ll> \
20
+ --out <findings.json>
21
+
22
+ Exit codes:
23
+ 0 — ran successfully (findings may be empty)
24
+ 1 — input file not found
25
+ 2 — argument error
26
+ """
27
+
28
+ import argparse
29
+ import json
30
+ import re
31
+ import sys
32
+ from pathlib import Path
33
+
34
+ # ---------------------------------------------------------------------------
35
+ # Secret-sized alloca sizes (bytes) — common cryptographic key sizes
36
+ # ---------------------------------------------------------------------------
37
+
38
+ SECRET_ALLOCA_SIZES = {16, 24, 32, 48, 64, 96, 128}
39
+
40
+ # Sensitive variable name pattern (matches LLVM SSA names)
41
+ SENSITIVE_SSA_RE = re.compile(
42
+ r"(?i)%(\w*(?:key|secret|password|token|nonce|seed|priv|master|credential)\w*)"
43
+ )
44
+
45
+ # ---------------------------------------------------------------------------
46
+ # Finding counter
47
+ # ---------------------------------------------------------------------------
48
+
49
+ _finding_counter = [0]
50
+
51
+
52
+ def make_finding(
53
+ category: str,
54
+ severity: str,
55
+ detail: str,
56
+ file: str,
57
+ line: int,
58
+ symbol: str = "",
59
+ confidence: str = "likely",
60
+ ) -> dict:
61
+ _finding_counter[0] += 1
62
+ fid = f"F-RUST-IR-{_finding_counter[0]:04d}"
63
+ return {
64
+ "id": fid,
65
+ "language": "rust",
66
+ "category": category,
67
+ "severity": severity,
68
+ "confidence": confidence,
69
+ "detail": detail,
70
+ "symbol": symbol,
71
+ "location": {"file": file, "line": line},
72
+ "evidence": [{"source": "llvm_ir", "detail": detail}],
73
+ }
74
+
75
+
76
+ # ---------------------------------------------------------------------------
77
+ # IR helpers
78
+ # ---------------------------------------------------------------------------
79
+
80
+
81
+ def count_volatile_stores(ir_text: str) -> int:
82
+ return len(re.findall(r"\bstore volatile\b", ir_text))
83
+
84
+
85
+ def extract_volatile_stores_by_target(ir_text: str) -> dict[str, int]:
86
+ """
87
+ Return volatile-store counts keyed by the destination symbol.
88
+ Example matches:
89
+ store volatile i8 0, ptr %key
90
+ store volatile i32 0, i32* %buf
91
+ """
92
+ stores: dict[str, int] = {}
93
+ vol_re = re.compile(r"\bstore volatile\b[^,]*,\s*(?:ptr|i\d+\*)\s+%([\w\.\-]+)")
94
+ for m in vol_re.finditer(ir_text):
95
+ name = m.group(1)
96
+ stores[name] = stores.get(name, 0) + 1
97
+ return stores
98
+
99
+
100
+ def extract_allocas(ir_text: str) -> dict[str, int]:
101
+ """
102
+ Return {alloca_name: size_bytes} for fixed-size byte array allocas.
103
+ Matches: %name = alloca [N x i8]
104
+ """
105
+ alloca_re = re.compile(r"%(\w+)\s*=\s*alloca\s+\[(\d+)\s*x\s*i8\]")
106
+ allocas: dict[str, int] = {}
107
+ for m in alloca_re.finditer(ir_text):
108
+ allocas[m.group(1)] = int(m.group(2))
109
+ return allocas
110
+
111
+
112
+ def extract_lifetime_ends(ir_text: str) -> set[str]:
113
+ """Return set of alloca names referenced in @llvm.lifetime.end calls."""
114
+ lifetime_re = re.compile(r"call void @llvm\.lifetime\.end[^(]*\([^,]+,\s*(?:ptr|i8\*)\s+%(\w+)")
115
+ return {m.group(1) for m in lifetime_re.finditer(ir_text)}
116
+
117
+
118
+ def extract_volatile_store_targets(ir_text: str) -> set[str]:
119
+ """Return set of symbols that receive volatile stores."""
120
+ return set(extract_volatile_stores_by_target(ir_text).keys())
121
+
122
+
123
+ def find_nonvolatile_memsets(ir_text: str) -> list[tuple[int, str]]:
124
+ """
125
+ Return (lineno, line) for non-volatile @llvm.memset calls.
126
+ Volatile variant is @llvm.memset.element.unordered.atomic or has i1 true volatile flag.
127
+ """
128
+ results: list[tuple[int, str]] = []
129
+ memset_re = re.compile(r"call void @llvm\.memset\.")
130
+ volatile_flag_re = re.compile(r"i1\s+true") # old-style volatile flag in args
131
+
132
+ for lineno, line in enumerate(ir_text.splitlines(), start=1):
133
+ if not memset_re.search(line):
134
+ continue
135
+ # Skip if it's the volatile atomic variant
136
+ if "unordered.atomic" in line:
137
+ continue
138
+ # Skip if volatile flag (i1 true) is present in args
139
+ if volatile_flag_re.search(line):
140
+ continue
141
+ results.append((lineno, line.strip()))
142
+ return results
143
+
144
+
145
+ def find_secret_returns(ir_text: str) -> list[tuple[int, str]]:
146
+ """
147
+ Detect returns of secret-named SSA values.
148
+ Returns (lineno, symbol_without_percent).
149
+ """
150
+ results: list[tuple[int, str]] = []
151
+ ret_re = re.compile(
152
+ r"\bret\s+[^%]*%(\w*(?:key|secret|password|token|nonce|seed|priv|master|credential)\w*)",
153
+ re.IGNORECASE,
154
+ )
155
+ for lineno, line in enumerate(ir_text.splitlines(), start=1):
156
+ m = ret_re.search(line)
157
+ if m:
158
+ results.append((lineno, m.group(1)))
159
+ return results
160
+
161
+
162
+ def find_secret_aggregate_passes(ir_text: str) -> list[tuple[int, str]]:
163
+ """
164
+ Detect call sites that appear to pass aggregate values containing secret-named
165
+ symbols by value. This is heuristic and intentionally conservative.
166
+ Returns (lineno, argument_snippet).
167
+ """
168
+ results: list[tuple[int, str]] = []
169
+ call_re = re.compile(r"\bcall\s+\S+\s+@\w+\s*\(([^)]*)\)")
170
+ for lineno, line in enumerate(ir_text.splitlines(), start=1):
171
+ m = call_re.search(line)
172
+ if not m:
173
+ continue
174
+ args = m.group(1)
175
+ if re.search(
176
+ r"%\w*(?:key|secret|password|token|nonce|seed|priv|master|credential)\w*",
177
+ args,
178
+ re.IGNORECASE,
179
+ ) and ("{" in args or "byval" in args):
180
+ results.append((lineno, args[:120]))
181
+ return results
182
+
183
+
184
+ def find_arg_load_calls(ir_text: str) -> list[tuple[int, str, str]]:
185
+ """
186
+ Detect: %secret_val = load ... %secret_alloca followed by a call that uses %secret_val.
187
+ Returns (lineno, varname, callee).
188
+ """
189
+ results: list[tuple[int, str, str]] = []
190
+ lines = ir_text.splitlines()
191
+
192
+ load_re = re.compile(
193
+ r"(%\w*(?:key|secret|password|token|nonce|seed)\w*)\s*=\s*load\b", re.IGNORECASE
194
+ )
195
+ call_re = re.compile(r"call\s+\S+\s+(@\w+)\s*\(([^)]*)\)")
196
+
197
+ loaded_vars: dict[str, int] = {} # varname → lineno
198
+ define_re = re.compile(r"^define\s")
199
+
200
+ for lineno, line in enumerate(lines, start=1):
201
+ # Reset tracked loads at each LLVM IR function boundary to avoid
202
+ # cross-function false positives (I17).
203
+ if define_re.match(line):
204
+ loaded_vars.clear()
205
+ continue
206
+
207
+ # Track loads of sensitive-named SSA values
208
+ m = load_re.search(line)
209
+ if m:
210
+ loaded_vars[m.group(1)] = lineno
211
+ continue
212
+
213
+ # Check call sites
214
+ mc = call_re.search(line)
215
+ if not mc:
216
+ continue
217
+ callee = mc.group(1)
218
+ if "zeroize" in callee.lower() or "memset" in callee.lower():
219
+ continue
220
+ args = mc.group(2)
221
+ for varname, _load_lineno in loaded_vars.items():
222
+ if varname in args:
223
+ results.append((lineno, varname.lstrip("%"), callee))
224
+
225
+ return results
226
+
227
+
228
+ # ---------------------------------------------------------------------------
229
+ # Main analysis
230
+ # ---------------------------------------------------------------------------
231
+
232
+
233
+ def analyze(level_to_ir: dict[str, tuple[str, str]]) -> list[dict]:
234
+ """Analyze LLVM IR files for zeroization issues.
235
+
236
+ Precondition: ``level_to_ir`` must contain at least ``"O0"`` and ``"O2"``
237
+ keys — if either is absent the function returns an empty list with no
238
+ diagnostic. The CLI always satisfies this; library callers must ensure it.
239
+ """
240
+ findings: list[dict] = []
241
+ if "O0" not in level_to_ir or "O2" not in level_to_ir:
242
+ return findings
243
+
244
+ o0_file, o0_text = level_to_ir["O0"]
245
+ o2_file, o2_text = level_to_ir["O2"]
246
+
247
+ # --- 1. Global volatile store count drop O0 → O2 ---
248
+ o0_vol_count = count_volatile_stores(o0_text)
249
+ o2_vol_count = count_volatile_stores(o2_text)
250
+
251
+ if o0_vol_count > o2_vol_count:
252
+ diff = o0_vol_count - o2_vol_count
253
+ # line=0 is used for file-level findings that cannot be attributed to a
254
+ # single source line (I18). Downstream consumers should treat line 0
255
+ # as "file-level / unknown line".
256
+ findings.append(
257
+ make_finding(
258
+ "OPTIMIZED_AWAY_ZEROIZE",
259
+ "high",
260
+ f"Volatile store count dropped from {o0_vol_count} (O0) to {o2_vol_count} (O2) "
261
+ f"— {diff} volatile wipe(s) eliminated by dead-store elimination",
262
+ o2_file,
263
+ 0,
264
+ )
265
+ )
266
+
267
+ # --- 1b. Per-target volatile store drop O0 -> O2 (hard evidence by symbol) ---
268
+ o0_vol_by_target = extract_volatile_stores_by_target(o0_text)
269
+ o2_vol_by_target = extract_volatile_stores_by_target(o2_text)
270
+ for target, o0_count in sorted(o0_vol_by_target.items()):
271
+ o2_count = o2_vol_by_target.get(target, 0)
272
+ if o0_count > o2_count:
273
+ findings.append(
274
+ make_finding(
275
+ "OPTIMIZED_AWAY_ZEROIZE",
276
+ "high",
277
+ f"Volatile stores to %{target} dropped from {o0_count} (O0) to {o2_count} (O2) "
278
+ f"— symbol-specific wipe elimination detected",
279
+ o2_file,
280
+ 0,
281
+ symbol=target,
282
+ )
283
+ )
284
+
285
+ # --- 2. Non-volatile llvm.memset calls in O2 IR ---
286
+ for lineno, line_text in find_nonvolatile_memsets(o2_text):
287
+ findings.append(
288
+ make_finding(
289
+ "OPTIMIZED_AWAY_ZEROIZE",
290
+ "high",
291
+ f"Non-volatile @llvm.memset in O2 IR — DSE-eligible, may be removed at higher "
292
+ f"optimization. Use zeroize crate or volatile memset. IR: {line_text[:80]}",
293
+ o2_file,
294
+ lineno,
295
+ )
296
+ )
297
+
298
+ # --- 3. alloca with lifetime.end but no volatile store (STACK_RETENTION) ---
299
+ o2_allocas = extract_allocas(o2_text)
300
+ o2_lifetime_ends = extract_lifetime_ends(o2_text)
301
+ o2_vol_targets = extract_volatile_store_targets(o2_text)
302
+
303
+ for alloca_name, size in o2_allocas.items():
304
+ if size not in SECRET_ALLOCA_SIZES:
305
+ continue
306
+ if alloca_name not in o2_lifetime_ends:
307
+ continue
308
+ if alloca_name in o2_vol_targets:
309
+ continue
310
+ findings.append(
311
+ make_finding(
312
+ "STACK_RETENTION",
313
+ "high",
314
+ f"alloca [{size} x i8] %{alloca_name} has @llvm.lifetime.end but no "
315
+ "volatile store — stack bytes not wiped before slot is freed",
316
+ o2_file,
317
+ 0,
318
+ symbol=alloca_name,
319
+ )
320
+ )
321
+
322
+ # --- 4. SROA/mem2reg: secret alloca present at O0 but absent at O2 ---
323
+ o0_allocas = extract_allocas(o0_text)
324
+
325
+ o0_vol_targets = extract_volatile_store_targets(o0_text)
326
+ for alloca_name, size in o0_allocas.items():
327
+ if size not in SECRET_ALLOCA_SIZES:
328
+ continue
329
+ if alloca_name in o2_allocas:
330
+ continue
331
+ # Hard evidence gate: only emit when O0 showed a wipe target on this alloca.
332
+ if alloca_name not in o0_vol_targets:
333
+ continue
334
+ findings.append(
335
+ make_finding(
336
+ "OPTIMIZED_AWAY_ZEROIZE",
337
+ "high",
338
+ f"alloca [{size} x i8] %{alloca_name} present at O0 but absent at O2 — "
339
+ "SROA/mem2reg promoted it to registers; any volatile stores targeting this "
340
+ "alloca are now unreachable",
341
+ o2_file,
342
+ 0,
343
+ symbol=alloca_name,
344
+ )
345
+ )
346
+
347
+ # --- 5. Secret value in argument registers at call site (REGISTER_SPILL) ---
348
+ for lineno, varname, callee in find_arg_load_calls(o2_text):
349
+ findings.append(
350
+ make_finding(
351
+ "REGISTER_SPILL",
352
+ "medium",
353
+ f"Secret-named SSA value '%{varname}' loaded and passed directly to "
354
+ f"'{callee}' — value in argument register may not be cleared after call",
355
+ o2_file,
356
+ lineno,
357
+ symbol=varname,
358
+ )
359
+ )
360
+
361
+ # --- 6. Secret return values can persist in return registers ---
362
+ for lineno, varname in find_secret_returns(o2_text):
363
+ findings.append(
364
+ make_finding(
365
+ "REGISTER_SPILL",
366
+ "medium",
367
+ f"Secret-named SSA value '%{varname}' is returned directly — "
368
+ "value may persist in return registers after function exit",
369
+ o2_file,
370
+ lineno,
371
+ symbol=varname,
372
+ )
373
+ )
374
+
375
+ # --- 7. Aggregate/by-value secret argument passing ---
376
+ for lineno, snippet in find_secret_aggregate_passes(o2_text):
377
+ findings.append(
378
+ make_finding(
379
+ "SECRET_COPY",
380
+ "medium",
381
+ "Potential by-value aggregate call argument contains secret-named data; "
382
+ f"copy may escape zeroization tracking. Args: {snippet}",
383
+ o2_file,
384
+ lineno,
385
+ )
386
+ )
387
+
388
+ # Collect targets already reported in section 1b (O0→O2 per-symbol comparison)
389
+ # so that the multi-level section below does not re-emit the same target.
390
+ reported_by_1b: set[str] = {
391
+ target
392
+ for target, o0_count in o0_vol_by_target.items()
393
+ if o0_count > o2_vol_by_target.get(target, 0)
394
+ }
395
+
396
+ # --- 8. Optional multi-level comparison (O0->O1->O2, O2->O3) ---
397
+ # Skip the (O0, O2) adjacent pair when O1 is absent — that comparison is already
398
+ # done by sections 1 and 1b above, and re-emitting it here causes duplicate findings.
399
+ level_order = ["O0", "O1", "O2", "O3"]
400
+ present = [lvl for lvl in level_order if lvl in level_to_ir]
401
+ for idx in range(len(present) - 1):
402
+ from_level = present[idx]
403
+ to_level = present[idx + 1]
404
+ # O0→O2 without an intermediate O1 is already covered by sections 1/1b.
405
+ if from_level == "O0" and to_level == "O2":
406
+ continue
407
+ _, from_ir = level_to_ir[from_level]
408
+ to_file, to_ir = level_to_ir[to_level]
409
+ from_targets = extract_volatile_stores_by_target(from_ir)
410
+ to_targets = extract_volatile_stores_by_target(to_ir)
411
+ for target, from_count in sorted(from_targets.items()):
412
+ # Skip targets already covered by section 1b to avoid cascading duplicates.
413
+ if target in reported_by_1b:
414
+ continue
415
+ to_count = to_targets.get(target, 0)
416
+ if from_count > to_count:
417
+ findings.append(
418
+ make_finding(
419
+ "OPTIMIZED_AWAY_ZEROIZE",
420
+ "high",
421
+ f"Volatile stores to %{target} dropped from {from_count} ({from_level}) "
422
+ f"to {to_count} ({to_level})",
423
+ to_file,
424
+ 0,
425
+ symbol=target,
426
+ )
427
+ )
428
+
429
+ return findings
430
+
431
+
432
+ # ---------------------------------------------------------------------------
433
+ # CLI
434
+ # ---------------------------------------------------------------------------
435
+
436
+
437
+ def main() -> int:
438
+ parser = argparse.ArgumentParser(
439
+ description="LLVM IR O0 vs O2 comparison for Rust dead-store-elimination findings"
440
+ )
441
+ parser.add_argument("--o0", required=True, help="Path to O0 .ll file")
442
+ parser.add_argument("--o2", required=True, help="Path to O2 .ll file")
443
+ parser.add_argument("--o1", required=False, help="Path to O1 .ll file (optional)")
444
+ parser.add_argument("--o3", required=False, help="Path to O3 .ll file (optional)")
445
+ parser.add_argument("--out", required=True, help="Output findings JSON path")
446
+ args = parser.parse_args()
447
+
448
+ level_paths: dict[str, Path] = {
449
+ "O0": Path(args.o0),
450
+ "O2": Path(args.o2),
451
+ }
452
+ if args.o1:
453
+ level_paths["O1"] = Path(args.o1)
454
+ if args.o3:
455
+ level_paths["O3"] = Path(args.o3)
456
+
457
+ for p in level_paths.values():
458
+ if not p.exists():
459
+ print(f"check_llvm_patterns.py: IR file not found: {p}", file=sys.stderr)
460
+ return 1
461
+
462
+ level_to_ir: dict[str, tuple[str, str]] = {}
463
+ try:
464
+ for level, path in level_paths.items():
465
+ level_to_ir[level] = (str(path), path.read_text(encoding="utf-8", errors="replace"))
466
+ except OSError as e:
467
+ print(f"check_llvm_patterns.py: failed to read IR: {e}", file=sys.stderr)
468
+ return 1
469
+
470
+ findings = analyze(level_to_ir)
471
+
472
+ out_path = Path(args.out)
473
+ out_path.parent.mkdir(parents=True, exist_ok=True)
474
+ out_path.write_text(json.dumps(findings, indent=2), encoding="utf-8")
475
+
476
+ print(f"check_llvm_patterns.py: {len(findings)} finding(s) written to {out_path}")
477
+ return 0
478
+
479
+
480
+ if __name__ == "__main__":
481
+ sys.exit(main())