@comate/zulu 1.4.0 → 1.4.2

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 (21) hide show
  1. package/comate-engine/assets/skills/code-security/SKILL.md +254 -63
  2. package/comate-engine/assets/skills/code-security/references/credential_hosting.md +263 -97
  3. package/comate-engine/assets/skills/code-security/references/framework_detection.md +91 -0
  4. package/comate-engine/assets/skills/code-security/references/vul_repair_sensitive.md +143 -74
  5. package/comate-engine/assets/skills/code-security/scripts/credential_hosting.py +12 -3
  6. package/comate-engine/assets/skills/code-security/scripts/credential_open_page.py +3 -3
  7. package/comate-engine/assets/skills/code-security/scripts/credential_poll.py +1 -1
  8. package/comate-engine/assets/skills/code-security/scripts/credential_print_url.py +43 -0
  9. package/comate-engine/assets/skills/code-security/scripts/credential_url.py +22 -2
  10. package/comate-engine/assets/skills/code-security/scripts/dodo/dodo_session.sh +183 -0
  11. package/comate-engine/assets/skills/code-security/scripts/ducc/get_claude_session_id.sh +8 -4
  12. package/comate-engine/assets/skills/code-security/scripts/ducc/open_browser.py +15 -18
  13. package/comate-engine/assets/skills/code-security/scripts/http_client.py +2 -3
  14. package/comate-engine/assets/skills/code-security/scripts/repair_vulnerability.py +3 -3
  15. package/comate-engine/assets/skills/code-security/scripts/report_chat.py +2 -2
  16. package/comate-engine/assets/skills/code-security/scripts/scan_vulnerability.py +7 -7
  17. package/comate-engine/assets/skills/code-security/scripts/utils.py +26 -11
  18. package/comate-engine/package.json +2 -0
  19. package/comate-engine/server.js +27 -27
  20. package/dist/bundle/index.js +6 -6
  21. package/package.json +1 -1
@@ -1,23 +1,29 @@
1
1
  # 敏感信息硬编码漏洞修复流程
2
2
 
3
- 修复漏洞分为以下三个步骤:
4
- 1. 读取漏洞报告
5
- 2. 修复硬编码漏洞
6
- 3. 引入SDK(如果需要)
3
+ 修复漏洞分为以下三个步骤,**必须按序执行,每步完成后再进入下一步**:
7
4
 
8
- ## 读取漏洞报告
5
+ 1. **读取配置数据**:解析 `poll_result.json`,提取 `deployment.platform`、`deployment.credential_id`、`files[].name`、`files[].vulList[]`(含 `line`、`extra.secret.credentialName`、`extra.secret.start/end`);**禁止凭印象跳过读取**
6
+ 2. **修复硬编码漏洞**:按文件遍历,每个文件内按 `line` **降序**处理漏洞;同行多漏洞按 `start.col` **降序**替换;每个漏洞**独立**依据 `credentialName` 是否为空决定托管或删除方式;**仅替换 `[start.col, end.col]` 范围内字符**,行内其余内容一律不动;修复后重新读取文件逐项校验
7
+ 3. **【强制执行】引入 SDK**:显式检查 `deployment.platform` 的值——`== 4` 时按第 3 节引入 keyless-sdk(`credentialID` 必须与 `deployment.credential_id` 完全一致,已有则不重复引入);`!= 4` 时明确跳过,**不得静默忽略此步骤**
8
+
9
+ > **稳定性总原则**:
10
+ > - **只改必要内容**:仅基于 `extra.secret.start` / `extra.secret.end` 精确定位敏感信息进行替换,禁止改动同一行其他字符(包括空格、引号、注释、分隔符)。
11
+ > - **白名单修复**:只修复 `poll_result.json` 中明确列出的漏洞,禁止顺带"修复"其他疑似硬编码。
12
+ > - **修复顺序**:同一文件内的多个漏洞,**按 `line` 降序处理**,避免前面的修改导致后续行号偏移;不同文件之间互不干扰。
13
+ > - **每个漏洞独立判断**:必须依据各自的 `credentialName` 决定修复方式,禁止基于内容相似性批量推断。
14
+ > - **修复后校验**:修复完毕重新读取文件,逐项确认(a)目标位置已替换;(b)非目标行/列未被改动;(c)文件整体语法/缩进合法。
15
+
16
+ ## 1. 读取漏洞修复配置数据
17
+
18
+ 基于凭证配置网页回传的 `poll_result.json` 数据获取漏洞修复配置,示例如下:
9
19
 
10
- 基于凭证配置网页回传的数据获取漏洞报告,示例如下:
11
20
  ```json
12
21
  {
13
22
  "scanChatID": "从 credential_poll 返回的 chatUUID",
14
- "repo": {
15
- "language": "java",
16
- "framework": "springboot"
17
- },
18
23
  "deployment": {
19
24
  "platform": 2,
20
- "platformName": "ipipe"
25
+ "platformName": "ipipe",
26
+ "credential_id": "appDemo_prod"
21
27
  },
22
28
  "files": [
23
29
  {
@@ -43,68 +49,119 @@
43
49
  "type": 4
44
50
  }
45
51
  ```
52
+
46
53
  各字段说明如下:
47
- * repo:代码库信息
48
- * language:代码语言
49
- * framework:代码框架
50
- * deployment:部署信息
51
- * platform:部署平台,1 表示 cnap,2 表示 ipipe,3 表示 opera,4 表示 datamanage
52
- * platformName:部署平台名称(如 ipipe、cnap、opera、datamanage)
53
- * credential_id:凭证 ID(platform 为 4 时使用,用于引入 keyless-sdk)
54
- * files:文件信息
55
- * name:漏洞文件的相对路径
56
- * vulList:漏洞列表
57
- * extra.secret.start:敏感信息的开始位置(行、列)
58
- * extra.secret.end:敏感信息的结束位置(行、列)
59
- * extra.secret.credentialName:建议使用的环境变量名称
60
-
61
- ## 修复漏洞
62
- 硬编码漏洞修复的本质就是改变原来在代码中的硬编码敏感信息写法,改为从环境变量中读取敏感信息。修复时遍历漏洞报告中的所有文件和漏洞,逐个修复,按以下流程进行处理。
63
-
64
- ### 代码文件
65
- 如果漏洞文件是代码文件,比如 java、go、php、py 等,则根据各语言的写法生成读取环境变量的代码,并替换掉原来的硬编码敏感信息,注意有以下几点要求:
66
- 1. 与环境变量读取无关的其他代码和字符不要做任何改动
67
- 2. 如果漏洞行是被引号包裹的纯字符串,输出时保留其结构不变,仍然可以直接执行字符串操作,但要避免空字符串的拼接
68
- 3. 不要给出环境变量默认值
69
- 4. 如果代码开头存在空格、tab等缩进,保留原始代码的缩进,不要清除两端的空白字符
70
- 5. 如果是 python 语言,使用 os.environ 的方式获取环境变量,而不是 os.getenv
71
- 6. 如果是 go 语言,不能在 const 中读取环境变量,在 const 下方重新定义读取代码,并删除 const 的硬编码的值
72
- 7. 如果是 java 语言,且 platform 4 时,则采用 System.getProperty 的语法读取变量
73
- 8. 如果原文件中缺失读取环境变量所需的第三方库,则自动引入,比如 python os 库,go 的 os 库等
74
-
75
- ### 配置文件
76
- 如果漏洞文件是配置文件,比如 yml、properties、ini 等,则结合语言和框架共同决定如何修改代码,修改原则如下:
77
- 1. 如果语言为 go,框架为 gdp,toml 后缀的文件直接使用特定的占位符替换敏感信息,占位符的环境变量名称根据 credential_name 命名,示例如下:
54
+
55
+ * `deployment`:部署信息
56
+ * `platform`:部署平台,1 表示 cnap,2 表示 ipipe,3 表示 opera,4 表示 datamanage
57
+ * `platformName`:部署平台名称(如 ipipe、cnap、opera、datamanage)
58
+ * `credential_id`:凭证 ID(platform 4 时使用,用于引入 keyless-sdk)
59
+ * `files`:文件信息
60
+ * `name`:漏洞文件的相对路径
61
+ * `vulList`:漏洞列表
62
+ * `line`:漏洞所在行号
63
+ * `extra.secret.start`:敏感信息的开始位置(行、列,列从 1 开始计数)
64
+ * `extra.secret.end`:敏感信息的结束位置(行、列)
65
+ * `extra.secret.credentialName`:建议使用的环境变量名称
66
+ * 不为空 → 修复方式为**托管**
67
+ * 为空 → 修复方式为**删除**
68
+
69
+ ## 2. 修复漏洞
70
+
71
+ 硬编码漏洞修复的本质是把代码中的硬编码敏感信息改为从环境变量读取(托管),或在不需要保留的场景下置空(删除)。
72
+
73
+ ### 2.1 通用修复流程
74
+
75
+ 1. **按文件聚合**:遍历 `files[]`,处理完一个文件再处理下一个。
76
+ 2. **按行倒序处理**:对当前文件的 `vulList[]` 按 `line` **降序**排序后再修复。
77
+ 3. **逐漏洞读取关键字段**:`line`、`extra.secret.credentialName`、`extra.secret.start`、`extra.secret.end`。
78
+ 4. **独立判断修复方式**:
79
+ - `credentialName` 不为空 **托管方式**(参考 2.2 / 2.3)
80
+ - `credentialName` 为空 **删除方式**(参考 2.4)
81
+ 5. **精确替换**:仅替换 `[start.col, end.col]` 范围内的字符,行内其余内容保持原样。
82
+ 6. **同行多漏洞**:若同一行存在多个漏洞,按 `start.col` **降序**依次替换,避免列偏移。
83
+ 7. **修复后校验**:重新读取文件,确认每个漏洞已按正确方式修复,且无误伤。
84
+
85
+ > **重要**:即使多行内容完全相同,也必须逐行依据各自的 `credentialName` 判断修复方式;不得基于内容相似性批量推断。仅当确认所有目标行的 `credentialName` 都相同且非空时,才可使用 `replace_all`。
86
+
87
+ > **白名单约束**:仅修复 `poll_result.json` 中列出的漏洞,文件中其他可疑硬编码一律不动。
88
+
89
+ ### 2.2 托管方式 - 代码文件
90
+
91
+ 如果漏洞文件是代码文件(如 java、go、php、py、js、c/c++ 等),根据语言生成读取环境变量的代码替换原硬编码值,注意:
92
+
93
+ 1. 与环境变量读取无关的其他代码、注释、空白字符**一律不动**。
94
+ 2. 如果漏洞行是被引号包裹的纯字符串字面量,输出时保留其结构与可执行语义,避免出现空字符串拼接(如 `"" + os.environ[...]`)。
95
+ 3. **不要给出环境变量默认值**。
96
+ 4. 保留原始代码的行首缩进(空格/Tab 数量与种类一致),不要清除两端空白字符。
97
+ 5. **Python**:使用 `os.environ['XXX']`,**不**使用 `os.getenv`。
98
+ 6. **Go**:不能在 `const` 中读取环境变量;将 `const` 中该值删除,并在 `const` 块下方用 `var` 重新定义并通过 `os.Getenv` 读取。
99
+ 7. **Java**:
100
+ - `platform == 4`:使用 `System.getProperty("XXX")`(与 keyless-sdk 注入方式一致)。
101
+ - 其他 platform:使用 `System.getenv("XXX")`。
102
+ 8. **依赖自动补齐**:若读取环境变量所需的标准库未引入,自动补全(如 Python 的 `os`、Go 的 `os`、C++ 的 `<cstdlib>` 等)。
103
+
104
+ ### 2.3 托管方式 - 配置文件
105
+
106
+ > **修复配置文件前**:先读取 `references/framework_detection.md`,识别配置文件的宿主语言和框架,再选择对应的占位符格式。
107
+
108
+ **占位符格式规则**(环境变量名取自 `credentialName`,**不写默认值**):
109
+
110
+ | 语言 / 框架 / 文件类型 | 占位符格式 | 示例 |
111
+ |---|---|---|
112
+ | Go + GDP 框架 + toml | `{env.XXX}` | `password={env.KL_PASSWORD}` |
113
+ | 其他语言 / 框架 / 配置文件(yml、yaml、properties、ini、toml、xml 等) | `${XXX}` | `password=${KL_PASSWORD}` |
114
+
115
+ 修复示例:
116
+
78
117
  ```toml
79
- // 修复前
118
+ # 修复前
80
119
  password=123123123
81
- // 修复后
82
- password=${env.KL_PASSWORD}
120
+ # 修复后(Go + GDP)
121
+ password={env.KL_PASSWORD}
83
122
  ```
84
- 2. 除第一种情况之外,其他语言、框架、配置文件类型,使用常规的环境变量占位符替换敏感信息,占位符的环境变量名称根据 credential_name 命名。
85
- ```toml
86
- // 修复前
87
- password=123123123
88
- // 修复后
89
- password=${KL_PASSWORD}
123
+
124
+ ```properties
125
+ # 修复前
126
+ spring.datasource.password=123123123
127
+ # 修复后(Spring Boot 等)
128
+ spring.datasource.password=${KL_SPRING_DATASOURCE_PASSWORD}
90
129
  ```
91
130
 
92
- 漏洞报告可能涉及多个文件,修复时按文件进行聚合,优先将一个文件中的漏洞全部修复完成再修复其他漏洞。
131
+ ### 2.4 删除方式(credentialName 为空)
132
+
133
+ 按文件类型采用合适的"清空"策略,**避免破坏文件语法**:
134
+
135
+ - **配置文件(properties/ini/yml/toml 等键值型)**:保留键和分隔符,仅清空值。
136
+ - 示例:`password=123123123` → `password=`
137
+ - YAML:`password: 123123123` → `password:`(或 `password: ""`,与文件原有空值风格一致)
138
+ - **代码文件中的字符串字面量**:将值清空为对应语言的空字符串字面量,保留引号与上下文语法。
139
+ - 示例:`String pwd = "123";` → `String pwd = "";`
140
+ - **代码文件中的非字符串字面量**:若直接置空会导致语法错误,则替换为该语言中合法的空值(如 Java 的 `""`、Go 的 `""`、Python 的 `""`),并附最小必要的类型保持。
141
+
142
+ 修复后必须确保文件可正常解析/编译。
143
+
144
+ ## 3. 引入 SDK(仅 platform == 4)
145
+
146
+ 仅当 `deployment.platform == 4` 时,才需要在项目中引入 keyless-sdk 并添加初始化代码——SDK 负责将凭证注入系统环境变量,使前面(步骤 2)的读取代码能取到值。其他 platform **不要**引入 SDK。
93
147
 
94
- ## 引入SDK
95
- 从第一个漏洞报告读取数据,如果 platform 为 4 时需要引入 keyless-sdk 并添加初始化代码。通过该 sdk 将凭证注入到环境变量中,然后其他位置的代码才能读取到环境变量。注意,只有平台为 4 时才需要引入 sdk,其他平台不需要。不同语言 sdk 及初始化代码添加方式如下。
148
+ 初始化代码中的 `credentialID` / `credential_id` / `CredentialID` 必须与 `deployment.credential_id` 完全一致。已有 SDK 依赖与初始化的项目,**不要重复引入**。
149
+
150
+ ### 3.1 Java
151
+
152
+ `pom.xml` 中添加依赖(保留注释):
96
153
 
97
- #### java 语言
98
- pom.xml 文件中添加 keyless-sdk 依赖,其中的注释需要保留。
99
154
  ```xml
100
155
  <!-- keyless-sdk dependency -->
101
156
  <dependency>
102
157
  <groupId>com.baidu.xbu-data</groupId>
103
158
  <artifactId>keyless-sdk</artifactId>
104
- <version>1.0.2</version>
159
+ <version>1.0.3</version>
105
160
  </dependency>
106
161
  ```
107
- 项目启动的函数中添加以下初始化代码,注意其中的 credentialID 需要与漏洞报告中的 credential_id 保持一致。
162
+
163
+ 在项目启动函数中添加初始化代码:
164
+
108
165
  ```java
109
166
  import com.baidu.keyless.KeylessClient;
110
167
 
@@ -129,14 +186,19 @@ public class KeylessAppCredentialDemo {
129
186
  }
130
187
  ```
131
188
 
132
- #### go 语言
133
- go.mod 文件中添加 keyless-sdk 依赖,其中的注释需要保留。
189
+ ### 3.2 Go
190
+
191
+ `go.mod` 中添加依赖(保留注释):
192
+ **require模块引入包和版本保持不变**
193
+
134
194
  ```mod
135
195
  require (
136
- icode.baidu.com/baidu/xbu-data/things-go keyless-sdk-2.2.0 // keyless-sdk dependency
196
+ icode.baidu.com/baidu/xbu-data/things-go v2.3.0 // keyless-sdk dependency
137
197
  )
138
198
  ```
139
- 项目启动的函数中添加以下初始化代码,注意其中的 CredentialID 需要与漏洞报告中的 credential_id 保持一致。
199
+
200
+ 在项目启动函数中添加初始化代码:
201
+
140
202
  ```go
141
203
  package main
142
204
 
@@ -163,17 +225,21 @@ func main() {
163
225
  }
164
226
  ```
165
227
 
166
- #### python 语言
167
- requirements.txt 文件中添加 keyless-sdk 依赖,其中的注释需要保留。
228
+ ### 3.3 Python
229
+
230
+ `requirements.txt` 中添加依赖(保留注释):
231
+
168
232
  ```txt
169
233
  // keyless-sdk dependency
170
- credential-vault-sdk>=0.1.12(Python3 版本添加此依赖)
171
- credential-vault-sdk-py2>=0.1.9(Python2 版本添加此依赖)
234
+ credential-vault-sdk>=0.1.15(Python3 版本添加此依赖)
235
+ credential-vault-sdk-py2>=0.1.12(Python2 版本添加此依赖)
172
236
  ```
173
- 初始化代码如下,注意
174
- * 其中的 credential_id 需要与漏洞报告中的 credential_id 保持一致;
175
- * 如果是完整的服务或项目,初始化代码要添加在项目启动的函数中;
176
- * 如果是独立的 Python 脚本文件,每个独立的文件都要添加 keyless-sdk 依赖和初始化代码。
237
+
238
+ 初始化代码:
239
+ - `credential_id` 必须与 `deployment.credential_id` 一致;
240
+ - 完整服务/项目:初始化代码加在项目启动函数中;
241
+ - 独立 Python 脚本:每个独立脚本都需添加依赖与初始化代码。
242
+
177
243
  ```python
178
244
  from credential_vault_sdk.keyless_client import KeylessClient
179
245
 
@@ -188,8 +254,10 @@ def main():
188
254
  ... ...
189
255
  ```
190
256
 
191
- #### C++ 语言
192
- 在文件开始引入头文件和命名空间,其中的注释需要保留:
257
+ ### 3.4 C++
258
+
259
+ 在文件开始引入头文件和命名空间(保留注释):
260
+
193
261
  ```C++
194
262
  // keyless-sdk dependency
195
263
  #include "keyless/keyless_client.h"
@@ -197,7 +265,8 @@ def main():
197
265
  using namespace baidu::credentialvault::cppsdk;
198
266
  ```
199
267
 
200
- 项目启动的函数中添加以下初始化代码,注意其中的 credentialID 需要与漏洞报告中的 credential_id 保持一致。
268
+ 在项目启动函数中添加初始化代码:
269
+
201
270
  ```C++
202
271
  #include "keyless/keyless_client.h"
203
272
 
@@ -216,4 +285,4 @@ int main(int argc, char *argv[]) {
216
285
  // 以下为其他原始代码
217
286
  ... ...
218
287
  }
219
- ```
288
+ ```
@@ -12,7 +12,7 @@
12
12
  """
13
13
 
14
14
  import argparse
15
- from utils import make_user_id, HOST, build_headers
15
+ from utils import make_user_id, build_url, build_headers
16
16
  import json
17
17
  import logging
18
18
  import sys
@@ -28,7 +28,7 @@ logger = logging.getLogger("credential_hosting")
28
28
 
29
29
  def hosting_credentials(chat_id, platform, credentials):
30
30
  """将凭证托管到指定平台。"""
31
- url = "{}/api/v1/deployments/{}/credentials".format(HOST, platform)
31
+ url = build_url("/api/v1/deployments/{}/credentials".format(platform))
32
32
  payload = {
33
33
  "configs": credentials,
34
34
  "scanChatID": chat_id,
@@ -81,7 +81,16 @@ def main():
81
81
  print("错误: 缺少必要参数。使用 --poll-result 或同时提供 --chat-id、--platform、--credentials", file=sys.stderr)
82
82
  sys.exit(1)
83
83
 
84
- result = hosting_credentials(chat_id, platform, credentials)
84
+ if platform == "datamanage" and isinstance(credentials, dict):
85
+ inner = credentials.get("credentials", [])
86
+ if isinstance(inner, list):
87
+ credentials["credentials"] = [
88
+ {("key" if k == "name" else k): v for k, v in item.items()}
89
+ for item in inner
90
+ ]
91
+ result = hosting_credentials(chat_id, platform, [credentials])
92
+ else:
93
+ result = hosting_credentials(chat_id, platform, credentials)
85
94
  print(json.dumps(result, ensure_ascii=False, indent=2))
86
95
 
87
96
 
@@ -18,6 +18,7 @@ import os
18
18
  import socket
19
19
  import subprocess
20
20
  import sys
21
+ import tempfile
21
22
 
22
23
  # 导入公共 URL 构建模块
23
24
  sys.path.insert(0, os.path.dirname(__file__))
@@ -43,12 +44,11 @@ def open_in_ide(url: str) -> bool:
43
44
  + body
44
45
  )
45
46
 
46
- tmpdir = os.environ.get("TMPDIR", "/tmp").rstrip("/")
47
+ tmpdir = tempfile.gettempdir()
47
48
  ppid_sock = f"{tmpdir}/comate-kernel-{os.getppid()}.sock"
48
49
  others = sorted(
49
50
  set(
50
51
  glob.glob(f"{tmpdir}/comate-kernel-*.sock")
51
- + glob.glob("/tmp/comate-kernel-*.sock")
52
52
  ),
53
53
  key=os.path.getmtime,
54
54
  reverse=True,
@@ -113,7 +113,7 @@ def main():
113
113
  print()
114
114
  print(build_markdown_link(url))
115
115
  print()
116
- print("配置完成后请在网页中点击「生成代码」按钮,我会自动检测到配置完成并继续执行修复。")
116
+ print("配置完成后请在网页中点击「修复代码」按钮,我会自动检测到配置完成并继续执行修复。")
117
117
  print()
118
118
 
119
119
  # 自动在 IDE 内嵌浏览器中打开
@@ -7,7 +7,7 @@
7
7
 
8
8
  流程:
9
9
  1. 连接 WebSocket 端点
10
- 2. 等待用户在网页完成凭证配置并点击「生成代码」
10
+ 2. 等待用户在网页完成凭证配置并点击「修复代码」
11
11
  3. 收到 msgType=repair 消息后输出配置数据
12
12
 
13
13
  注意: 使用 Python 标准库实现 WebSocket 客户端,无需第三方依赖。
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Dodo 环境专用:构建并输出硬编码风险治理网页 URL(标准 Markdown 超链接)。
4
+
5
+ 用法:
6
+ python3 scripts/credential_print_url.py \
7
+ --chat-id <chatID> --username <用户名> --ide <ideType> --project-dir <目录>
8
+
9
+ 输出:
10
+ 输出可点击的 Markdown 超链接,用户点击后在本地浏览器打开(不使用 simpleBrowser)。
11
+ """
12
+
13
+ import argparse
14
+ import os
15
+ import sys
16
+
17
+ # 使用绝对路径确保无论从哪个目录调用都能正确 import
18
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
19
+ from credential_url import build_url_from_args_raw
20
+
21
+
22
+ def main():
23
+ """入口函数, 输出硬编码风险治理网页 Markdown 链接, 用于 Dodo 环境"""
24
+ parser = argparse.ArgumentParser(description="输出硬编码风险治理网页 Markdown 链接(Dodo 环境)")
25
+ parser.add_argument("--chat-id", required=True, help="会话 ID (_CHAT_ID)")
26
+ parser.add_argument("--username", required=True, help="用户名 (_USERNAME)")
27
+ parser.add_argument("--ide", required=True, help="IDE 类型 (_IDE)")
28
+ parser.add_argument("--project-dir", required=True, help="项目目录绝对路径")
29
+ parser.add_argument("--repo", default="", help="仓库标识,留空则自动从 git remote 获取")
30
+ args = parser.parse_args()
31
+
32
+ url = build_url_from_args_raw(args.chat_id, args.username, args.ide,
33
+ args.project_dir, args.repo)
34
+
35
+ print("请点击以下链接打开硬编码风险治理网页,配置凭证信息:")
36
+ print()
37
+ print(f"[打开硬编码风险治理界面]({url})")
38
+ print()
39
+ print("配置完成后请在网页中点击「修复代码」按钮,我会自动检测到配置完成并继续执行修复。")
40
+
41
+
42
+ if __name__ == "__main__":
43
+ main()
@@ -11,9 +11,9 @@ import subprocess
11
11
  import urllib.parse
12
12
  import json
13
13
 
14
- from utils import HOST
14
+ from utils import build_url
15
15
 
16
- BASE_URL = f"{HOST}/app/credential"
16
+ BASE_URL = build_url("/app/credential")
17
17
  VERSION = "2.9.1"
18
18
 
19
19
 
@@ -63,6 +63,18 @@ def build_url(chat_id: str, uid: str, ide_name: str, repo: str) -> str:
63
63
  return f"{BASE_URL}?{params}"
64
64
 
65
65
 
66
+ def build_url_raw(chat_id: str, uid: str, ide_name: str, repo: str) -> str:
67
+ """构建完整的凭证配置网页 URL(不对参数进行 URL 编码,适用于 Dodo 环境)。"""
68
+ params = "&".join([
69
+ f"chatID={chat_id}",
70
+ f"comateUID={uid}",
71
+ f"version={VERSION}",
72
+ f"repo={repo}",
73
+ f"ideType={ide_name}",
74
+ ])
75
+ return f"{BASE_URL}?{params}"
76
+
77
+
66
78
  def build_markdown_link(url: str) -> str:
67
79
  """构建可点击的 Markdown 链接,点击后在 IDE SimpleBrowser 中打开。
68
80
 
@@ -79,3 +91,11 @@ def build_url_from_args(chat_id: str, username: str, ide_name: str,
79
91
  uid = compute_uid(username)
80
92
  repo = repo or get_repo(project_dir)
81
93
  return build_url(chat_id, uid, ide_name, repo)
94
+
95
+
96
+ def build_url_from_args_raw(chat_id: str, username: str, ide_name: str,
97
+ project_dir: str, repo: str = "") -> str:
98
+ """从原始参数一步构建不转码的 URL(Dodo 环境便捷方法)。"""
99
+ uid = compute_uid(username)
100
+ repo = repo or get_repo(project_dir)
101
+ return build_url_raw(chat_id, uid, ide_name, repo)
@@ -0,0 +1,183 @@
1
+ #!/bin/bash
2
+
3
+ # Session Info Fetcher Script
4
+ # 功能:检测环境并获取当前会话信息
5
+
6
+ set -e
7
+
8
+ # 颜色定义
9
+ RED='\033[0;31m'
10
+ GREEN='\033[0;32m'
11
+ YELLOW='\033[1;33m'
12
+ NC='\033[0m' # No Color
13
+
14
+ # 日志函数(全部输出到 stderr,避免污染 stdout 的 JSON 输出)
15
+ log_info() {
16
+ echo -e "${GREEN}[INFO]${NC} $1" >&2
17
+ }
18
+
19
+ log_warn() {
20
+ echo -e "${YELLOW}[WARN]${NC} $1" >&2
21
+ }
22
+
23
+ log_error() {
24
+ echo -e "${RED}[ERROR]${NC} $1" >&2
25
+ }
26
+
27
+ # 检查环境变量是否存在
28
+ if [ -z "$ANTHROPIC_CUSTOM_HEADERS" ]; then
29
+ log_warn "环境变量 ANTHROPIC_CUSTOM_HEADERS 未设置或为空"
30
+ echo "{}"
31
+ exit 0
32
+ fi
33
+
34
+ log_info "检测到 ANTHROPIC_CUSTOM_HEADERS 环境变量"
35
+
36
+ # 提取 source 字段
37
+ # 实际格式:HTTP Header 列表,如 "comate_custom_header: {\"source\": \"dodo\", ...}, other_header: val"
38
+ # comate_custom_header 的值为转义 JSON(\" 表示引号),需先提取再去转义后解析
39
+ SOURCE=$(python3 -c "
40
+ import os, re, json, sys
41
+
42
+ val = os.environ.get('ANTHROPIC_CUSTOM_HEADERS', '')
43
+
44
+ # 尝试方式1:从 comate_custom_header 提取转义 JSON
45
+ m = re.search(r'comate_custom_header:\s*(\{.+?\})(?:,[a-z_]+:|$)', val)
46
+ if m:
47
+ raw = m.group(1).replace('\\\\\"', '\"')
48
+ try:
49
+ data = json.loads(raw)
50
+ print(data.get('source', ''))
51
+ sys.exit(0)
52
+ except Exception:
53
+ pass
54
+
55
+ # 尝试方式2:直接当 JSON 解析(兼容旧格式)
56
+ try:
57
+ data = json.loads(val)
58
+ print(data.get('source', ''))
59
+ sys.exit(0)
60
+ except Exception:
61
+ pass
62
+
63
+ # 尝试方式3:正则直接匹配 source 字段(支持转义和非转义引号)
64
+ m = re.search(r'[\\\\]?\"source[\\\\]?\"\\s*:\\s*[\\\\]?\"([^\"\\\\]+)', val)
65
+ if m:
66
+ print(m.group(1))
67
+ sys.exit(0)
68
+
69
+ print('')
70
+ " 2>/dev/null)
71
+
72
+ if [ -z "$SOURCE" ]; then
73
+ log_warn "无法从 ANTHROPIC_CUSTOM_HEADERS 中提取 source 字段"
74
+ echo "{}"
75
+ exit 0
76
+ fi
77
+
78
+ log_info "提取到 source 值:$SOURCE"
79
+
80
+ # 判断是否为 dodo 环境
81
+ if [ "$SOURCE" != "dodo" ]; then
82
+ log_warn "当前环境为:$SOURCE (非 dodo 环境)"
83
+ # 非 dodo 环境仅记录日志,返回空对象
84
+ echo "{}"
85
+ exit 0
86
+ fi
87
+
88
+ log_info "当前环境为 dodo,开始获取会话信息..."
89
+
90
+ # 从 ANTHROPIC_CUSTOM_HEADERS 提取聊天用户名(username 字段),取不到则降级用 whoami
91
+ CURRENT_USERNAME=$(python3 -c "
92
+ import os, re, json, sys
93
+
94
+ val = os.environ.get('ANTHROPIC_CUSTOM_HEADERS', '')
95
+
96
+ # 尝试方式1:从 comate_custom_header 提取转义 JSON,取 username 字段
97
+ m = re.search(r'comate_custom_header:\s*(\{.+?\})(?:,[a-z_]+:|$)', val)
98
+ if m:
99
+ raw = m.group(1).replace('\\\\\"', '\"')
100
+ try:
101
+ data = json.loads(raw)
102
+ username = data.get('username', '')
103
+ if username:
104
+ print(username)
105
+ sys.exit(0)
106
+ except Exception:
107
+ pass
108
+
109
+ # 尝试方式2:直接当 JSON 解析(兼容旧格式)
110
+ try:
111
+ data = json.loads(val)
112
+ username = data.get('username', '')
113
+ if username:
114
+ print(username)
115
+ sys.exit(0)
116
+ except Exception:
117
+ pass
118
+
119
+ # 尝试方式3:正则直接匹配 username 字段
120
+ m = re.search(r'[\\\\]?\"username[\\\\]?\"\\s*:\\s*[\\\\]?\"([^\"\\\\]+)', val)
121
+ if m:
122
+ print(m.group(1))
123
+ sys.exit(0)
124
+
125
+ print('')
126
+ " 2>/dev/null)
127
+
128
+ # 降级:取不到聊天用户名则使用系统用户
129
+ if [ -z "$CURRENT_USERNAME" ]; then
130
+ log_warn "无法从 ANTHROPIC_CUSTOM_HEADERS 提取 username,降级使用 whoami"
131
+ CURRENT_USERNAME=$(whoami 2>/dev/null || echo "unknown")
132
+ fi
133
+
134
+ # 获取当前会话 ID (从环境变量或生成)
135
+ CURRENT_SESSION_ID="${SESSION_ID:-$(date +%s)}"
136
+
137
+ # 获取当前任务 ID (从环境变量或生成)
138
+ CURRENT_TASK_ID="${TASK_ID:-$(date +%s%N)}"
139
+
140
+ # 预先生成 chat_id(随机 UUID,供调用方直接提取,避免多次调用 uuid4 产生不一致)
141
+ CURRENT_CHAT_ID=$(python3 -c "import uuid; print(str(uuid.uuid4()))")
142
+
143
+ # 使用 export 导出环境变量
144
+ export CURRENT_ENV="dodo"
145
+ export CURRENT_SOURCE="$SOURCE"
146
+ export CURRENT_USERNAME="$CURRENT_USERNAME"
147
+ export CURRENT_SESSION_ID="$CURRENT_SESSION_ID"
148
+ export CURRENT_TASK_ID="$CURRENT_TASK_ID"
149
+ export CURRENT_CHAT_ID="$CURRENT_CHAT_ID"
150
+ export CURRENT_TIMESTAMP="$(date -Iseconds)"
151
+
152
+ log_info "已导出以下环境变量:"
153
+ log_info " - CURRENT_ENV=$CURRENT_ENV"
154
+ log_info " - CURRENT_SOURCE=$CURRENT_SOURCE"
155
+ log_info " - CURRENT_USERNAME=$CURRENT_USERNAME"
156
+ log_info " - CURRENT_SESSION_ID=$CURRENT_SESSION_ID"
157
+ log_info " - CURRENT_TASK_ID=$CURRENT_TASK_ID"
158
+ log_info " - CURRENT_CHAT_ID=$CURRENT_CHAT_ID"
159
+ log_info " - CURRENT_TIMESTAMP=$CURRENT_TIMESTAMP"
160
+
161
+ # 输出 JSON 格式结果
162
+ cat <<EOF
163
+ {
164
+ "environment": "$CURRENT_ENV",
165
+ "source": "$CURRENT_SOURCE",
166
+ "username": "$CURRENT_USERNAME",
167
+ "session_id": "$CURRENT_SESSION_ID",
168
+ "task_id": "$CURRENT_TASK_ID",
169
+ "chat_id": "$CURRENT_CHAT_ID",
170
+ "timestamp": "$CURRENT_TIMESTAMP",
171
+ "exported_vars": {
172
+ "CURRENT_ENV": "$CURRENT_ENV",
173
+ "CURRENT_SOURCE": "$CURRENT_SOURCE",
174
+ "CURRENT_USERNAME": "$CURRENT_USERNAME",
175
+ "CURRENT_SESSION_ID": "$CURRENT_SESSION_ID",
176
+ "CURRENT_TASK_ID": "$CURRENT_TASK_ID",
177
+ "CURRENT_CHAT_ID": "$CURRENT_CHAT_ID",
178
+ "CURRENT_TIMESTAMP": "$CURRENT_TIMESTAMP"
179
+ }
180
+ }
181
+ EOF
182
+
183
+ log_info "dodo会话信息获取成功,环境变量已导出"
@@ -1,11 +1,15 @@
1
1
  #!/bin/bash
2
2
  # 获取当前 Claude Code 会话 ID
3
3
 
4
- # 获取当前工作目录的绝对路径
5
- CURRENT_DIR=$(pwd)
4
+ # 优先使用 git 根目录来确定项目路径
5
+ if git rev-parse --show-toplevel &>/dev/null; then
6
+ CURRENT_DIR=$(git rev-parse --show-toplevel)
7
+ else
8
+ CURRENT_DIR=$(pwd)
9
+ fi
6
10
 
7
- # 将路径中的斜杠替换为连字符,构建项目目录名
8
- PROJECT_DIR_NAME=$(echo "$CURRENT_DIR" | sed 's/\//-/g')
11
+ # 将路径中的斜杠和下划线替换为连字符,构建项目目录名
12
+ PROJECT_DIR_NAME=$(echo "$CURRENT_DIR" | sed 's/[\/_]/-/g')
9
13
 
10
14
  # Claude projects 目录路径
11
15
  PROJECTS_DIR="$HOME/.claude/projects"