@comate/zulu 1.2.1-beta.1 → 1.3.0

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 (169) hide show
  1. package/comate-engine/assets/skills/auto-commit-comate/SKILL.md +260 -0
  2. package/comate-engine/assets/skills/auto-commit-comate/references/data_structures.md +189 -0
  3. package/comate-engine/assets/skills/auto-commit-comate/references/new_version_instruction.md +209 -0
  4. package/comate-engine/assets/skills/auto-commit-comate/references/old_version_instruction.md +208 -0
  5. package/comate-engine/assets/skills/auto-commit-comate/scripts/git_diff_cli.py +196 -0
  6. package/comate-engine/assets/skills/{smart-commit → auto-commit-comate}/scripts/git_utils.py +20 -10
  7. package/comate-engine/assets/skills/{smart-commit → auto-commit-comate}/scripts/icafe/client.py +69 -40
  8. package/comate-engine/assets/skills/{smart-commit → auto-commit-comate}/scripts/icafe/farseer.py +8 -9
  9. package/comate-engine/assets/skills/{smart-commit → auto-commit-comate}/scripts/icafe/matching.py +65 -9
  10. package/comate-engine/assets/skills/auto-commit-comate/scripts/match_card_cli.py +37 -0
  11. package/comate-engine/assets/skills/cnap-comate/SKILL.md +157 -0
  12. package/comate-engine/assets/skills/cnap-comate/references/cases.md +198 -0
  13. package/comate-engine/assets/skills/cnap-comate/references/deploy-troubleshoot.md +15 -0
  14. package/comate-engine/assets/skills/cnap-comate/references/install.md +43 -0
  15. package/comate-engine/assets/skills/cnap-comate/references/kubectl.md +55 -0
  16. package/comate-engine/assets/skills/cnap-comate/references/login.md +125 -0
  17. package/comate-engine/assets/skills/cnap-comate/references/oncall.md +24 -0
  18. package/comate-engine/assets/skills/cnap-comate/scripts/install_cnap_cli.sh +36 -0
  19. package/comate-engine/assets/skills/code-security/SKILL.md +176 -0
  20. package/comate-engine/assets/skills/code-security/references/credential_hosting.md +102 -0
  21. package/comate-engine/assets/skills/code-security/references/vul_repair_sensitive.md +219 -0
  22. package/comate-engine/assets/skills/code-security/scripts/build_repair_info.py +0 -0
  23. package/comate-engine/assets/skills/code-security/scripts/credential_hosting.py +99 -0
  24. package/comate-engine/assets/skills/code-security/scripts/credential_poll.py +350 -0
  25. package/comate-engine/assets/skills/code-security/scripts/http_client.py +173 -0
  26. package/comate-engine/assets/skills/code-security/scripts/parse_scan_result.py +301 -0
  27. package/comate-engine/assets/skills/code-security/scripts/repair_vulnerability.py +261 -0
  28. package/comate-engine/assets/skills/code-security/scripts/report_chat.py +198 -0
  29. package/comate-engine/assets/skills/code-security/scripts/scan_vulnerability.py +316 -0
  30. package/comate-engine/assets/skills/code-security-comate/SKILL.md +219 -0
  31. package/comate-engine/assets/skills/code-security-comate/references/credential_hosting.md +102 -0
  32. package/comate-engine/assets/skills/code-security-comate/references/vul_repair-go_sql_injection.md +399 -0
  33. package/comate-engine/assets/skills/code-security-comate/references/vul_repair-java_sql_injection.md +591 -0
  34. package/comate-engine/assets/skills/code-security-comate/references/vul_repair-php_sql_injection.md +318 -0
  35. package/comate-engine/assets/skills/code-security-comate/references/vul_repair-python_sql_injection.md +198 -0
  36. package/comate-engine/assets/skills/code-security-comate/references/vul_repair_sensitive.md +219 -0
  37. package/comate-engine/assets/skills/code-security-comate/scripts/credential_hosting.py +87 -0
  38. package/comate-engine/assets/skills/code-security-comate/scripts/credential_poll.py +345 -0
  39. package/comate-engine/assets/skills/code-security-comate/scripts/http_client.py +173 -0
  40. package/comate-engine/assets/skills/code-security-comate/scripts/parse_scan_result.py +392 -0
  41. package/comate-engine/assets/skills/code-security-comate/scripts/repair_vulnerability.py +245 -0
  42. package/comate-engine/assets/skills/code-security-comate/scripts/report_chat.py +145 -0
  43. package/comate-engine/assets/skills/code-security-comate/scripts/scan_vulnerability.py +444 -0
  44. package/comate-engine/assets/skills/code-security-comate/scripts/utils.py +153 -0
  45. package/comate-engine/assets/skills/comate-docs-comate/SKILL.md +148 -0
  46. package/comate-engine/assets/skills/comate-docs-comate/references/doc-map-extended.md +78 -0
  47. package/comate-engine/assets/skills/comate-docs-comate/references/models-and-billing.md +51 -0
  48. package/comate-engine/assets/skills/comate-docs-comate/references/product-overview.md +73 -0
  49. package/comate-engine/assets/skills/comate-docs-comate/references/query_content.md +83 -0
  50. package/comate-engine/assets/skills/comate-docs-comate/references/query_repo.md +57 -0
  51. package/comate-engine/assets/skills/comate-docs-comate/scripts/ku_operator.py +1575 -0
  52. package/comate-engine/assets/skills/create-image-comate/SKILL.md +278 -0
  53. package/comate-engine/assets/skills/create-skill-comate/SKILL.md +308 -217
  54. package/comate-engine/assets/skills/create-skill-comate/agents/analyzer.md +274 -0
  55. package/comate-engine/assets/skills/create-skill-comate/agents/comparator.md +202 -0
  56. package/comate-engine/assets/skills/create-skill-comate/agents/grader.md +223 -0
  57. package/comate-engine/assets/skills/create-skill-comate/assets/eval_review.html +146 -0
  58. package/comate-engine/assets/skills/create-skill-comate/eval-viewer/generate_review.py +489 -0
  59. package/comate-engine/assets/skills/create-skill-comate/eval-viewer/viewer.html +1325 -0
  60. package/comate-engine/assets/skills/create-skill-comate/references/schemas.md +430 -0
  61. package/comate-engine/assets/skills/create-skill-comate/scripts/__init__.py +0 -0
  62. package/comate-engine/assets/skills/create-skill-comate/scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  63. package/comate-engine/assets/skills/create-skill-comate/scripts/__pycache__/aggregate_benchmark.cpython-311.pyc +0 -0
  64. package/comate-engine/assets/skills/create-skill-comate/scripts/aggregate_benchmark.py +412 -0
  65. package/comate-engine/assets/skills/create-skill-comate/scripts/generate_report.py +334 -0
  66. package/comate-engine/assets/skills/create-skill-comate/scripts/package_skill.py +140 -0
  67. package/comate-engine/assets/skills/create-skill-comate/scripts/utils.py +53 -0
  68. package/comate-engine/assets/skills/find-skills-comate/SKILL.md +15 -12
  69. package/comate-engine/assets/skills/find-skills-comate/scripts/fetch_skills.py +32 -3
  70. package/comate-engine/assets/skills/get-ugate-token-comate/SKILL.md +159 -0
  71. package/comate-engine/assets/skills/get-ugate-token-comate/getUgateToken.py +150 -0
  72. package/comate-engine/assets/skills/icafe-comate/SKILL.md +240 -0
  73. package/comate-engine/assets/skills/icafe-comate/references/ai-workflows.md +233 -0
  74. package/comate-engine/assets/skills/icafe-comate/references/commands.md +1147 -0
  75. package/comate-engine/assets/skills/icafe-comate/references/error-handling.md +164 -0
  76. package/comate-engine/assets/skills/icafe-comate/references/git-auto-bindcard-workflow.md +201 -0
  77. package/comate-engine/assets/skills/icafe-comate/references/git-bindcard-workflow.md +327 -0
  78. package/comate-engine/assets/skills/icafe-comate/references/iql-syntax.md +327 -0
  79. package/comate-engine/assets/skills/icafe-comate/references/platform-concepts.md +317 -0
  80. package/comate-engine/assets/skills/icafe-comate/references/smart-create-workflow.md +171 -0
  81. package/comate-engine/assets/skills/icafe-comate/references/smart-find-workflow.md +127 -0
  82. package/comate-engine/assets/skills/icafe-comate/references/smart-update-workflow.md +118 -0
  83. package/comate-engine/assets/skills/icode-comate/SKILL.md +366 -0
  84. package/comate-engine/assets/skills/icode-comate/references/api/add_reviewers.md +44 -0
  85. package/comate-engine/assets/skills/icode-comate/references/api/build_fetch_command.md +89 -0
  86. package/comate-engine/assets/skills/icode-comate/references/api/check_repo_permission.md +89 -0
  87. package/comate-engine/assets/skills/icode-comate/references/api/create_branch.md +79 -0
  88. package/comate-engine/assets/skills/icode-comate/references/api/create_draft_comment.md +109 -0
  89. package/comate-engine/assets/skills/icode-comate/references/api/get_ai_cr_result.md +190 -0
  90. package/comate-engine/assets/skills/icode-comate/references/api/get_ai_review.md +97 -0
  91. package/comate-engine/assets/skills/icode-comate/references/api/get_diff_content.md +92 -0
  92. package/comate-engine/assets/skills/icode-comate/references/api/get_diff_file.md +88 -0
  93. package/comate-engine/assets/skills/icode-comate/references/api/get_machine_check.md +73 -0
  94. package/comate-engine/assets/skills/icode-comate/references/api/get_my_reviews.md +115 -0
  95. package/comate-engine/assets/skills/icode-comate/references/api/get_person_commit.md +89 -0
  96. package/comate-engine/assets/skills/icode-comate/references/api/get_person_repo.md +63 -0
  97. package/comate-engine/assets/skills/icode-comate/references/api/get_repo_branch.md +62 -0
  98. package/comate-engine/assets/skills/icode-comate/references/api/get_repo_config.md +91 -0
  99. package/comate-engine/assets/skills/icode-comate/references/api/get_repo_members.md +118 -0
  100. package/comate-engine/assets/skills/icode-comate/references/api/get_repo_reviews.md +91 -0
  101. package/comate-engine/assets/skills/icode-comate/references/api/get_review_comments.md +87 -0
  102. package/comate-engine/assets/skills/icode-comate/references/api/get_review_info.md +81 -0
  103. package/comate-engine/assets/skills/icode-comate/references/api/get_submit_settings.md +105 -0
  104. package/comate-engine/assets/skills/icode-comate/references/api/icode-api.md +86 -0
  105. package/comate-engine/assets/skills/icode-comate/references/api/publish_comments.md +72 -0
  106. package/comate-engine/assets/skills/icode-comate/references/api/set_review_score.md +58 -0
  107. package/comate-engine/assets/skills/icode-comate/references/api/start_ai_review.md +77 -0
  108. package/comate-engine/assets/skills/icode-comate/references/api/submit_review.md +50 -0
  109. package/comate-engine/assets/skills/icode-comate/references/api/trigger_ai_cr.md +63 -0
  110. package/comate-engine/assets/skills/icode-comate/references/feature/add-reviewer.md +92 -0
  111. package/comate-engine/assets/skills/icode-comate/references/feature/fix-machine-check.md +144 -0
  112. package/comate-engine/assets/skills/icode-comate/references/feature/merge-cr.md +100 -0
  113. package/comate-engine/assets/skills/icode-comate/references/feature/ssh-setup.md +106 -0
  114. package/comate-engine/assets/skills/icode-comate/references/feature/submit-acr.md +135 -0
  115. package/comate-engine/assets/skills/icode-comate/references/feature/submit-cr.md +123 -0
  116. package/comate-engine/assets/skills/icode-comate/references/git/clone.md +67 -0
  117. package/comate-engine/assets/skills/icode-comate/references/git/icode-git.md +68 -0
  118. package/comate-engine/assets/skills/icode-comate/references/git/push.md +64 -0
  119. package/comate-engine/assets/skills/icode-comate/references/git/push_cr.md +103 -0
  120. package/comate-engine/assets/skills/icode-comate/references/install.md +144 -0
  121. package/comate-engine/assets/skills/icode-comate/references/login.md +111 -0
  122. package/comate-engine/assets/skills/icode-comate/scripts/add-reviewer.sh +154 -0
  123. package/comate-engine/assets/skills/icode-comate/scripts/common.sh +145 -0
  124. package/comate-engine/assets/skills/icode-comate/scripts/fix-machine-check.sh +131 -0
  125. package/comate-engine/assets/skills/icode-comate/scripts/merge-cr.sh +105 -0
  126. package/comate-engine/assets/skills/icode-comate/scripts/ssh-setup.sh +159 -0
  127. package/comate-engine/assets/skills/icode-comate/scripts/submit-acr.sh +236 -0
  128. package/comate-engine/assets/skills/icode-comate/scripts/submit-cr.sh +104 -0
  129. package/comate-engine/assets/skills/icode-comate/scripts/test-preflight.sh +89 -0
  130. package/comate-engine/assets/skills/ku-operator-comate/SKILL.md +121 -0
  131. package/comate-engine/assets/skills/ku-operator-comate/examples.md +190 -0
  132. package/comate-engine/assets/skills/ku-operator-comate/references/add_member.md +49 -0
  133. package/comate-engine/assets/skills/ku-operator-comate/references/change_scope.md +38 -0
  134. package/comate-engine/assets/skills/ku-operator-comate/references/copy_doc.md +50 -0
  135. package/comate-engine/assets/skills/ku-operator-comate/references/create_doc.md +61 -0
  136. package/comate-engine/assets/skills/ku-operator-comate/references/delete_doc.md +31 -0
  137. package/comate-engine/assets/skills/ku-operator-comate/references/edit_content.md +568 -0
  138. package/comate-engine/assets/skills/ku-operator-comate/references/move_doc.md +45 -0
  139. package/comate-engine/assets/skills/ku-operator-comate/references/query_comment.md +79 -0
  140. package/comate-engine/assets/skills/ku-operator-comate/references/query_content.md +83 -0
  141. package/comate-engine/assets/skills/ku-operator-comate/references/query_flowchart.md +84 -0
  142. package/comate-engine/assets/skills/ku-operator-comate/references/query_permission.md +38 -0
  143. package/comate-engine/assets/skills/ku-operator-comate/references/query_recent_view.md +67 -0
  144. package/comate-engine/assets/skills/ku-operator-comate/references/query_repo.md +57 -0
  145. package/comate-engine/assets/skills/ku-operator-comate/references/query_user_info.md +37 -0
  146. package/comate-engine/assets/skills/ku-operator-comate/references/update_member.md +41 -0
  147. package/comate-engine/assets/skills/ku-operator-comate/references/upload_attachment.md +52 -0
  148. package/comate-engine/assets/skills/ku-operator-comate/scripts/ku_operator.py +1575 -0
  149. package/comate-engine/node_modules/better-sqlite3/node_modules/.bin/prebuild-install +2 -2
  150. package/comate-engine/node_modules/tree-sitter-bash/node_modules/.bin/node-gyp-build +2 -2
  151. package/comate-engine/node_modules/tree-sitter-bash/node_modules/.bin/node-gyp-build-optional +2 -2
  152. package/comate-engine/node_modules/tree-sitter-bash/node_modules/.bin/node-gyp-build-test +2 -2
  153. package/comate-engine/package.json +2 -0
  154. package/comate-engine/server.js +263 -79
  155. package/dist/bundle/index.js +8 -8
  156. package/package.json +1 -1
  157. package/comate-engine/assets/skills/figma2code-comate/codeConnect.md +0 -37
  158. package/comate-engine/assets/skills/figma2code-comate/designToken.md +0 -3
  159. package/comate-engine/assets/skills/figma2code-comate/f2cMcp.md +0 -59
  160. package/comate-engine/assets/skills/smart-commit/SKILL.md +0 -646
  161. package/comate-engine/node_modules/@comate/plugin-host/dist/index-AZIho4HV.js +0 -1
  162. package/comate-engine/node_modules/@comate/plugin-host/dist/user-BIpzRUfb.js +0 -44
  163. /package/comate-engine/assets/skills/{smart-commit → auto-commit-comate}/references/issue_type_mapping.json +0 -0
  164. /package/comate-engine/assets/skills/{smart-commit → auto-commit-comate}/references/query_reference.md +0 -0
  165. /package/comate-engine/assets/skills/{smart-commit → auto-commit-comate}/scripts/compat.py +0 -0
  166. /package/comate-engine/assets/skills/{smart-commit → auto-commit-comate}/scripts/create_card_cli.py +0 -0
  167. /package/comate-engine/assets/skills/{smart-commit → auto-commit-comate}/scripts/icafe/__init__.py +0 -0
  168. /package/comate-engine/assets/skills/{smart-commit → auto-commit-comate}/scripts/logger.py +0 -0
  169. /package/comate-engine/assets/skills/{smart-commit → auto-commit-comate}/scripts/recognize_card_cli.py +0 -0
@@ -0,0 +1,444 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 漏洞扫描工具 - 将项目代码上传至 Comate 安全服务端进行 SAST 扫描。
4
+
5
+ 用法:
6
+ python3 scan_vulnerability.py --root-path <项目目录> --username <用户名>
7
+
8
+ 流程:
9
+ 1. 获取扫描配置(支持的文件类型)
10
+ 2. 遍历项目目录,收集文件哈希
11
+ 3. 创建 bundle 并上传缺失文件
12
+ 4. 发起扫描并轮询结果
13
+ 5. 输出 SARIF 格式漏洞报告
14
+ """
15
+
16
+ import argparse
17
+ import json
18
+ import logging
19
+ import os
20
+ import re
21
+ import sys
22
+ import time
23
+
24
+ import http_client # noqa: F401 (triggers shared logging config)
25
+ import utils
26
+
27
+ logger = logging.getLogger("scan")
28
+
29
+ # 扫描轮询最大次数(每次间隔 3 秒,约 15 分钟)
30
+ MAX_SCAN_POLLS = 300
31
+ SCAN_POLL_INTERVAL = 3
32
+ # AI 误报分析等待超时(秒,默认 20 分钟)
33
+ AI_ANALYSIS_TIMEOUT = 1200
34
+
35
+
36
+ def parse_gitignore(gitignore_path):
37
+ # type: (str) -> tuple
38
+ """解析 .gitignore 文件,返回 (ignore_patterns, negation_patterns) 正则表达式元组。
39
+
40
+ 支持:
41
+ - `**` 匹配任意多级目录
42
+ - `!` 取消忽略
43
+ - `*` / `?` 通配符
44
+ - `/` 开头锚定根目录
45
+ - `/` 结尾匹配目录
46
+ """
47
+ ignore_patterns = []
48
+ negation_patterns = []
49
+ try:
50
+ with open(gitignore_path, "r", encoding="utf-8") as f:
51
+ for line in f:
52
+ line = line.strip()
53
+ if not line or line.startswith("#"):
54
+ continue
55
+
56
+ is_negation = line.startswith("!")
57
+ if is_negation:
58
+ line = line[1:]
59
+
60
+ regex = line
61
+ regex = regex.replace(".", r"\.")
62
+ # 先处理 ** 再处理 *,避免 ** 被错误替换
63
+ regex = regex.replace("/**/", r"(?:/|/.+/)")
64
+ regex = regex.replace("**/", r"(?:|.+/)")
65
+ regex = regex.replace("/**", r"(?:|.*)")
66
+ regex = regex.replace("**", r".*")
67
+ regex = regex.replace("*", "[^/]*")
68
+ regex = regex.replace("?", "[^/]")
69
+
70
+ if line.startswith("/"):
71
+ regex = "^" + regex[1:]
72
+ if line.endswith("/"):
73
+ regex = regex[:-1] + r"($|/.*)"
74
+
75
+ compiled = re.compile(regex)
76
+ if is_negation:
77
+ negation_patterns.append(compiled)
78
+ else:
79
+ ignore_patterns.append(compiled)
80
+ except FileNotFoundError:
81
+ pass
82
+ return ignore_patterns, negation_patterns
83
+
84
+
85
+ def is_ignored(path, gitignore_patterns, root_dir):
86
+ # type: (str, tuple, str) -> bool
87
+ rel_path = os.path.relpath(path, root_dir)
88
+ ignore_patterns, negation_patterns = gitignore_patterns
89
+ # 先检查是否被忽略
90
+ matched = any(p.search(rel_path) for p in ignore_patterns)
91
+ if not matched:
92
+ return False
93
+ # 再检查是否被取消忽略
94
+ if any(p.search(rel_path) for p in negation_patterns):
95
+ return False
96
+ return True
97
+
98
+
99
+ def walk_dir(directory, extensions=None, gitignore_patterns=None):
100
+ # type: (str, list | None, list | None) -> list
101
+ """递归遍历目录,按扩展名过滤并排除 .gitignore 中的文件。"""
102
+ files = []
103
+ resolved_dir = os.path.realpath(directory)
104
+ for root, dirs, filenames in os.walk(resolved_dir):
105
+ # 跳过常见大目录
106
+ dirs[:] = [d for d in dirs if d not in utils.SKIP_DIRS]
107
+
108
+ if gitignore_patterns:
109
+ dirs[:] = [
110
+ d for d in dirs
111
+ if not is_ignored(os.path.join(root, d), gitignore_patterns, resolved_dir)
112
+ ]
113
+ for name in filenames:
114
+ full_path = os.path.join(root, name)
115
+ # 跳过过大的文件
116
+ try:
117
+ if os.path.getsize(full_path) > utils.MAX_FILE_SIZE:
118
+ continue
119
+ except OSError:
120
+ continue
121
+ if gitignore_patterns and is_ignored(full_path, gitignore_patterns, resolved_dir):
122
+ continue
123
+ if extensions:
124
+ if not any(name.lower().endswith(ext.lower()) for ext in extensions):
125
+ continue
126
+ files.append(full_path)
127
+ return files
128
+
129
+
130
+ def get_settings(username, user_id):
131
+ # type: (str, str) -> dict
132
+ """获取扫描配置。"""
133
+ return http_client.get(
134
+ "{}/api/v2/analysis/settings".format(utils.HOST),
135
+ headers=utils.build_headers(username, user_id),
136
+ )
137
+
138
+
139
+ def create_bundle(file_hashes, username, user_id):
140
+ # type: (dict, str, str) -> tuple
141
+ """创建扫描 bundle,返回 (bundle_hash, missing_files)。"""
142
+ data = http_client.post(
143
+ "{}/api/v1/bundle".format(utils.HOST),
144
+ headers=utils.build_headers(username, user_id),
145
+ json_body=file_hashes,
146
+ )
147
+
148
+ if isinstance(data.get("data"), list):
149
+ return data.get("bundleHash", ""), data["data"]
150
+ return (
151
+ data.get("data", {}).get("bundleHash", ""),
152
+ data.get("data", {}).get("missingFiles", []),
153
+ )
154
+
155
+
156
+ def upload_files(bundle_hash, root_path, missing_files, file_hashes, username, user_id, upload_type="scan"):
157
+ # type: (str, str, list, dict, str, str, str) -> tuple
158
+ """上传缺失文件,返回 (bundle_hash, remaining_missing_files)。"""
159
+ payload = {"files": {}}
160
+ for name in missing_files:
161
+ file_path = os.path.join(root_path, name)
162
+ try:
163
+ content = utils.read_file_content(file_path)
164
+ payload["files"][name] = {"hash": file_hashes.get(name, ""), "content": content}
165
+ except Exception as e:
166
+ print("警告: 读取文件失败 {}: {}".format(file_path, e), file=sys.stderr)
167
+
168
+ if upload_type == "scan":
169
+ url = "{}/api/v1/bundle/{}".format(utils.HOST, bundle_hash)
170
+ else:
171
+ url = "{}/api/v1/upload".format(utils.HOST)
172
+
173
+ data = http_client.put(url, headers=utils.build_headers(username, user_id), json_body=payload)
174
+
175
+ if isinstance(data.get("data"), list):
176
+ return data.get("bundleHash", bundle_hash), data["data"]
177
+ return (
178
+ data.get("data", {}).get("bundleHash", bundle_hash),
179
+ data.get("data", {}).get("missingFiles", []),
180
+ )
181
+
182
+
183
+ def scan_init(root_path, username, user_id):
184
+ # type: (str, str, str) -> tuple
185
+ """初始化扫描:获取配置、收集文件、创建 bundle、上传文件。返回 (bundle_hash, file_hashes)。"""
186
+ # 获取配置
187
+ cfg = get_settings(username, user_id)
188
+ scan_config = cfg.get("data", {}).get("scanConfiguration", {})
189
+ extensions = list(
190
+ set(scan_config.get("sca", {}).get("supportedLanguages", []))
191
+ | set(scan_config.get("sast", {}).get("supportedLanguages", []))
192
+ )
193
+
194
+ # 解析 .gitignore
195
+ gitignore_path = os.path.join(root_path, ".gitignore")
196
+ gitignore_patterns = parse_gitignore(gitignore_path)
197
+
198
+ # 遍历目录,收集文件哈希
199
+ all_files = walk_dir(root_path, extensions, gitignore_patterns)
200
+ file_hashes = {} # type: dict
201
+ for f in all_files:
202
+ rel = os.path.relpath(f, root_path)
203
+ try:
204
+ file_hashes[rel] = utils.calc_sha256(f)
205
+ except Exception as e:
206
+ print("警告: 计算哈希失败 {}: {}".format(f, e), file=sys.stderr)
207
+
208
+ print("收集文件: {} 个".format(len(file_hashes)), file=sys.stderr)
209
+
210
+ # 创建 bundle
211
+ bundle_hash, missing_files = create_bundle(file_hashes, username, user_id)
212
+ print("Bundle: {}, 待上传: {} 个文件".format(bundle_hash, len(missing_files)), file=sys.stderr)
213
+
214
+ # 循环上传缺失文件
215
+ upload_retry = 0
216
+ MAX_UPLOAD_RETRIES = 10
217
+ while missing_files:
218
+ bundle_hash, missing_files = upload_files(
219
+ bundle_hash, root_path, missing_files, file_hashes, username, user_id, "scan"
220
+ )
221
+ upload_retry += 1
222
+ if missing_files:
223
+ print("剩余待上传: {} 个文件 (重试 {}/{})".format(len(missing_files), upload_retry,
224
+ MAX_UPLOAD_RETRIES), file=sys.stderr)
225
+ if upload_retry >= MAX_UPLOAD_RETRIES:
226
+ print("错误: 上传重试 {} 次后仍有 {} 个文件未上传".format(MAX_UPLOAD_RETRIES, len(missing_files)), file=sys.stderr)
227
+ sys.exit(1)
228
+
229
+ return bundle_hash, file_hashes
230
+
231
+
232
+ def get_analyzing_count(result):
233
+ # type: (dict) -> int
234
+ """统计处于 AI 分析中状态的漏洞数量。aiAnalysisStatus: 0-无需分析, 1-分析中, 2-真实漏洞, 3-误报"""
235
+ data = result.get("data", {})
236
+ runs = data.get("sarif", {}).get("runs") or data.get("runs") or []
237
+ count = 0
238
+ for run in runs:
239
+ results = run.get("results") or run.get("result") or []
240
+ for r in results:
241
+ status = r.get("properties", {}).get("aiAnalysisStatus", 0)
242
+ if status == 1:
243
+ count += 1
244
+ return count
245
+
246
+
247
+ def _poll_ai_analysis(scan_info, chat_id, username, user_id, ai_analysis_timeout):
248
+ # type: (dict, str, str, str, int) -> dict
249
+ """轮询等待 AI 分析完成,返回最新结果。"""
250
+ start_time = time.time()
251
+ poll_interval = 10
252
+
253
+ while True:
254
+ elapsed = time.time() - start_time
255
+ if elapsed >= ai_analysis_timeout:
256
+ print("\nAI 分析等待超时(已等待 {} 分钟),将使用当前结果继续".format(
257
+ int(elapsed // 60)), file=sys.stderr)
258
+ logger.warning("AI analysis timeout after %d seconds", int(elapsed))
259
+ break
260
+
261
+ time.sleep(poll_interval)
262
+
263
+ result = http_client.post(
264
+ "{}/api/v2/analysis".format(utils.HOST),
265
+ headers=utils.build_headers(username, user_id, chat_id),
266
+ json_body=scan_info,
267
+ )
268
+
269
+ analyzing_count = get_analyzing_count(result)
270
+ if analyzing_count == 0:
271
+ print("\nAI 分析完成!", file=sys.stderr)
272
+ logger.info("AI analysis completed after %d seconds", int(time.time() - start_time))
273
+ return result
274
+
275
+ elapsed_min = int(elapsed // 60)
276
+ elapsed_sec = int(elapsed % 60)
277
+ print("AI 分析中... 剩余 {} 个漏洞待分析(已等待 {}分{}秒)".format(
278
+ analyzing_count, elapsed_min, elapsed_sec), file=sys.stderr)
279
+
280
+ return result
281
+
282
+
283
+ def scan_vulnerability(root_path, chat_id="", username="", user_id="", wait_ai=True):
284
+ # type: (str, str, str, str, bool) -> tuple
285
+ """执行漏洞扫描,返回 (SARIF 格式结果, bundleHash)。
286
+
287
+ Args:
288
+ root_path: 项目根目录
289
+ chat_id: 对话 ID
290
+ username: Comate 用户名
291
+ user_id: 用户 ID
292
+ wait_ai: 是否等待 AI 分析完成,False 则扫描完成后立即返回
293
+ """
294
+ bundle_hash, file_hashes = scan_init(root_path, username, user_id)
295
+
296
+ git_info = utils.get_git_info(root_path)
297
+ scan_info = {
298
+ "key": {"hash": bundle_hash, "type": "file"},
299
+ "scan": 3,
300
+ "analysisContext": {
301
+ "initiator": "",
302
+ "trigger": "manual",
303
+ "workspaceName": "",
304
+ "workspacePath": "",
305
+ "gitInfo": [{
306
+ "path": "",
307
+ "gitURL": git_info["gitURL"],
308
+ "gitBranch": git_info["gitBranch"],
309
+ "gitCommitID": git_info["gitCommitID"],
310
+ }],
311
+ },
312
+ }
313
+
314
+ headers = utils.build_headers(username, user_id, chat_id)
315
+
316
+ print("开始扫描...", file=sys.stderr)
317
+ logger.info("scan start: chat_id=%s, bundle_hash=%s", chat_id, bundle_hash)
318
+
319
+ # 第一阶段:等待扫描完成
320
+ poll_count = 0
321
+ while poll_count < MAX_SCAN_POLLS:
322
+ result = http_client.post(
323
+ "{}/api/v2/analysis".format(utils.HOST), headers=headers, json_body=scan_info
324
+ )
325
+ if result.get("status") != 1:
326
+ break
327
+ poll_count += 1
328
+ print("扫描中,等待结果... ({}/{})".format(poll_count, MAX_SCAN_POLLS), file=sys.stderr)
329
+ time.sleep(SCAN_POLL_INTERVAL)
330
+
331
+ if result.get("status") == 1:
332
+ print("错误: 扫描超时,已轮询 {} 次仍未完成".format(MAX_SCAN_POLLS), file=sys.stderr)
333
+ return {"status": -1, "message": "扫描超时"}, bundle_hash
334
+
335
+ # 第二阶段:等待 AI 分析完成(可选)
336
+ analyzing_count = get_analyzing_count(result)
337
+ if analyzing_count > 0:
338
+ if wait_ai:
339
+ print("\n检测到 {} 个漏洞正在进行 AI 误报分析,等待分析完成...".format(analyzing_count), file=sys.stderr)
340
+ result = _poll_ai_analysis(scan_info, chat_id, username, user_id, AI_ANALYSIS_TIMEOUT)
341
+ else:
342
+ print("\n扫描完成,{} 个漏洞正在 AI 分析中(已跳过等待)".format(analyzing_count), file=sys.stderr)
343
+
344
+ return result, bundle_hash
345
+
346
+
347
+ def wait_ai_analysis(root_path, scan_result_path, chat_id="", username="", user_id=""):
348
+ # type: (str, str, str, str, str) -> tuple
349
+ """仅等待 AI 分析完成,不重新扫描。从已有 scan_result.json 恢复上下文。"""
350
+ with open(scan_result_path, "r", encoding="utf-8") as f:
351
+ result = json.load(f)
352
+
353
+ bundle_hash = result.get("bundleHash", "")
354
+ if not bundle_hash:
355
+ print("错误: scan_result.json 中未找到 bundleHash", file=sys.stderr)
356
+ sys.exit(1)
357
+
358
+ analyzing_count = get_analyzing_count(result)
359
+ if analyzing_count == 0:
360
+ print("所有漏洞 AI 分析已完成,无需等待", file=sys.stderr)
361
+ return result, bundle_hash
362
+
363
+ git_info = utils.get_git_info(root_path)
364
+ scan_info = {
365
+ "key": {"hash": bundle_hash, "type": "file"},
366
+ "scan": 3,
367
+ "analysisContext": {
368
+ "initiator": "",
369
+ "trigger": "manual",
370
+ "workspaceName": "",
371
+ "workspacePath": "",
372
+ "gitInfo": [{
373
+ "path": "",
374
+ "gitURL": git_info["gitURL"],
375
+ "gitBranch": git_info["gitBranch"],
376
+ "gitCommitID": git_info["gitCommitID"],
377
+ }],
378
+ },
379
+ }
380
+
381
+ print("等待 {} 个漏洞的 AI 分析完成...".format(analyzing_count), file=sys.stderr)
382
+ result = _poll_ai_analysis(scan_info, chat_id, username, user_id, AI_ANALYSIS_TIMEOUT)
383
+ result["bundleHash"] = bundle_hash
384
+ return result, bundle_hash
385
+
386
+
387
+ def main():
388
+ parser = argparse.ArgumentParser(description="代码安全漏洞扫描工具")
389
+ parser.add_argument("--root-path", required=True, help="待扫描项目根目录")
390
+ parser.add_argument("--username", required=True, help="Comate 用户名")
391
+ parser.add_argument("--chat-id", default="", help="对话唯一标识 (COMATE_SESSION_ID)")
392
+ parser.add_argument("--output-dir", default=None, help="结果输出目录,默认为 skill 临时目录")
393
+ parser.add_argument("--no-wait-ai", action="store_true",
394
+ help="扫描完成后不等待 AI 分析,立即返回结果")
395
+ parser.add_argument("--wait-ai-only", action="store_true",
396
+ help="仅等待 AI 分析完成(需配合 --scan-result 使用)")
397
+ parser.add_argument("--scan-result", default=None,
398
+ help="已有扫描结果文件路径(与 --wait-ai-only 配合使用)")
399
+ args = parser.parse_args()
400
+
401
+ user_id = utils.make_user_id(args.username)
402
+
403
+ logger.info("scan_vulnerability start: chat_id=%s, username=%s, root_path=%s",
404
+ args.chat_id, args.username, args.root_path)
405
+
406
+ root_path = os.path.realpath(args.root_path)
407
+ if not os.path.isdir(root_path):
408
+ print("错误: 目录不存在 {}".format(root_path), file=sys.stderr)
409
+ sys.exit(1)
410
+
411
+ if args.wait_ai_only:
412
+ # --wait-ai-only 模式:仅等待 AI 分析,不重新扫描
413
+ if not args.scan_result:
414
+ print("错误: --wait-ai-only 需要配合 --scan-result 使用", file=sys.stderr)
415
+ sys.exit(1)
416
+ result, bundle_hash = wait_ai_analysis(
417
+ root_path, args.scan_result, chat_id=args.chat_id,
418
+ username=args.username, user_id=user_id,
419
+ )
420
+ # 原地更新 scan_result.json
421
+ output_file = os.path.realpath(args.scan_result)
422
+ with open(output_file, "w", encoding="utf-8") as f:
423
+ json.dump(result, f, ensure_ascii=False, indent=2)
424
+ print(output_file)
425
+ else:
426
+ # 正常扫描模式
427
+ output_dir = utils.get_output_dir(root_path, args.output_dir)
428
+
429
+ result, bundle_hash = scan_vulnerability(
430
+ root_path, chat_id=args.chat_id, username=args.username, user_id=user_id,
431
+ wait_ai=not args.no_wait_ai,
432
+ )
433
+
434
+ # 将 bundleHash 写入结果,供后续解析和修复使用
435
+ result["bundleHash"] = bundle_hash
436
+
437
+ output_file = os.path.join(output_dir, "scan_result.json")
438
+ with open(output_file, "w", encoding="utf-8") as f:
439
+ json.dump(result, f, ensure_ascii=False, indent=2)
440
+ print(output_file)
441
+
442
+
443
+ if __name__ == "__main__":
444
+ main()
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 公共工具函数 - 各脚本共享的常量和辅助函数。
4
+ """
5
+
6
+ import base64
7
+ import hashlib
8
+ import os
9
+ import shutil
10
+ import subprocess
11
+ import time
12
+ import uuid
13
+ from typing import Optional
14
+
15
+ HOST = "https://comate-sec.baidu-int.com"
16
+ WS_HOST = "wss://comate-sec.baidu-int.com"
17
+
18
+ # 常见应跳过的大目录(即使 .gitignore 未列出)
19
+ SKIP_DIRS = {".git", ".svn", ".hg", "node_modules", "__pycache__", "vendor", ".idea", ".vscode"}
20
+
21
+ # 单文件最大扫描大小(10MB)
22
+ MAX_FILE_SIZE = 10 * 1024 * 1024
23
+
24
+ # 临时数据过期时间(秒),超过此时间的 .tmp 子目录将被自动清理
25
+ TMP_EXPIRE_SECONDS = 24 * 60 * 60 # 24 小时
26
+
27
+
28
+ def calc_sha256(file_path):
29
+ # type: (str) -> str
30
+ """计算文件 SHA256 哈希。"""
31
+ sha256_hash = hashlib.sha256()
32
+ with open(file_path, "rb") as f:
33
+ for chunk in iter(lambda: f.read(4096), b""):
34
+ sha256_hash.update(chunk)
35
+ return sha256_hash.hexdigest()
36
+
37
+
38
+ def calc_sha256_safe(file_path):
39
+ # type: (str) -> str
40
+ """计算文件 SHA256 哈希,失败返回空字符串。"""
41
+ try:
42
+ return calc_sha256(file_path)
43
+ except Exception:
44
+ return ""
45
+
46
+
47
+ def read_file_content(file_path):
48
+ # type: (str) -> str
49
+ """读取文件内容,二进制文件用 base64 编码。"""
50
+ try:
51
+ with open(file_path, "r", encoding="utf-8") as f:
52
+ return f.read()
53
+ except UnicodeDecodeError:
54
+ with open(file_path, "rb") as f:
55
+ return base64.b64encode(f.read()).decode("ascii")
56
+
57
+
58
+ def make_user_id(username):
59
+ # type: (str) -> str
60
+ """从用户名生成 USER_ID(MD5 前 12 位)。"""
61
+ return hashlib.md5(username.encode("utf-8")).hexdigest()[:12]
62
+
63
+
64
+ def build_headers(username, user_id, chat_id=""):
65
+ # type: (str, str, str) -> dict
66
+ """构建统一的 HTTP 请求头。"""
67
+ headers = {
68
+ "Comate-Username": username,
69
+ "Comate-User-Id": user_id,
70
+ "SAST-Request-ID": str(uuid.uuid4()),
71
+ }
72
+ if chat_id:
73
+ headers["SAST-Chat-ID"] = chat_id
74
+ return headers
75
+
76
+
77
+ def get_git_info(root_path):
78
+ # type: (str) -> dict
79
+ """从项目目录获取 git 信息(URL、分支、commitID)。"""
80
+ info = {"gitURL": "", "gitBranch": "", "gitCommitID": ""}
81
+ try:
82
+ info["gitURL"] = subprocess.check_output(
83
+ ["git", "remote", "get-url", "origin"], cwd=root_path, stderr=subprocess.DEVNULL
84
+ ).decode("utf-8").strip()
85
+ except Exception:
86
+ pass
87
+ try:
88
+ info["gitBranch"] = subprocess.check_output(
89
+ ["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=root_path, stderr=subprocess.DEVNULL
90
+ ).decode("utf-8").strip()
91
+ except Exception:
92
+ pass
93
+ try:
94
+ info["gitCommitID"] = subprocess.check_output(
95
+ ["git", "rev-parse", "HEAD"], cwd=root_path, stderr=subprocess.DEVNULL
96
+ ).decode("utf-8").strip()
97
+ except Exception:
98
+ pass
99
+ return info
100
+
101
+
102
+ def get_output_dir(root_path, output_dir=None):
103
+ # type: (str, Optional[str]) -> str
104
+ """获取输出目录,默认为 skill 临时目录下的项目隔离子目录。
105
+
106
+ 每次调用时自动清理超过 24 小时的过期临时数据。
107
+ """
108
+ skill_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
109
+ project_name = os.path.basename(os.path.realpath(root_path))
110
+ path_hash = hashlib.md5(os.path.realpath(root_path).encode("utf-8")).hexdigest()[:8]
111
+ default_output = os.path.join(skill_dir, ".tmp", "{}_{}".format(project_name, path_hash))
112
+ result = os.path.realpath(output_dir) if output_dir else default_output
113
+
114
+ # 清理过期临时数据
115
+ _cleanup_expired_tmp(skill_dir)
116
+
117
+ os.makedirs(result, exist_ok=True)
118
+ return result
119
+
120
+
121
+ def _cleanup_expired_tmp(skill_dir):
122
+ # type: (str) -> None
123
+ """清理 .tmp 目录下超过 24 小时的子目录。"""
124
+ tmp_dir = os.path.join(skill_dir, ".tmp")
125
+ if not os.path.isdir(tmp_dir):
126
+ return
127
+
128
+ now = time.time()
129
+ try:
130
+ for entry in os.listdir(tmp_dir):
131
+ entry_path = os.path.join(tmp_dir, entry)
132
+ if not os.path.isdir(entry_path):
133
+ continue
134
+ try:
135
+ mtime = os.path.getmtime(entry_path)
136
+ if now - mtime > TMP_EXPIRE_SECONDS:
137
+ shutil.rmtree(entry_path)
138
+ except OSError:
139
+ pass
140
+ except OSError:
141
+ pass
142
+
143
+
144
+ def safe_rmtree(dir_path):
145
+ # type: (str) -> None
146
+ """安全删除临时目录,校验路径位于 .tmp 下防止误删。"""
147
+ dir_path = os.path.realpath(dir_path)
148
+ # 安全校验:确保路径在 .tmp 目录下
149
+ if ".tmp" not in dir_path.split(os.sep):
150
+ return
151
+ if not os.path.isdir(dir_path):
152
+ return
153
+ shutil.rmtree(dir_path)