v4-pro 2.0.0__py3-none-any.whl
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.
- v4_pro/__init__.py +8 -0
- v4_pro/audit/__init__.py +5 -0
- v4_pro/audit/scanner.py +137 -0
- v4_pro/ci_check.py +79 -0
- v4_pro/cli.py +784 -0
- v4_pro/config.py +155 -0
- v4_pro/context_enricher.py +197 -0
- v4_pro/engine.py +649 -0
- v4_pro/freeze/__init__.py +5 -0
- v4_pro/freeze/manager.py +278 -0
- v4_pro/gate.py +406 -0
- v4_pro/llm/__init__.py +14 -0
- v4_pro/llm/base.py +88 -0
- v4_pro/llm/factory.py +76 -0
- v4_pro/llm/openai_adapter.py +110 -0
- v4_pro/phantom.py +408 -0
- v4_pro/prompts/__init__.py +9 -0
- v4_pro/prompts/loader.py +156 -0
- v4_pro/py.typed +1 -0
- v4_pro/scaffold.py +76 -0
- v4_pro/smells.py +335 -0
- v4_pro/trace.py +182 -0
- v4_pro/verify/__init__.py +7 -0
- v4_pro/verify/arch_compliance.py +267 -0
- v4_pro/verify/security_scan.py +403 -0
- v4_pro/verify/static_analysis.py +306 -0
- v4_pro-2.0.0.dist-info/METADATA +275 -0
- v4_pro-2.0.0.dist-info/RECORD +32 -0
- v4_pro-2.0.0.dist-info/WHEEL +5 -0
- v4_pro-2.0.0.dist-info/entry_points.txt +2 -0
- v4_pro-2.0.0.dist-info/licenses/LICENSE +21 -0
- v4_pro-2.0.0.dist-info/top_level.txt +1 -0
v4_pro/__init__.py
ADDED
v4_pro/audit/__init__.py
ADDED
v4_pro/audit/scanner.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""
|
|
2
|
+
独立安全审计模块 (SecurityAuditor)。
|
|
3
|
+
|
|
4
|
+
v4-pro audit 命令的实现 — 与 verify 的安全扫描共用同一套检测引擎
|
|
5
|
+
(v4_pro.verify.security_scan.SecurityScanner),保证两个入口的结果一致。
|
|
6
|
+
|
|
7
|
+
v2.0 变化:
|
|
8
|
+
- 检测逻辑统一到 SecurityScanner(此前是两套正则,规则漂移、误报翻倍)
|
|
9
|
+
- 保留审计报告 schema: audit_metadata / summary / findings / remediation_priority
|
|
10
|
+
- OWASP/CWE 映射由规则表统一维护
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
from datetime import datetime, timezone
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from v4_pro.verify.security_scan import SecurityScanner
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
# verify 严重度 → 审计严重度
|
|
25
|
+
_SEV_MAP = {"P0": "critical", "P1": "high", "P2": "medium", "P3": "low"}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SecurityAuditor:
|
|
29
|
+
"""
|
|
30
|
+
独立安全审计器。
|
|
31
|
+
|
|
32
|
+
用法:
|
|
33
|
+
auditor = SecurityAuditor()
|
|
34
|
+
report = auditor.audit(Path("./generated/"))
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self):
|
|
38
|
+
self._scanned_files = 0
|
|
39
|
+
|
|
40
|
+
def audit(self, code_dir: Path, extra_test_paths: list[str] | None = None) -> dict[str, Any]:
|
|
41
|
+
"""
|
|
42
|
+
执行全面安全审计。
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
code_dir: 代码目录
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
审计报告字典(schema 与 1.x 兼容)
|
|
49
|
+
"""
|
|
50
|
+
code_dir = Path(code_dir)
|
|
51
|
+
|
|
52
|
+
scanner = SecurityScanner()
|
|
53
|
+
scan_result = scanner.scan(code_dir, extra_test_paths=extra_test_paths)
|
|
54
|
+
self._scanned_files = scan_result["summary"].get("files_scanned", 0)
|
|
55
|
+
|
|
56
|
+
findings = []
|
|
57
|
+
for issue in scan_result["issues"]:
|
|
58
|
+
findings.append({
|
|
59
|
+
"file": issue.get("file", ""),
|
|
60
|
+
"line": issue.get("line", 0),
|
|
61
|
+
"title": issue.get("title", ""),
|
|
62
|
+
"severity": _SEV_MAP.get(issue.get("severity", "P3"), "low"),
|
|
63
|
+
"rule_id": issue.get("rule_id", ""),
|
|
64
|
+
"owasp_category": issue.get("owasp_category", ""),
|
|
65
|
+
"cwe": issue.get("cwe", ""),
|
|
66
|
+
"code_snippet": issue.get("code_snippet", ""),
|
|
67
|
+
"recommendation": issue.get("suggestion", ""),
|
|
68
|
+
"confidence": "high" if issue.get("severity") in ("P0", "P1") else "medium",
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
for i, f in enumerate(findings):
|
|
72
|
+
f["id"] = f"AUDIT-{i+1:04d}"
|
|
73
|
+
|
|
74
|
+
severity_counts = {"critical": 0, "high": 0, "medium": 0, "low": 0, "info": 0}
|
|
75
|
+
for f in findings:
|
|
76
|
+
sev = f.get("severity", "info")
|
|
77
|
+
severity_counts[sev] = severity_counts.get(sev, 0) + 1
|
|
78
|
+
|
|
79
|
+
owasp_covered = sorted({f["owasp_category"] for f in findings if f.get("owasp_category")})
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
"audit_metadata": {
|
|
83
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
84
|
+
"code_directory": str(code_dir),
|
|
85
|
+
"files_scanned": self._scanned_files,
|
|
86
|
+
"owasp_categories_covered": owasp_covered,
|
|
87
|
+
"tool": "V4 Pro Security Auditor v2.0",
|
|
88
|
+
},
|
|
89
|
+
"summary": {
|
|
90
|
+
"total_findings": len(findings),
|
|
91
|
+
"critical": severity_counts["critical"],
|
|
92
|
+
"high": severity_counts["high"],
|
|
93
|
+
"medium": severity_counts["medium"],
|
|
94
|
+
"low": severity_counts["low"],
|
|
95
|
+
"info": severity_counts["info"],
|
|
96
|
+
"risk_score": self._calculate_risk_score(severity_counts),
|
|
97
|
+
},
|
|
98
|
+
"findings": findings,
|
|
99
|
+
"remediation_priority": self._build_remediation_plan(findings),
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def _calculate_risk_score(counts: dict[str, int]) -> float:
|
|
104
|
+
"""综合风险评分 (0-100): critical=10, high=5, medium=2, low=1, info=0.2。"""
|
|
105
|
+
weights = {"critical": 10, "high": 5, "medium": 2, "low": 1, "info": 0.2}
|
|
106
|
+
raw = sum(counts.get(sev, 0) * weight for sev, weight in weights.items())
|
|
107
|
+
return round(min(raw, 100), 1)
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def _build_remediation_plan(findings: list[dict]) -> list[dict]:
|
|
111
|
+
plan = []
|
|
112
|
+
critical = [f for f in findings if f["severity"] == "critical"]
|
|
113
|
+
high = [f for f in findings if f["severity"] == "high"]
|
|
114
|
+
medium = [f for f in findings if f["severity"] == "medium"]
|
|
115
|
+
|
|
116
|
+
if critical:
|
|
117
|
+
plan.append({
|
|
118
|
+
"priority": 1,
|
|
119
|
+
"action": f"立即修复 {len(critical)} 个严重漏洞",
|
|
120
|
+
"findings": [f["id"] for f in critical],
|
|
121
|
+
"deadline": "24 小时内",
|
|
122
|
+
})
|
|
123
|
+
if high:
|
|
124
|
+
plan.append({
|
|
125
|
+
"priority": 2,
|
|
126
|
+
"action": f"尽快修复 {len(high)} 个高危漏洞",
|
|
127
|
+
"findings": [f["id"] for f in high],
|
|
128
|
+
"deadline": "本周内",
|
|
129
|
+
})
|
|
130
|
+
if medium:
|
|
131
|
+
plan.append({
|
|
132
|
+
"priority": 3,
|
|
133
|
+
"action": f"计划修复 {len(medium)} 个中危漏洞",
|
|
134
|
+
"findings": [f["id"] for f in medium],
|
|
135
|
+
"deadline": "下个迭代",
|
|
136
|
+
})
|
|
137
|
+
return plan
|
v4_pro/ci_check.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""CI/CD 集成 — 将 audit JSON 转为 GitHub Actions 行内注解。
|
|
2
|
+
|
|
3
|
+
用法:
|
|
4
|
+
v4-pro audit --code ./src/ --format json > report.json
|
|
5
|
+
python -m v4_pro.ci_check report.json
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _sanitize(text: str) -> str:
|
|
15
|
+
"""清理控制字符和不可见字符。"""
|
|
16
|
+
if not text:
|
|
17
|
+
return ""
|
|
18
|
+
text = re.sub(r"[\x00-\x08\x0b\x0c\x0e-\x1f\ufffd]", "", text)
|
|
19
|
+
return text.strip()[:200]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main():
|
|
23
|
+
# Force UTF-8 for GitHub Actions output
|
|
24
|
+
try:
|
|
25
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
26
|
+
except AttributeError:
|
|
27
|
+
pass # Python < 3.7
|
|
28
|
+
|
|
29
|
+
args = sys.argv[1:]
|
|
30
|
+
if not args:
|
|
31
|
+
print("Usage: python -m v4_pro.ci_check <audit-report.json>")
|
|
32
|
+
sys.exit(1)
|
|
33
|
+
|
|
34
|
+
report_path = Path(args[0])
|
|
35
|
+
if not report_path.exists():
|
|
36
|
+
print(f"File not found: {report_path}")
|
|
37
|
+
sys.exit(1)
|
|
38
|
+
|
|
39
|
+
report = json.loads(report_path.read_bytes())
|
|
40
|
+
findings = report.get("findings", [])
|
|
41
|
+
|
|
42
|
+
if not findings:
|
|
43
|
+
print("✅ v4-pro: No issues found")
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
critical_findings = [f for f in findings if f.get("severity") == "critical"]
|
|
47
|
+
high_findings = [f for f in findings if f.get("severity") == "high"]
|
|
48
|
+
|
|
49
|
+
for finding in findings:
|
|
50
|
+
severity = finding.get("severity", "warning")
|
|
51
|
+
level = "error" if severity in ("critical", "high") else "warning"
|
|
52
|
+
title = _sanitize(finding.get("title", "Unknown issue"))
|
|
53
|
+
file_path = _sanitize(finding.get("file", ""))
|
|
54
|
+
line = finding.get("line", 1)
|
|
55
|
+
|
|
56
|
+
# GitHub Actions annotation
|
|
57
|
+
msg = f"::{level} file={file_path},line={line},title={title}::{title}"
|
|
58
|
+
print(msg)
|
|
59
|
+
|
|
60
|
+
rec = _sanitize(finding.get("recommendation", ""))
|
|
61
|
+
if rec:
|
|
62
|
+
print(f"::notice file={file_path},line={line}::Suggestion: {rec}")
|
|
63
|
+
|
|
64
|
+
# Summary
|
|
65
|
+
print("::group::V4 Pro Audit Summary")
|
|
66
|
+
total = len(findings)
|
|
67
|
+
critical = len(critical_findings)
|
|
68
|
+
high = len(high_findings)
|
|
69
|
+
print(f"Critical: {critical} | High: {high} | Total: {total}")
|
|
70
|
+
print("::endgroup::")
|
|
71
|
+
|
|
72
|
+
if critical > 0:
|
|
73
|
+
print(f"❌ {critical} critical issue(s) found — PR blocked")
|
|
74
|
+
sys.exit(1)
|
|
75
|
+
print("✅ V4 Pro quality gate passed")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
if __name__ == "__main__":
|
|
79
|
+
main()
|