@comate/zulu 1.4.1 → 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.
- package/comate-engine/assets/skills/code-security/SKILL.md +254 -63
- package/comate-engine/assets/skills/code-security/references/credential_hosting.md +263 -97
- package/comate-engine/assets/skills/code-security/references/framework_detection.md +91 -0
- package/comate-engine/assets/skills/code-security/references/vul_repair_sensitive.md +143 -74
- package/comate-engine/assets/skills/code-security/scripts/credential_hosting.py +12 -3
- package/comate-engine/assets/skills/code-security/scripts/credential_open_page.py +3 -3
- package/comate-engine/assets/skills/code-security/scripts/credential_poll.py +1 -1
- package/comate-engine/assets/skills/code-security/scripts/credential_print_url.py +43 -0
- package/comate-engine/assets/skills/code-security/scripts/credential_url.py +22 -2
- package/comate-engine/assets/skills/code-security/scripts/dodo/dodo_session.sh +183 -0
- package/comate-engine/assets/skills/code-security/scripts/ducc/get_claude_session_id.sh +8 -4
- package/comate-engine/assets/skills/code-security/scripts/ducc/open_browser.py +15 -18
- package/comate-engine/assets/skills/code-security/scripts/http_client.py +2 -3
- package/comate-engine/assets/skills/code-security/scripts/repair_vulnerability.py +3 -3
- package/comate-engine/assets/skills/code-security/scripts/report_chat.py +2 -2
- package/comate-engine/assets/skills/code-security/scripts/scan_vulnerability.py +7 -7
- package/comate-engine/assets/skills/code-security/scripts/utils.py +26 -11
- package/comate-engine/node_modules/better-sqlite3/node_modules/.bin/prebuild-install +2 -2
- package/comate-engine/node_modules/tree-sitter-bash/node_modules/.bin/node-gyp-build +2 -2
- package/comate-engine/node_modules/tree-sitter-bash/node_modules/.bin/node-gyp-build-optional +2 -2
- package/comate-engine/node_modules/tree-sitter-bash/node_modules/.bin/node-gyp-build-test +2 -2
- package/comate-engine/package.json +2 -0
- package/comate-engine/server.js +26 -26
- package/dist/bundle/index.js +3 -3
- 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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
*
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
1
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
120
|
+
# 修复后(Go + GDP)
|
|
121
|
+
password={env.KL_PASSWORD}
|
|
83
122
|
```
|
|
84
|
-
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
password=123123123
|
|
88
|
-
|
|
89
|
-
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
|
-
|
|
95
|
-
|
|
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.
|
|
159
|
+
<version>1.0.3</version>
|
|
105
160
|
</dependency>
|
|
106
161
|
```
|
|
107
|
-
|
|
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
|
-
|
|
133
|
-
|
|
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
|
|
196
|
+
icode.baidu.com/baidu/xbu-data/things-go v2.3.0 // keyless-sdk dependency
|
|
137
197
|
)
|
|
138
198
|
```
|
|
139
|
-
|
|
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
|
-
|
|
167
|
-
|
|
228
|
+
### 3.3 Python
|
|
229
|
+
|
|
230
|
+
`requirements.txt` 中添加依赖(保留注释):
|
|
231
|
+
|
|
168
232
|
```txt
|
|
169
233
|
// keyless-sdk dependency
|
|
170
|
-
credential-vault-sdk>=0.1.
|
|
171
|
-
credential-vault-sdk-py2>=0.1.
|
|
234
|
+
credential-vault-sdk>=0.1.15(Python3 版本添加此依赖)
|
|
235
|
+
credential-vault-sdk-py2>=0.1.12(Python2 版本添加此依赖)
|
|
172
236
|
```
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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 = "
|
|
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
|
-
|
|
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 =
|
|
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 内嵌浏览器中打开
|
|
@@ -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
|
|
14
|
+
from utils import build_url
|
|
15
15
|
|
|
16
|
-
BASE_URL =
|
|
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
|
-
|
|
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
|
|
11
|
+
# 将路径中的斜杠和下划线替换为连字符,构建项目目录名
|
|
12
|
+
PROJECT_DIR_NAME=$(echo "$CURRENT_DIR" | sed 's/[\/_]/-/g')
|
|
9
13
|
|
|
10
14
|
# Claude projects 目录路径
|
|
11
15
|
PROJECTS_DIR="$HOME/.claude/projects"
|