@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.
- package/LICENSE +21 -0
- package/README.md +117 -0
- package/agents/access-auditor.md +300 -0
- package/agents/assumption-breaker.md +154 -0
- package/agents/attack-designer.md +116 -0
- package/agents/code-scanner.md +139 -0
- package/agents/concurrency-auditor.md +238 -0
- package/agents/confirm-writer.md +257 -0
- package/agents/context-reviewer.md +274 -0
- package/agents/cross-verifier.md +165 -0
- package/agents/cve-scout.md +381 -0
- package/agents/env-builder.md +282 -0
- package/agents/env-profiler.md +205 -0
- package/agents/evidence-collector.md +140 -0
- package/agents/finding-grader.md +142 -0
- package/agents/finding-writer.md +148 -0
- package/agents/flow-tracer.md +106 -0
- package/agents/goal-backtracer.md +146 -0
- package/agents/history-miner.md +467 -0
- package/agents/independent-verifier.md +118 -0
- package/agents/intent-mapper.md +183 -0
- package/agents/longshot-collector.md +128 -0
- package/agents/longshot-prober.md +126 -0
- package/agents/patch-auditor.md +73 -0
- package/agents/poc-author.md +124 -0
- package/agents/poc-runner.md +194 -0
- package/agents/probe-lead.md +269 -0
- package/agents/red-challenger.md +101 -0
- package/agents/report-composer.md +208 -0
- package/agents/review-adjudicator.md +216 -0
- package/agents/spec-auditor.md +155 -0
- package/agents/taint-tracer.md +265 -0
- package/agents/test-locator.md +209 -0
- package/agents/threat-modeler.md +132 -0
- package/agents/variant-scanner.md +108 -0
- package/agents/variant-spotter.md +110 -0
- package/bin/piolium.mjs +376 -0
- package/extensions/piolium/_vendor/yaml.bundle.d.mts +6 -0
- package/extensions/piolium/_vendor/yaml.bundle.mjs +139 -0
- package/extensions/piolium/agent-runner.ts +322 -0
- package/extensions/piolium/agents.ts +266 -0
- package/extensions/piolium/audit-state.ts +522 -0
- package/extensions/piolium/bundled-resources.ts +97 -0
- package/extensions/piolium/candidate-scan.ts +966 -0
- package/extensions/piolium/command-target.ts +177 -0
- package/extensions/piolium/console-stream.ts +57 -0
- package/extensions/piolium/export-results.ts +380 -0
- package/extensions/piolium/findings.ts +448 -0
- package/extensions/piolium/heartbeat.ts +182 -0
- package/extensions/piolium/help.ts +234 -0
- package/extensions/piolium/index.ts +1865 -0
- package/extensions/piolium/longshot.ts +530 -0
- package/extensions/piolium/matcher-suggestions.ts +196 -0
- package/extensions/piolium/matcher-utils.ts +83 -0
- package/extensions/piolium/modes/balanced.ts +750 -0
- package/extensions/piolium/modes/confirm-bootstrap.ts +186 -0
- package/extensions/piolium/modes/confirm.ts +697 -0
- package/extensions/piolium/modes/deep.ts +917 -0
- package/extensions/piolium/modes/diff.ts +177 -0
- package/extensions/piolium/modes/lite.ts +540 -0
- package/extensions/piolium/modes/longshot.ts +595 -0
- package/extensions/piolium/modes/merge.ts +204 -0
- package/extensions/piolium/modes/phase-runner.ts +267 -0
- package/extensions/piolium/modes/reinvest.ts +546 -0
- package/extensions/piolium/modes/revisit.ts +279 -0
- package/extensions/piolium/modes.ts +48 -0
- package/extensions/piolium/phase-labels.ts +123 -0
- package/extensions/piolium/phase-status-strip.ts +92 -0
- package/extensions/piolium/prompt-prefix-editor.ts +39 -0
- package/extensions/piolium/providers/anthropic-vertex.ts +836 -0
- package/extensions/piolium/recon.ts +409 -0
- package/extensions/piolium/result-stats.ts +105 -0
- package/extensions/piolium/retry.ts +120 -0
- package/extensions/piolium/scheduler.ts +212 -0
- package/extensions/piolium/secrets.ts +368 -0
- package/extensions/piolium/tools/web-tools.ts +148 -0
- package/package.json +77 -0
- package/skills/agentic-actions-auditor/SKILL.md +327 -0
- package/skills/agentic-actions-auditor/references/action-profiles.md +186 -0
- package/skills/agentic-actions-auditor/references/cross-file-resolution.md +209 -0
- package/skills/agentic-actions-auditor/references/foundations.md +94 -0
- package/skills/agentic-actions-auditor/references/vector-a-env-var-intermediary.md +77 -0
- package/skills/agentic-actions-auditor/references/vector-b-direct-expression-injection.md +83 -0
- package/skills/agentic-actions-auditor/references/vector-c-cli-data-fetch.md +83 -0
- package/skills/agentic-actions-auditor/references/vector-d-pr-target-checkout.md +88 -0
- package/skills/agentic-actions-auditor/references/vector-e-error-log-injection.md +88 -0
- package/skills/agentic-actions-auditor/references/vector-f-subshell-expansion.md +82 -0
- package/skills/agentic-actions-auditor/references/vector-g-eval-of-ai-output.md +91 -0
- package/skills/agentic-actions-auditor/references/vector-h-dangerous-sandbox-configs.md +102 -0
- package/skills/agentic-actions-auditor/references/vector-i-wildcard-allowlists.md +88 -0
- package/skills/audit/SKILL.md +562 -0
- package/skills/audit/assets/icon.svg +7 -0
- package/skills/audit/hooks/scripts/validate_phase_output.py +550 -0
- package/skills/audit/references/adversarial-review.md +148 -0
- package/skills/audit/references/architecture-aware-sast.md +306 -0
- package/skills/audit/references/audit-workflow.md +737 -0
- package/skills/audit/references/chamber-protocol.md +384 -0
- package/skills/audit/references/creative-attack-modes.md +221 -0
- package/skills/audit/references/deep-analysis.md +273 -0
- package/skills/audit/references/domain-attack-playbooks.md +1129 -0
- package/skills/audit/references/knowledge-base-template.md +513 -0
- package/skills/audit/references/real-env-validation.md +191 -0
- package/skills/audit/references/report-templates.md +417 -0
- package/skills/audit/references/triage-and-prereqs.md +134 -0
- package/skills/audit/scripts/consolidate_drafts.py +554 -0
- package/skills/audit/scripts/partition_findings.py +152 -0
- package/skills/audit/scripts/rg-hotspots.sh +121 -0
- package/skills/audit/scripts/stamp_file_state.py +349 -0
- package/skills/code-reviewer/SKILL.md +65 -0
- package/skills/codeql/SKILL.md +281 -0
- package/skills/codeql/references/build-fixes.md +90 -0
- package/skills/codeql/references/diagnostic-query-templates.md +339 -0
- package/skills/codeql/references/extension-yaml-format.md +209 -0
- package/skills/codeql/references/important-only-suite.md +153 -0
- package/skills/codeql/references/language-details.md +207 -0
- package/skills/codeql/references/macos-arm64e-workaround.md +179 -0
- package/skills/codeql/references/performance-tuning.md +111 -0
- package/skills/codeql/references/quality-assessment.md +172 -0
- package/skills/codeql/references/ruleset-catalog.md +63 -0
- package/skills/codeql/references/run-all-suite.md +92 -0
- package/skills/codeql/references/sarif-processing.md +79 -0
- package/skills/codeql/references/threat-models.md +51 -0
- package/skills/codeql/workflows/build-database.md +280 -0
- package/skills/codeql/workflows/create-data-extensions.md +261 -0
- package/skills/codeql/workflows/run-analysis.md +301 -0
- package/skills/differential-review/SKILL.md +220 -0
- package/skills/differential-review/adversarial.md +203 -0
- package/skills/differential-review/methodology.md +234 -0
- package/skills/differential-review/patterns.md +300 -0
- package/skills/differential-review/reporting.md +369 -0
- package/skills/fp-check/SKILL.md +125 -0
- package/skills/fp-check/references/bug-class-verification.md +114 -0
- package/skills/fp-check/references/deep-verification.md +143 -0
- package/skills/fp-check/references/evidence-templates.md +91 -0
- package/skills/fp-check/references/false-positive-patterns.md +115 -0
- package/skills/fp-check/references/gate-reviews.md +27 -0
- package/skills/fp-check/references/standard-verification.md +78 -0
- package/skills/insecure-defaults/SKILL.md +117 -0
- package/skills/insecure-defaults/references/examples.md +409 -0
- package/skills/last30days/SKILL.md +444 -0
- package/skills/sarif-parsing/SKILL.md +483 -0
- package/skills/sarif-parsing/resources/jq-queries.md +162 -0
- package/skills/sarif-parsing/resources/sarif_helpers.py +331 -0
- package/skills/security-threat-model/LICENSE.txt +201 -0
- package/skills/security-threat-model/SKILL.md +81 -0
- package/skills/security-threat-model/agents/openai.yaml +4 -0
- package/skills/security-threat-model/references/prompt-template.md +255 -0
- package/skills/security-threat-model/references/security-controls-and-assets.md +32 -0
- package/skills/semgrep/SKILL.md +212 -0
- package/skills/semgrep/references/rulesets.md +162 -0
- package/skills/semgrep/references/scan-modes.md +110 -0
- package/skills/semgrep/references/scanner-task-prompt.md +140 -0
- package/skills/semgrep/scripts/merge_sarif.py +203 -0
- package/skills/semgrep/workflows/scan-workflow.md +311 -0
- package/skills/semgrep-rule-creator/SKILL.md +168 -0
- package/skills/semgrep-rule-creator/references/quick-reference.md +202 -0
- package/skills/semgrep-rule-creator/references/workflow.md +240 -0
- package/skills/semgrep-rule-variant-creator/SKILL.md +205 -0
- package/skills/semgrep-rule-variant-creator/references/applicability-analysis.md +250 -0
- package/skills/semgrep-rule-variant-creator/references/language-syntax-guide.md +324 -0
- package/skills/semgrep-rule-variant-creator/references/workflow.md +518 -0
- package/skills/sharp-edges/SKILL.md +292 -0
- package/skills/sharp-edges/references/auth-patterns.md +252 -0
- package/skills/sharp-edges/references/case-studies.md +274 -0
- package/skills/sharp-edges/references/config-patterns.md +333 -0
- package/skills/sharp-edges/references/crypto-apis.md +190 -0
- package/skills/sharp-edges/references/lang-c.md +205 -0
- package/skills/sharp-edges/references/lang-csharp.md +285 -0
- package/skills/sharp-edges/references/lang-go.md +270 -0
- package/skills/sharp-edges/references/lang-java.md +263 -0
- package/skills/sharp-edges/references/lang-javascript.md +269 -0
- package/skills/sharp-edges/references/lang-kotlin.md +265 -0
- package/skills/sharp-edges/references/lang-php.md +245 -0
- package/skills/sharp-edges/references/lang-python.md +274 -0
- package/skills/sharp-edges/references/lang-ruby.md +273 -0
- package/skills/sharp-edges/references/lang-rust.md +272 -0
- package/skills/sharp-edges/references/lang-swift.md +287 -0
- package/skills/sharp-edges/references/language-specific.md +588 -0
- package/skills/spec-to-code-compliance/SKILL.md +357 -0
- package/skills/spec-to-code-compliance/resources/COMPLETENESS_CHECKLIST.md +69 -0
- package/skills/spec-to-code-compliance/resources/IR_EXAMPLES.md +417 -0
- package/skills/spec-to-code-compliance/resources/OUTPUT_REQUIREMENTS.md +105 -0
- package/skills/supply-chain-risk-auditor/SKILL.md +67 -0
- package/skills/supply-chain-risk-auditor/resources/results-template.md +41 -0
- package/skills/variant-analysis/METHODOLOGY.md +327 -0
- package/skills/variant-analysis/SKILL.md +142 -0
- package/skills/variant-analysis/resources/codeql/cpp.ql +119 -0
- package/skills/variant-analysis/resources/codeql/go.ql +69 -0
- package/skills/variant-analysis/resources/codeql/java.ql +71 -0
- package/skills/variant-analysis/resources/codeql/javascript.ql +63 -0
- package/skills/variant-analysis/resources/codeql/python.ql +80 -0
- package/skills/variant-analysis/resources/semgrep/cpp.yaml +98 -0
- package/skills/variant-analysis/resources/semgrep/go.yaml +63 -0
- package/skills/variant-analysis/resources/semgrep/java.yaml +61 -0
- package/skills/variant-analysis/resources/semgrep/javascript.yaml +60 -0
- package/skills/variant-analysis/resources/semgrep/python.yaml +72 -0
- package/skills/variant-analysis/resources/variant-report-template.md +75 -0
- package/skills/vuln-report/SKILL.md +137 -0
- package/skills/vuln-report/agents/openai.yaml +4 -0
- package/skills/vuln-report/references/report-template.md +135 -0
- package/skills/wooyun-legacy/SKILL.md +367 -0
- package/skills/wooyun-legacy/references/bank-penetration.md +222 -0
- package/skills/wooyun-legacy/references/checklists/command-execution-checklist.md +119 -0
- package/skills/wooyun-legacy/references/checklists/csrf-checklist.md +74 -0
- package/skills/wooyun-legacy/references/checklists/file-upload-checklist.md +108 -0
- package/skills/wooyun-legacy/references/checklists/info-disclosure-checklist.md +114 -0
- package/skills/wooyun-legacy/references/checklists/logic-flaws-checklist.md +95 -0
- package/skills/wooyun-legacy/references/checklists/misconfig-checklist.md +124 -0
- package/skills/wooyun-legacy/references/checklists/path-traversal-checklist.md +87 -0
- package/skills/wooyun-legacy/references/checklists/rce-checklist.md +93 -0
- package/skills/wooyun-legacy/references/checklists/sql-injection-checklist.md +97 -0
- package/skills/wooyun-legacy/references/checklists/ssrf-checklist.md +99 -0
- package/skills/wooyun-legacy/references/checklists/unauthorized-access-checklist.md +89 -0
- package/skills/wooyun-legacy/references/checklists/weak-password-checklist.md +115 -0
- package/skills/wooyun-legacy/references/checklists/xss-checklist.md +103 -0
- package/skills/wooyun-legacy/references/checklists/xxe-checklist.md +130 -0
- package/skills/wooyun-legacy/references/info-disclosure.md +975 -0
- package/skills/wooyun-legacy/references/logic-flaws.md +721 -0
- package/skills/wooyun-legacy/references/path-traversal.md +1191 -0
- package/skills/wooyun-legacy/references/telecom-penetration.md +156 -0
- package/skills/wooyun-legacy/references/unauthorized-access.md +980 -0
- package/skills/wooyun-legacy/references/xss.md +746 -0
- package/skills/zeroize-audit/SKILL.md +371 -0
- package/skills/zeroize-audit/configs/c.yaml +21 -0
- package/skills/zeroize-audit/configs/default.yaml +128 -0
- package/skills/zeroize-audit/configs/rust.yaml +83 -0
- package/skills/zeroize-audit/prompts/report_template.md +238 -0
- package/skills/zeroize-audit/prompts/system.md +163 -0
- package/skills/zeroize-audit/prompts/task.md +97 -0
- package/skills/zeroize-audit/references/compile-commands.md +231 -0
- package/skills/zeroize-audit/references/detection-strategy.md +191 -0
- package/skills/zeroize-audit/references/ir-analysis.md +252 -0
- package/skills/zeroize-audit/references/mcp-analysis.md +221 -0
- package/skills/zeroize-audit/references/poc-generation.md +470 -0
- package/skills/zeroize-audit/references/rust-zeroization-patterns.md +867 -0
- package/skills/zeroize-audit/schemas/input.json +83 -0
- package/skills/zeroize-audit/schemas/output.json +140 -0
- package/skills/zeroize-audit/tools/analyze_asm.sh +202 -0
- package/skills/zeroize-audit/tools/analyze_cfg.py +381 -0
- package/skills/zeroize-audit/tools/analyze_heap.sh +211 -0
- package/skills/zeroize-audit/tools/analyze_ir_semantic.py +429 -0
- package/skills/zeroize-audit/tools/diff_ir.sh +135 -0
- package/skills/zeroize-audit/tools/diff_rust_mir.sh +189 -0
- package/skills/zeroize-audit/tools/emit_asm.sh +67 -0
- package/skills/zeroize-audit/tools/emit_ir.sh +77 -0
- package/skills/zeroize-audit/tools/emit_rust_asm.sh +178 -0
- package/skills/zeroize-audit/tools/emit_rust_ir.sh +150 -0
- package/skills/zeroize-audit/tools/emit_rust_mir.sh +158 -0
- package/skills/zeroize-audit/tools/extract_compile_flags.py +284 -0
- package/skills/zeroize-audit/tools/generate_poc.py +1329 -0
- package/skills/zeroize-audit/tools/mcp/apply_confidence_gates.py +113 -0
- package/skills/zeroize-audit/tools/mcp/check_mcp.sh +68 -0
- package/skills/zeroize-audit/tools/mcp/normalize_mcp_evidence.py +125 -0
- package/skills/zeroize-audit/tools/scripts/check_llvm_patterns.py +481 -0
- package/skills/zeroize-audit/tools/scripts/check_mir_patterns.py +554 -0
- package/skills/zeroize-audit/tools/scripts/check_rust_asm.py +424 -0
- package/skills/zeroize-audit/tools/scripts/check_rust_asm_aarch64.py +300 -0
- package/skills/zeroize-audit/tools/scripts/check_rust_asm_x86.py +283 -0
- package/skills/zeroize-audit/tools/scripts/find_dangerous_apis.py +375 -0
- package/skills/zeroize-audit/tools/scripts/semantic_audit.py +923 -0
- package/skills/zeroize-audit/tools/track_dataflow.sh +196 -0
- package/skills/zeroize-audit/tools/validate_rust_toolchain.sh +298 -0
- package/skills/zeroize-audit/workflows/phase-0-preflight.md +150 -0
- package/skills/zeroize-audit/workflows/phase-1-source-analysis.md +144 -0
- package/skills/zeroize-audit/workflows/phase-2-compiler-analysis.md +139 -0
- package/skills/zeroize-audit/workflows/phase-3-interim-report.md +46 -0
- package/skills/zeroize-audit/workflows/phase-4-poc-generation.md +46 -0
- package/skills/zeroize-audit/workflows/phase-5-poc-validation.md +136 -0
- package/skills/zeroize-audit/workflows/phase-6-final-report.md +44 -0
- package/skills/zeroize-audit/workflows/phase-7-test-generation.md +42 -0
- 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()
|