@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,554 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # requires-python = ">=3.11"
4
+ # dependencies = []
5
+ # ///
6
+ """
7
+ check_mir_patterns.py — MIR text pattern analysis for Rust zeroization issues.
8
+
9
+ Reads a Rust MIR file (emitted by emit_rust_mir.sh) and a sensitive-objects JSON
10
+ file, then detects patterns indicative of missing or incorrect zeroization.
11
+
12
+ All analysis is text/regex based — no MIR parser required.
13
+
14
+ Usage:
15
+ uv run check_mir_patterns.py \
16
+ --mir <path.mir> --secrets <sensitive-objects.json> --out <findings.json>
17
+
18
+ Exit codes:
19
+ 0 — ran successfully (findings may be empty)
20
+ 1 — input file not found
21
+ 2 — argument error
22
+ """
23
+
24
+ import argparse
25
+ import json
26
+ import re
27
+ import sys
28
+ from pathlib import Path
29
+
30
+ # ---------------------------------------------------------------------------
31
+ # Sensitive name patterns (applied to local variable names in MIR)
32
+ # ---------------------------------------------------------------------------
33
+
34
+ SENSITIVE_LOCAL_RE = re.compile(
35
+ # Match keyword not preceded/followed by a letter so that compound names
36
+ # like 'secret_key', 'private_key', and 'auth_token' are correctly matched
37
+ # while avoiding spurious hits on words like 'monkey' or 'tokenize'.
38
+ r"(?i)(?<![a-zA-Z])(key|secret|password|token|nonce|seed|priv|master|credential)(?![a-zA-Z])"
39
+ )
40
+
41
+ # ---------------------------------------------------------------------------
42
+ # Finding counter
43
+ # ---------------------------------------------------------------------------
44
+
45
+ _finding_counter = [0]
46
+
47
+
48
+ def make_finding(
49
+ category: str,
50
+ severity: str,
51
+ detail: str,
52
+ file: str,
53
+ line: int,
54
+ symbol: str = "",
55
+ confidence: str = "likely",
56
+ ) -> dict:
57
+ _finding_counter[0] += 1
58
+ fid = f"F-RUST-MIR-{_finding_counter[0]:04d}"
59
+ return {
60
+ "id": fid,
61
+ "language": "rust",
62
+ "category": category,
63
+ "severity": severity,
64
+ "confidence": confidence,
65
+ "detail": detail,
66
+ "symbol": symbol,
67
+ "location": {"file": file, "line": line},
68
+ "evidence": [{"source": "mir_text", "detail": detail}],
69
+ }
70
+
71
+
72
+ # ---------------------------------------------------------------------------
73
+ # MIR parsing helpers
74
+ # ---------------------------------------------------------------------------
75
+
76
+
77
+ def split_into_functions(mir_text: str) -> list[tuple[str, list[str], int]]:
78
+ """
79
+ Split MIR text into (fn_name, body_lines, start_lineno) tuples.
80
+ MIR functions start with 'fn <name>' or 'mir_body' headers.
81
+ """
82
+ functions: list[tuple[str, list[str], int]] = []
83
+ lines = mir_text.splitlines()
84
+ fn_re = re.compile(r"^fn\s+(\S+)\s*\(")
85
+ current_name = "<top>"
86
+ current_lines: list[str] = []
87
+ current_start = 0
88
+ depth = 0
89
+
90
+ for lineno, line in enumerate(lines, start=1):
91
+ m = fn_re.match(line.strip())
92
+ if m and depth == 0:
93
+ if current_lines:
94
+ functions.append((current_name, current_lines, current_start))
95
+ current_name = m.group(1)
96
+ current_lines = [line]
97
+ current_start = lineno
98
+ depth = line.count("{") - line.count("}")
99
+ else:
100
+ current_lines.append(line)
101
+ depth += line.count("{") - line.count("}")
102
+ if depth < 0:
103
+ print(
104
+ f"check_mir_patterns.py: warning: negative brace depth at line {lineno} "
105
+ f"in {current_name!r} — MIR may be malformed",
106
+ file=sys.stderr,
107
+ )
108
+ depth = 0
109
+
110
+ if current_lines:
111
+ functions.append((current_name, current_lines, current_start))
112
+
113
+ return functions
114
+
115
+
116
+ def local_names_from_debug_info(fn_lines: list[str]) -> dict[str, str]:
117
+ """
118
+ Extract MIR debug variable map: local slot → variable name.
119
+ MIR debug lines look like: debug varname => _5;
120
+ """
121
+ mapping: dict[str, str] = {}
122
+ debug_re = re.compile(r"debug\s+(\w+)\s*=>\s*(_\d+)")
123
+ for line in fn_lines:
124
+ m = debug_re.search(line)
125
+ if m:
126
+ varname, slot = m.group(1), m.group(2)
127
+ mapping[slot] = varname
128
+ return mapping
129
+
130
+
131
+ def is_sensitive_local(
132
+ slot: str, debug_map: dict[str, str], sensitive_re: re.Pattern[str] = SENSITIVE_LOCAL_RE
133
+ ) -> bool:
134
+ varname = debug_map.get(slot, "")
135
+ return bool(sensitive_re.search(varname))
136
+
137
+
138
+ def is_zeroizing_type(type_name: str) -> bool:
139
+ return bool(re.search(r"(?i)(Zeroiz|ZeroizeOnDrop|SecretBox|Zeroizing)", type_name))
140
+
141
+
142
+ # ---------------------------------------------------------------------------
143
+ # Pattern detectors
144
+ # ---------------------------------------------------------------------------
145
+
146
+
147
+ def detect_drop_before_storagedead(
148
+ fn_name: str,
149
+ fn_lines: list[str],
150
+ fn_start: int,
151
+ debug_map: dict[str, str],
152
+ mir_file: str,
153
+ sensitive_re: re.Pattern[str] = SENSITIVE_LOCAL_RE,
154
+ ) -> list[dict]:
155
+ """
156
+ Pattern: drop(_X) present but StorageDead(_X) absent for any sensitive local.
157
+ Indicates the local may persist on stack after the drop.
158
+ """
159
+ findings: list[dict] = []
160
+ drop_re = re.compile(r"\bdrop\(_(\d+)\)")
161
+ storagedead_re = re.compile(r"StorageDead\(_(\d+)\)")
162
+
163
+ dropped: set[str] = set()
164
+ storage_dead: set[str] = set()
165
+
166
+ for line in fn_lines:
167
+ for m in drop_re.finditer(line):
168
+ dropped.add(f"_{m.group(1)}")
169
+ for m in storagedead_re.finditer(line):
170
+ storage_dead.add(f"_{m.group(1)}")
171
+
172
+ has_return = any(re.search(r"\breturn\b", line) for line in fn_lines)
173
+
174
+ for slot in dropped - storage_dead:
175
+ if not is_sensitive_local(slot, debug_map, sensitive_re):
176
+ continue
177
+ if has_return:
178
+ # Prefer the path-sensitive NOT_ON_ALL_PATHS finding over the generic
179
+ # MISSING_SOURCE_ZEROIZE to avoid emitting duplicate findings for the
180
+ # same slot (C7: both fired before for slots with explicit return paths).
181
+ findings.append(
182
+ make_finding(
183
+ "NOT_ON_ALL_PATHS",
184
+ "high",
185
+ f"Secret local {slot} ({debug_map.get(slot, '?')!r}) is dropped but not "
186
+ f"StorageDead on explicit return path(s) in '{fn_name}'",
187
+ mir_file,
188
+ fn_start,
189
+ symbol=debug_map.get(slot, slot),
190
+ )
191
+ )
192
+ else:
193
+ findings.append(
194
+ make_finding(
195
+ "MISSING_SOURCE_ZEROIZE",
196
+ "medium",
197
+ f"Secret local {slot} ({debug_map.get(slot, '?')!r}) is dropped without "
198
+ f"StorageDead in '{fn_name}' — verify zeroize call in drop glue",
199
+ mir_file,
200
+ fn_start,
201
+ symbol=debug_map.get(slot, slot),
202
+ )
203
+ )
204
+
205
+ return findings
206
+
207
+
208
+ def detect_resume_with_live_secrets(
209
+ fn_name: str,
210
+ fn_lines: list[str],
211
+ fn_start: int,
212
+ debug_map: dict[str, str],
213
+ mir_file: str,
214
+ sensitive_re: re.Pattern[str] = SENSITIVE_LOCAL_RE,
215
+ ) -> list[dict]:
216
+ """
217
+ Pattern: 'resume' terminator (unwind/panic path) with sensitive locals in scope.
218
+ """
219
+ findings: list[dict] = []
220
+ resume_re = re.compile(r"\bresume\b")
221
+ has_resume = any(resume_re.search(line) for line in fn_lines)
222
+ if not has_resume:
223
+ return findings
224
+
225
+ sensitive_locals = [
226
+ slot for slot in debug_map if is_sensitive_local(slot, debug_map, sensitive_re)
227
+ ]
228
+ if sensitive_locals:
229
+ names = [debug_map[s] for s in sensitive_locals[:3]]
230
+ findings.append(
231
+ make_finding(
232
+ "MISSING_SOURCE_ZEROIZE",
233
+ "medium",
234
+ f"Panic/unwind path (resume) in '{fn_name}' with sensitive "
235
+ f"locals {names} in scope — verify these locals are dropped "
236
+ "(and zeroed) on the unwind path",
237
+ mir_file,
238
+ fn_start,
239
+ symbol=names[0] if names else "",
240
+ )
241
+ )
242
+ return findings
243
+
244
+
245
+ def detect_aggregate_move_non_zeroizing(
246
+ fn_name: str,
247
+ fn_lines: list[str],
248
+ fn_start: int,
249
+ debug_map: dict[str, str],
250
+ mir_file: str,
251
+ sensitive_re: re.Pattern[str] = SENSITIVE_LOCAL_RE,
252
+ ) -> list[dict]:
253
+ """
254
+ Pattern: _Y = TypeName { field: move _X } where _X is a sensitive local
255
+ and TypeName does not appear to be a Zeroizing wrapper.
256
+ """
257
+ findings: list[dict] = []
258
+ agg_re = re.compile(r"(_\d+)\s*=\s*(\w[\w:]*)\s*\{[^}]*move\s+(_\d+)")
259
+
260
+ for lineno, line in enumerate(fn_lines, start=fn_start):
261
+ m = agg_re.search(line)
262
+ if not m:
263
+ continue
264
+ _dest, type_name, _src = m.group(1), m.group(2), m.group(3)
265
+ if is_sensitive_local(_src, debug_map, sensitive_re) and not is_zeroizing_type(type_name):
266
+ src_name = debug_map.get(_src, _src)
267
+ findings.append(
268
+ make_finding(
269
+ "SECRET_COPY",
270
+ "medium",
271
+ f"Secret local '{src_name}' moved into non-Zeroizing aggregate '{type_name}' "
272
+ f"in '{fn_name}' — copy now untracked",
273
+ mir_file,
274
+ lineno,
275
+ symbol=src_name,
276
+ )
277
+ )
278
+ return findings
279
+
280
+
281
+ def detect_closure_capture_secret(
282
+ fn_name: str,
283
+ fn_lines: list[str],
284
+ fn_start: int,
285
+ debug_map: dict[str, str],
286
+ mir_file: str,
287
+ sensitive_re: re.Pattern[str] = SENSITIVE_LOCAL_RE,
288
+ ) -> list[dict]:
289
+ """
290
+ Pattern: closure/async state captures a sensitive local by move.
291
+ """
292
+ findings: list[dict] = []
293
+ closure_re = re.compile(
294
+ r"(_\d+)\s*=\s*.*(?:closure|async|generator|Coroutine).*move\s+(_\d+)",
295
+ re.IGNORECASE,
296
+ )
297
+ for lineno, line in enumerate(fn_lines, start=fn_start):
298
+ m = closure_re.search(line)
299
+ if not m:
300
+ continue
301
+ captured_slot = m.group(2)
302
+ if is_sensitive_local(captured_slot, debug_map, sensitive_re):
303
+ name = debug_map.get(captured_slot, captured_slot)
304
+ findings.append(
305
+ make_finding(
306
+ "SECRET_COPY",
307
+ "high",
308
+ f"Sensitive local '{name}' is captured by move into a closure/async state "
309
+ f"in '{fn_name}' — copy may outlive intended wipe scope",
310
+ mir_file,
311
+ lineno,
312
+ symbol=name,
313
+ )
314
+ )
315
+ return findings
316
+
317
+
318
+ def detect_drop_glue_without_zeroize(
319
+ fn_name: str, fn_lines: list[str], fn_start: int, mir_file: str
320
+ ) -> list[dict]:
321
+ """
322
+ Pattern: function is a drop glue (drop_in_place / _drop_impl) and contains
323
+ drop(_X) but no call to zeroize::.
324
+ """
325
+ if not re.search(r"(drop_in_place|_drop_impl)", fn_name):
326
+ return []
327
+
328
+ findings: list[dict] = []
329
+ has_drop_call = any(re.search(r"\bdrop\(_\d+\)", line) for line in fn_lines)
330
+ has_zeroize_call = any(re.search(r"\bzeroize::", line) for line in fn_lines)
331
+
332
+ if has_drop_call and not has_zeroize_call:
333
+ findings.append(
334
+ make_finding(
335
+ "MISSING_SOURCE_ZEROIZE",
336
+ "high",
337
+ f"Drop glue '{fn_name}' calls drop() but no call to zeroize:: found — "
338
+ "secret not wiped on drop",
339
+ mir_file,
340
+ fn_start,
341
+ symbol=fn_name,
342
+ )
343
+ )
344
+ return findings
345
+
346
+
347
+ def detect_ffi_call_with_secret(
348
+ fn_name: str,
349
+ fn_lines: list[str],
350
+ fn_start: int,
351
+ debug_map: dict[str, str],
352
+ mir_file: str,
353
+ sensitive_re: re.Pattern[str] = SENSITIVE_LOCAL_RE,
354
+ ) -> list[dict]:
355
+ """
356
+ Pattern: extern "C" call with a sensitive local as an argument.
357
+ In MIR: extern fns are called with ABI specifier; we look for
358
+ 'extern "C"' in fn declaration context and call sites with sensitive locals.
359
+ """
360
+ findings: list[dict] = []
361
+ call_re = re.compile(r"\bcall\s+(\S+)\s*\(([^)]*)\)")
362
+
363
+ # In MIR, extern fn calls appear as calls to paths containing "extern_C" or similar.
364
+ # Heuristic: look for call sites that pass a sensitive local as an argument.
365
+ for lineno, line in enumerate(fn_lines, start=fn_start):
366
+ m = call_re.search(line)
367
+ if not m:
368
+ continue
369
+ callee = m.group(1)
370
+ args_text = m.group(2)
371
+ # Check if any argument is a sensitive local
372
+ arg_slots = re.findall(r"_(\d+)", args_text)
373
+ for slot_num in arg_slots:
374
+ slot = f"_{slot_num}"
375
+ if is_sensitive_local(slot, debug_map, sensitive_re):
376
+ # Check if the callee looks like an FFI function (not zeroize::)
377
+ if "zeroize" in callee.lower():
378
+ continue
379
+ # Look for extern "C" indication — either in callee name or nearby
380
+ if re.search(r"(::c_|_ffi_|_sys_|extern)", callee, re.IGNORECASE):
381
+ src_name = debug_map.get(slot, slot)
382
+ findings.append(
383
+ make_finding(
384
+ "SECRET_COPY",
385
+ "high",
386
+ f"Secret local '{src_name}' passed to potential FFI call '{callee}' "
387
+ f"in '{fn_name}' — zeroization guarantees lost in callee",
388
+ mir_file,
389
+ lineno,
390
+ symbol=src_name,
391
+ )
392
+ )
393
+ return findings
394
+
395
+
396
+ def detect_yield_with_live_secret(
397
+ fn_name: str,
398
+ fn_lines: list[str],
399
+ fn_start: int,
400
+ debug_map: dict[str, str],
401
+ mir_file: str,
402
+ sensitive_re: re.Pattern[str] = SENSITIVE_LOCAL_RE,
403
+ ) -> list[dict]:
404
+ """
405
+ Pattern: Yield terminator (async/coroutine state machine) with sensitive-named
406
+ locals that could be live at the yield point.
407
+ """
408
+ findings: list[dict] = []
409
+ yield_re = re.compile(r"\byield\b")
410
+ has_yield = any(yield_re.search(line) for line in fn_lines)
411
+ if not has_yield:
412
+ return findings
413
+
414
+ sensitive_locals = [
415
+ slot for slot in debug_map if is_sensitive_local(slot, debug_map, sensitive_re)
416
+ ]
417
+ if sensitive_locals:
418
+ names = [debug_map[s] for s in sensitive_locals[:3]]
419
+ findings.append(
420
+ make_finding(
421
+ "NOT_ON_ALL_PATHS",
422
+ "high",
423
+ f"Coroutine/async fn '{fn_name}' has Yield terminator with sensitive locals "
424
+ f"{names} potentially live at suspension point — secrets stored in heap-allocated "
425
+ "Future state machine; ZeroizeOnDrop covers stack variables only",
426
+ mir_file,
427
+ fn_start,
428
+ symbol=names[0] if names else "",
429
+ )
430
+ )
431
+ return findings
432
+
433
+
434
+ def detect_result_err_path_with_secret(
435
+ fn_name: str,
436
+ fn_lines: list[str],
437
+ fn_start: int,
438
+ debug_map: dict[str, str],
439
+ mir_file: str,
440
+ sensitive_re: re.Pattern[str] = SENSITIVE_LOCAL_RE,
441
+ ) -> list[dict]:
442
+ """
443
+ Pattern: explicit error-path style return (`Err(...)`) while sensitive locals
444
+ are still in scope.
445
+ """
446
+ findings: list[dict] = []
447
+ err_re = re.compile(r"\bErr\s*\(")
448
+ if not any(err_re.search(line) for line in fn_lines):
449
+ return findings
450
+ sensitive_locals = [
451
+ slot for slot in debug_map if is_sensitive_local(slot, debug_map, sensitive_re)
452
+ ]
453
+ if not sensitive_locals:
454
+ return findings
455
+ names = [debug_map[s] for s in sensitive_locals[:3]]
456
+ findings.append(
457
+ make_finding(
458
+ "NOT_ON_ALL_PATHS",
459
+ "high",
460
+ f"Potential Result::Err early-return path in '{fn_name}' with sensitive locals {names} "
461
+ "still in scope — verify cleanup on all error exits",
462
+ mir_file,
463
+ fn_start,
464
+ symbol=names[0] if names else "",
465
+ )
466
+ )
467
+ return findings
468
+
469
+
470
+ # ---------------------------------------------------------------------------
471
+ # Main analysis
472
+ # ---------------------------------------------------------------------------
473
+
474
+
475
+ def analyze(mir_text: str, sensitive_objects: list[dict], mir_file: str) -> list[dict]:
476
+ findings: list[dict] = []
477
+ functions = split_into_functions(mir_text)
478
+
479
+ extra_names = [obj.get("name", "") for obj in sensitive_objects if obj.get("name")]
480
+ sensitive_re = SENSITIVE_LOCAL_RE
481
+ if extra_names:
482
+ augmented = (
483
+ SENSITIVE_LOCAL_RE.pattern
484
+ + "|"
485
+ + "|".join(r"\b" + re.escape(n) + r"\b" for n in extra_names)
486
+ )
487
+ sensitive_re = re.compile(augmented, re.IGNORECASE)
488
+
489
+ for fn_name, fn_lines, fn_start in functions:
490
+ debug_map = local_names_from_debug_info(fn_lines)
491
+
492
+ ctx = (fn_name, fn_lines, fn_start)
493
+ findings.extend(detect_drop_before_storagedead(*ctx, debug_map, mir_file, sensitive_re))
494
+ findings.extend(detect_resume_with_live_secrets(*ctx, debug_map, mir_file, sensitive_re))
495
+ findings.extend(
496
+ detect_aggregate_move_non_zeroizing(*ctx, debug_map, mir_file, sensitive_re)
497
+ )
498
+ findings.extend(detect_closure_capture_secret(*ctx, debug_map, mir_file, sensitive_re))
499
+ findings.extend(detect_drop_glue_without_zeroize(*ctx, mir_file))
500
+ findings.extend(detect_ffi_call_with_secret(*ctx, debug_map, mir_file, sensitive_re))
501
+ findings.extend(detect_yield_with_live_secret(*ctx, debug_map, mir_file, sensitive_re))
502
+ findings.extend(detect_result_err_path_with_secret(*ctx, debug_map, mir_file, sensitive_re))
503
+
504
+ return findings
505
+
506
+
507
+ # ---------------------------------------------------------------------------
508
+ # CLI
509
+ # ---------------------------------------------------------------------------
510
+
511
+
512
+ def main() -> int:
513
+ parser = argparse.ArgumentParser(
514
+ description="MIR text pattern analysis for Rust zeroization issues"
515
+ )
516
+ parser.add_argument("--mir", required=True, help="Path to .mir file")
517
+ parser.add_argument("--secrets", required=True, help="Path to sensitive-objects.json")
518
+ parser.add_argument("--out", required=True, help="Output findings JSON path")
519
+ args = parser.parse_args()
520
+
521
+ mir_path = Path(args.mir)
522
+ if not mir_path.exists():
523
+ print(f"check_mir_patterns.py: MIR file not found: {mir_path}", file=sys.stderr)
524
+ return 1
525
+
526
+ secrets_path = Path(args.secrets)
527
+ if not secrets_path.exists():
528
+ print(f"check_mir_patterns.py: secrets file not found: {secrets_path}", file=sys.stderr)
529
+ return 1
530
+
531
+ try:
532
+ mir_text = mir_path.read_text(encoding="utf-8", errors="replace")
533
+ except OSError as e:
534
+ print(f"check_mir_patterns.py: failed to read MIR: {e}", file=sys.stderr)
535
+ return 1
536
+
537
+ try:
538
+ sensitive_objects = json.loads(secrets_path.read_text(encoding="utf-8", errors="replace"))
539
+ except (json.JSONDecodeError, OSError) as e:
540
+ print(f"check_mir_patterns.py: failed to parse secrets JSON: {e}", file=sys.stderr)
541
+ return 1
542
+
543
+ findings = analyze(mir_text, sensitive_objects, str(mir_path))
544
+
545
+ out_path = Path(args.out)
546
+ out_path.parent.mkdir(parents=True, exist_ok=True)
547
+ out_path.write_text(json.dumps(findings, indent=2), encoding="utf-8")
548
+
549
+ print(f"check_mir_patterns.py: {len(findings)} finding(s) written to {out_path}")
550
+ return 0
551
+
552
+
553
+ if __name__ == "__main__":
554
+ sys.exit(main())