@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,550 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Validate phase output before marking complete in audit-state.json.
4
+
5
+ Usage: validate_phase_output.py <phase_number> <security_dir>
6
+ Exit 0: validation passed
7
+ Exit 1: validation failed (prints reasons)
8
+ Exit 2: usage error
9
+ """
10
+
11
+ import os
12
+ import sys
13
+
14
+ # All phase outputs write into archon/attack-surface/knowledge-base-report.md (phases 1-9)
15
+ # or archon/final-audit-report.md (phase 15). Checks verify KB sections and CodeQL artifacts.
16
+ #
17
+ # "kb_sections" entries are literal strings that must appear in knowledge-base-report.md.
18
+ # "files" entries are non-KB artifact files that must exist (relative to security_dir).
19
+ ATTACK_SURFACE_DIR = "attack-surface"
20
+ KB_FILE = f"{ATTACK_SURFACE_DIR}/knowledge-base-report.md"
21
+
22
+ # Files that may legitimately appear inside archon/attack-surface/ — anything else there is an orphan.
23
+ KNOWN_ATTACK_SURFACE_FILES: set[str] = {
24
+ "knowledge-base-report.md",
25
+ "authz-matrix.md",
26
+ "authz-coverage-gaps.md",
27
+ "cross-service-edges.json",
28
+ "cross-service-edges.md",
29
+ "commit-recon-report.md",
30
+ "lite-recon.md",
31
+ # Legacy / merge-mode passthrough names:
32
+ "advisory-report.md",
33
+ "spec-gap-report.md",
34
+ "sast-results.md",
35
+ "sast-summary.md",
36
+ "enrichment-report.md",
37
+ "enrichment-summary.md",
38
+ }
39
+
40
+ PHASE_REQUIREMENTS = {
41
+ 1: {
42
+ "files": [KB_FILE],
43
+ "kb_sections": ["Advisory Intelligence"],
44
+ },
45
+ 2: {
46
+ "files": [KB_FILE],
47
+ "kb_sections": ["Bypass Analysis"],
48
+ },
49
+ 3: {
50
+ "files": [KB_FILE],
51
+ "kb_sections": [
52
+ "Project Classification",
53
+ "Trust Boundaries",
54
+ "DFD",
55
+ "Threat Model",
56
+ "Attack Surface",
57
+ "Domain Attack Research",
58
+ ],
59
+ },
60
+ 4: {
61
+ "files": [
62
+ KB_FILE,
63
+ "codeql-artifacts/entry-points.json",
64
+ "codeql-artifacts/sinks.json",
65
+ "codeql-artifacts/call-graph-slices.json",
66
+ "codeql-artifacts/flow-paths-all-severities.md",
67
+ ],
68
+ "kb_sections": ["Static Analysis Summary", "CodeQL Structural Analysis", "SAST Enrichment"],
69
+ },
70
+ 9: {
71
+ # Spec Gap Analysis section must exist (may be "None identified" if no specs found)
72
+ "files": [KB_FILE],
73
+ "kb_sections": ["Spec Gap Analysis"],
74
+ },
75
+ 10: {
76
+ "files": [],
77
+ "kb_sections": [],
78
+ "findings_draft_required": True,
79
+ "kb_addendum_required": True,
80
+ "chamber_workspace_required": True,
81
+ "attack_pattern_registry_required": True,
82
+ },
83
+ 11: {
84
+ "files": [],
85
+ "kb_sections": [],
86
+ "verdict_in_drafts_required": True,
87
+ "adversarial_review_required": True, # P11-LITE: only for CRITICAL/HIGH
88
+ },
89
+ 12: {
90
+ "files": [],
91
+ "kb_sections": [],
92
+ "findings_draft_required": True,
93
+ },
94
+ 15: {
95
+ "files": ["final-audit-report.md"],
96
+ "kb_sections": [],
97
+ "sections": {
98
+ "final-audit-report.md": [
99
+ "Executive Summary",
100
+ "Methodology",
101
+ "Summary of Findings",
102
+ "Conclusion",
103
+ ],
104
+ },
105
+ },
106
+ }
107
+
108
+ # Files that must NOT exist in archon/ (consolidated into knowledge-base-report.md)
109
+ STALE_FILES = [
110
+ "cve-scout-report.md",
111
+ "bypass-analysis-report.md",
112
+ "threat-model-report.md",
113
+ "attack-surface-report.md",
114
+ "static-analysis-report.md",
115
+ "actions-audit-report.md",
116
+ "spec-gaps-report.md",
117
+ "final-findings-report.md",
118
+ ]
119
+
120
+ VERDICT_MARKERS = ("VALID", "FALSE POSITIVE", "BY DESIGN", "OUT OF SCOPE", "FALSE POSITIVE (adversarial)")
121
+
122
+
123
+ def check_findings_draft(security_dir: str) -> tuple[bool, str]:
124
+ draft_dir = os.path.join(security_dir, "findings-draft")
125
+ if not os.path.isdir(draft_dir):
126
+ return False, "findings-draft/ directory does not exist"
127
+ entries = [f for f in os.listdir(draft_dir) if f.endswith(".md")]
128
+ if not entries:
129
+ return False, "findings-draft/ is empty — no findings were persisted to disk"
130
+ return True, ""
131
+
132
+
133
+ def check_kb_addendum(security_dir: str) -> tuple[bool, str]:
134
+ kb_path = os.path.join(security_dir, KB_FILE)
135
+ if not os.path.isfile(kb_path):
136
+ return False, f"{KB_FILE} not found for addendum check"
137
+ try:
138
+ content = open(kb_path).read()
139
+ except OSError as e:
140
+ return False, f"Could not read {KB_FILE}: {e}"
141
+ if "Phase 10 Addendum" not in content:
142
+ return False, f"{KB_FILE} missing '## Phase 10 Addendum' section"
143
+ return True, ""
144
+
145
+
146
+ def check_chamber_workspace(security_dir: str) -> tuple[bool, str]:
147
+ """Verify chamber-workspace/ exists with at least one chamber containing a CLOSED debate."""
148
+ workspace = os.path.join(security_dir, "chamber-workspace")
149
+ if not os.path.isdir(workspace):
150
+ return False, "chamber-workspace/ directory does not exist — Phase 10 Review Chambers required"
151
+ chambers = [d for d in os.listdir(workspace) if os.path.isdir(os.path.join(workspace, d))]
152
+ if not chambers:
153
+ return False, "chamber-workspace/ is empty — no Review Chambers were created"
154
+ for chamber in chambers:
155
+ debate_path = os.path.join(workspace, chamber, "debate.md")
156
+ if not os.path.isfile(debate_path):
157
+ return False, f"chamber-workspace/{chamber}/debate.md does not exist"
158
+ try:
159
+ content = open(debate_path).read()
160
+ except OSError as e:
161
+ return False, f"Could not read debate.md for {chamber}: {e}"
162
+ if "CLOSED" not in content:
163
+ return False, f"chamber-workspace/{chamber}/debate.md Status is not CLOSED"
164
+ return True, ""
165
+
166
+
167
+ def check_attack_pattern_registry(security_dir: str) -> tuple[bool, str]:
168
+ """Verify attack-pattern-registry.json exists and is valid JSON."""
169
+ import json
170
+ registry_path = os.path.join(security_dir, "attack-pattern-registry.json")
171
+ if not os.path.isfile(registry_path):
172
+ return False, "attack-pattern-registry.json does not exist"
173
+ try:
174
+ data = json.loads(open(registry_path).read())
175
+ except (OSError, json.JSONDecodeError) as e:
176
+ return False, f"attack-pattern-registry.json is invalid: {e}"
177
+ if "patterns" not in data:
178
+ return False, "attack-pattern-registry.json missing 'patterns' key"
179
+ return True, ""
180
+
181
+
182
+ def check_adversarial_reviews(security_dir: str) -> tuple[bool, str]:
183
+ """P11-LITE: adversarial reviews required only for CRITICAL and HIGH VALID findings.
184
+ Medium findings skip Stage 2 (already challenged by Devil's Advocate in chamber debate)."""
185
+ draft_dir = os.path.join(security_dir, "findings-draft")
186
+ reviews_dir = os.path.join(security_dir, "adversarial-reviews")
187
+
188
+ if not os.path.isdir(draft_dir):
189
+ return True, "" # No drafts to check
190
+
191
+ # Find VALID findings that are CRITICAL or HIGH (these need cold verification)
192
+ critical_high_valid = []
193
+ for fname in os.listdir(draft_dir):
194
+ if not fname.endswith(".md"):
195
+ continue
196
+ fpath = os.path.join(draft_dir, fname)
197
+ try:
198
+ content = open(fpath).read()
199
+ except OSError:
200
+ continue
201
+ if "Verdict: VALID" in content:
202
+ # Check if CRITICAL or HIGH severity
203
+ content_upper = content.upper()
204
+ if "SEVERITY-ORIGINAL: CRITICAL" in content_upper or "SEVERITY-ORIGINAL: HIGH" in content_upper:
205
+ critical_high_valid.append(fname)
206
+
207
+ if not critical_high_valid:
208
+ return True, "" # No CRITICAL/HIGH VALID findings, Stage 2 not required
209
+
210
+ # Check that adversarial-reviews/ directory exists and has review files
211
+ if not os.path.isdir(reviews_dir):
212
+ return False, (
213
+ "CRITICAL/HIGH VALID findings exist but archon/adversarial-reviews/ is missing. "
214
+ "P11-LITE Stage 2 cold verification is required for CRITICAL and HIGH findings."
215
+ )
216
+ review_files = [f for f in os.listdir(reviews_dir) if f.endswith(".md")]
217
+ if not review_files:
218
+ return False, (
219
+ "CRITICAL/HIGH VALID findings exist but archon/adversarial-reviews/ is empty. "
220
+ "P11-LITE Stage 2 cold verification is required for CRITICAL and HIGH findings."
221
+ )
222
+
223
+ # Check each CRITICAL/HIGH VALID draft has an Adversarial-Verdict: line
224
+ missing_verdict = []
225
+ for fname in critical_high_valid:
226
+ fpath = os.path.join(draft_dir, fname)
227
+ try:
228
+ content = open(fpath).read()
229
+ except OSError:
230
+ continue
231
+ if "Adversarial-Verdict:" not in content:
232
+ missing_verdict.append(fname)
233
+
234
+ if missing_verdict:
235
+ return False, (
236
+ f"CRITICAL/HIGH VALID findings missing Adversarial-Verdict: {', '.join(missing_verdict)}. "
237
+ "P11-LITE Stage 2 cold verification must write verdicts back into CRITICAL/HIGH drafts."
238
+ )
239
+
240
+ return True, ""
241
+
242
+
243
+ def check_verdict_in_drafts(security_dir: str) -> tuple[bool, str]:
244
+ draft_dir = os.path.join(security_dir, "findings-draft")
245
+ if not os.path.isdir(draft_dir):
246
+ return False, "findings-draft/ directory does not exist"
247
+ for fname in os.listdir(draft_dir):
248
+ if not fname.endswith(".md"):
249
+ continue
250
+ try:
251
+ content = open(os.path.join(draft_dir, fname)).read()
252
+ except OSError:
253
+ continue
254
+ if any(marker in content for marker in VERDICT_MARKERS):
255
+ return True, ""
256
+ return False, (
257
+ "No verdict found in any findings-draft/ file. "
258
+ "FP elimination verdicts (VALID / FALSE POSITIVE / BY DESIGN / OUT OF SCOPE) "
259
+ "must be written back into draft files during Phase 11."
260
+ )
261
+
262
+
263
+ def validate_phase(phase: int, security_dir: str) -> tuple[bool, list[str]]:
264
+ errors: list[str] = []
265
+ req = PHASE_REQUIREMENTS.get(phase)
266
+ if req is None:
267
+ return True, []
268
+
269
+ # Required files must exist and be non-empty
270
+ for fname in req.get("files", []):
271
+ fpath = os.path.join(security_dir, fname)
272
+ if not os.path.isfile(fpath):
273
+ errors.append(f"Missing required output file: {fname}")
274
+ continue
275
+ if os.path.getsize(fpath) == 0:
276
+ errors.append(f"Output file is empty: {fname}")
277
+
278
+ # KB section checks — verify knowledge-base-report.md contains expected section headers
279
+ kb_sections = req.get("kb_sections", [])
280
+ if kb_sections:
281
+ kb_path = os.path.join(security_dir, KB_FILE)
282
+ try:
283
+ kb_content = open(kb_path).read()
284
+ except OSError as e:
285
+ errors.append(f"Could not read {KB_FILE}: {e}")
286
+ kb_content = ""
287
+ for section in kb_sections:
288
+ if section not in kb_content:
289
+ errors.append(f"{KB_FILE}: missing required section '{section}'")
290
+
291
+ # Structural content checks for non-KB files (Phase 15 final-audit-report)
292
+ for fname, markers in req.get("sections", {}).items():
293
+ fpath = os.path.join(security_dir, fname)
294
+ if not os.path.isfile(fpath):
295
+ continue # already reported above
296
+ try:
297
+ content = open(fpath).read()
298
+ except OSError as e:
299
+ errors.append(f"Could not read {fname}: {e}")
300
+ continue
301
+ for marker in markers:
302
+ if marker not in content:
303
+ errors.append(f"{fname}: missing required content '{marker}'")
304
+
305
+ # findings-draft presence check
306
+ if req.get("findings_draft_required"):
307
+ ok, msg = check_findings_draft(security_dir)
308
+ if not ok:
309
+ errors.append(msg)
310
+
311
+ # KB addendum check (Phase 10 only)
312
+ if req.get("kb_addendum_required"):
313
+ ok, msg = check_kb_addendum(security_dir)
314
+ if not ok:
315
+ errors.append(msg)
316
+
317
+ # Verdict-in-drafts check (Phase 11)
318
+ if req.get("verdict_in_drafts_required"):
319
+ ok, msg = check_verdict_in_drafts(security_dir)
320
+ if not ok:
321
+ errors.append(msg)
322
+
323
+ # Adversarial review check (Phase 11 Stage 2 — P11-LITE: CRITICAL/HIGH only)
324
+ if req.get("adversarial_review_required"):
325
+ ok, msg = check_adversarial_reviews(security_dir)
326
+ if not ok:
327
+ errors.append(msg)
328
+
329
+ # Chamber workspace check (Phase 10 Review Chambers)
330
+ if req.get("chamber_workspace_required"):
331
+ ok, msg = check_chamber_workspace(security_dir)
332
+ if not ok:
333
+ errors.append(msg)
334
+
335
+ # Attack pattern registry check (Phase 10)
336
+ if req.get("attack_pattern_registry_required"):
337
+ ok, msg = check_attack_pattern_registry(security_dir)
338
+ if not ok:
339
+ errors.append(msg)
340
+
341
+ return len(errors) == 0, errors
342
+
343
+
344
+ def lint_all(security_dir: str) -> tuple[bool, list[str]]:
345
+ """Full-audit consistency checks across the archon/ directory."""
346
+ import json
347
+
348
+ errors: list[str] = []
349
+
350
+ # 1. Load audit-state.json — history format: {"audits": [...]}
351
+ state_path = os.path.join(security_dir, "audit-state.json")
352
+ current_audit: dict = {}
353
+ if os.path.isfile(state_path):
354
+ try:
355
+ data = json.loads(open(state_path).read())
356
+ audits = data.get("audits", [])
357
+ if audits:
358
+ current_audit = audits[-1] # most recent entry is the current audit
359
+ else:
360
+ errors.append("audit-state.json has an empty 'audits' array")
361
+ except (OSError, json.JSONDecodeError) as e:
362
+ errors.append(f"audit-state.json unreadable: {e}")
363
+
364
+ # 2. State vs artifact alignment: completed phases must have their KB sections and files
365
+ phases_state = current_audit.get("phases", {})
366
+ kb_path = os.path.join(security_dir, KB_FILE)
367
+ kb_content = ""
368
+ if os.path.isfile(kb_path):
369
+ try:
370
+ kb_content = open(kb_path).read()
371
+ except OSError:
372
+ pass
373
+
374
+ for phase_str, info in phases_state.items():
375
+ if info.get("status") != "complete":
376
+ continue
377
+ try:
378
+ phase_num = int(phase_str)
379
+ except ValueError:
380
+ continue
381
+ req = PHASE_REQUIREMENTS.get(phase_num, {})
382
+ for fname in req.get("files", []):
383
+ fpath = os.path.join(security_dir, fname)
384
+ if not os.path.isfile(fpath):
385
+ errors.append(
386
+ f"Phase {phase_num} marked complete but output missing: {fname}"
387
+ )
388
+ for section in req.get("kb_sections", []):
389
+ if kb_content and section not in kb_content:
390
+ errors.append(
391
+ f"Phase {phase_num} marked complete but KB missing section '{section}'"
392
+ )
393
+
394
+ # 3. KB addendum: if Phase 10 (Review Chambers) is complete, addendum must be present
395
+ chamber_info = phases_state.get("10", {})
396
+ if chamber_info.get("status") == "complete":
397
+ ok, msg = check_kb_addendum(security_dir)
398
+ if not ok:
399
+ errors.append(f"Phase 10 complete but KB addendum missing: {msg}")
400
+
401
+ # 4. Finding ID cross-reference: IDs in final-audit-report.md must have findings/ dirs
402
+ final_report = os.path.join(security_dir, "final-audit-report.md")
403
+ if os.path.isfile(final_report):
404
+ import re
405
+ content = open(final_report).read()
406
+ ids_in_report = re.findall(r"\b([CH ML][0-9]+)-[\w-]+", content)
407
+ findings_dir = os.path.join(security_dir, "findings")
408
+ if os.path.isdir(findings_dir):
409
+ existing = set(os.listdir(findings_dir))
410
+ for fid in set(ids_in_report):
411
+ matches = [d for d in existing if d.startswith(fid + "-") or d.startswith(fid.replace(" ", ""))]
412
+ if not matches:
413
+ errors.append(
414
+ f"final-audit-report.md references {fid} but no matching directory in archon/findings/"
415
+ )
416
+
417
+ # 5. Findings-draft cleanup: VALID drafts must have corresponding findings/ dirs
418
+ draft_dir = os.path.join(security_dir, "findings-draft")
419
+ findings_dir = os.path.join(security_dir, "findings")
420
+ if os.path.isdir(draft_dir):
421
+ for fname in os.listdir(draft_dir):
422
+ if not fname.endswith(".md"):
423
+ continue
424
+ fpath = os.path.join(draft_dir, fname)
425
+ try:
426
+ content = open(fpath).read()
427
+ except OSError:
428
+ continue
429
+ if "Verdict: VALID" in content:
430
+ slug = fname.replace(".md", "")
431
+ if os.path.isdir(findings_dir):
432
+ matches = [d for d in os.listdir(findings_dir) if slug in d]
433
+ if not matches:
434
+ errors.append(
435
+ f"findings-draft/{fname} has Verdict: VALID but was not promoted to archon/findings/"
436
+ )
437
+
438
+ # 5b. Finding completeness: every finding directory MUST have a non-empty report.md.
439
+ # This is the programmatic gate for Phase 14 (deep mode) and Phase 7 (balanced mode) —
440
+ # finding-writer is responsible for authoring report.md, and it must run before
441
+ # report-composer. A missing or stub report.md here is a real regression.
442
+ REPORT_MIN_BYTES = 500
443
+ if os.path.isdir(findings_dir):
444
+ for entry in sorted(os.listdir(findings_dir)):
445
+ fdir = os.path.join(findings_dir, entry)
446
+ if not os.path.isdir(fdir):
447
+ continue
448
+ # Only enforce for severity-prefixed finding directories (C*, H*, M*).
449
+ if not (entry[:1] in ("C", "H", "M") and len(entry) > 1 and entry[1:2].isdigit()):
450
+ continue
451
+ report_path = os.path.join(fdir, "report.md")
452
+ if not os.path.isfile(report_path):
453
+ errors.append(
454
+ f"findings/{entry}/report.md is missing — finding-writer "
455
+ f"must author it before report-composer runs."
456
+ )
457
+ continue
458
+ try:
459
+ size = os.path.getsize(report_path)
460
+ except OSError as e:
461
+ errors.append(f"findings/{entry}/report.md unreadable: {e}")
462
+ continue
463
+ if size < REPORT_MIN_BYTES:
464
+ errors.append(
465
+ f"findings/{entry}/report.md is too small ({size} bytes, min "
466
+ f"{REPORT_MIN_BYTES}) — likely a stub. Re-run finding-writer."
467
+ )
468
+ # Also require draft.md — every promoted finding should have it.
469
+ if not os.path.isfile(os.path.join(fdir, "draft.md")):
470
+ errors.append(
471
+ f"findings/{entry}/draft.md is missing — consolidation should have "
472
+ f"copied it from findings-draft/."
473
+ )
474
+
475
+ # 6. Stale separate report files must not exist (consolidated into knowledge-base-report.md)
476
+ for stale in STALE_FILES:
477
+ if os.path.isfile(os.path.join(security_dir, stale)):
478
+ errors.append(
479
+ f"Stale report file exists: {stale} — this has been consolidated into "
480
+ f"{KB_FILE} and should be removed."
481
+ )
482
+
483
+ # 7. Orphan detection: files in archon/ root not in the known output set.
484
+ # Recon artifacts live under attack-surface/; verify both layers.
485
+ known_outputs: set[str] = {"final-audit-report.md", "audit-state.json", "bounty-scope.md",
486
+ "attack-pattern-registry.json"}
487
+ known_dirs = {
488
+ ATTACK_SURFACE_DIR, "findings", "findings-draft", "codeql-artifacts", "codeql-queries",
489
+ "semgrep-rules", "adversarial-reviews", "real-env-evidence", "chamber-workspace",
490
+ }
491
+ for entry in os.listdir(security_dir):
492
+ entry_path = os.path.join(security_dir, entry)
493
+ if os.path.isdir(entry_path):
494
+ if entry not in known_dirs:
495
+ errors.append(f"Unexpected directory in archon/: {entry}/")
496
+ elif entry.endswith(".md") or entry.endswith(".json"):
497
+ if entry not in known_outputs:
498
+ errors.append(f"Orphaned file in archon/ (not in any phase output map): {entry}")
499
+
500
+ attack_surface_path = os.path.join(security_dir, ATTACK_SURFACE_DIR)
501
+ if os.path.isdir(attack_surface_path):
502
+ for entry in os.listdir(attack_surface_path):
503
+ sub = os.path.join(attack_surface_path, entry)
504
+ if os.path.isdir(sub):
505
+ errors.append(f"Unexpected directory in archon/{ATTACK_SURFACE_DIR}/: {entry}/")
506
+ elif entry not in KNOWN_ATTACK_SURFACE_FILES:
507
+ errors.append(f"Orphaned file in archon/{ATTACK_SURFACE_DIR}/: {entry}")
508
+
509
+ return len(errors) == 0, errors
510
+
511
+
512
+ def main() -> None:
513
+ if len(sys.argv) < 3:
514
+ print(
515
+ "Usage: validate_phase_output.py <phase_number|all> <security_dir>",
516
+ file=sys.stderr,
517
+ )
518
+ sys.exit(2)
519
+
520
+ phase_arg = sys.argv[1]
521
+ security_dir = sys.argv[2]
522
+
523
+ if not os.path.isdir(security_dir):
524
+ print(f"Security directory not found: {security_dir}", file=sys.stderr)
525
+ sys.exit(2)
526
+
527
+ if phase_arg == "all":
528
+ passed, errors = lint_all(security_dir)
529
+ label = "Full audit lint"
530
+ else:
531
+ try:
532
+ phase = int(phase_arg)
533
+ except ValueError:
534
+ print(f"Invalid phase number: {phase_arg}", file=sys.stderr)
535
+ sys.exit(2)
536
+ passed, errors = validate_phase(phase, security_dir)
537
+ label = f"Phase {phase} validation"
538
+
539
+ if passed:
540
+ print(f"{label} passed.")
541
+ sys.exit(0)
542
+ else:
543
+ print(f"{label} FAILED:")
544
+ for err in errors:
545
+ print(f" - {err}")
546
+ sys.exit(1)
547
+
548
+
549
+ if __name__ == "__main__":
550
+ main()