# 敏感信息硬编码漏洞修复流程

修复漏洞分为以下三个步骤，**必须按序执行，每步完成后再进入下一步**：

1. **读取配置数据**：解析 `poll_result.json`，提取 `deployment.platform`、`deployment.credential_id`、`files[].name`、`files[].vulList[]`（含 `extra.secret.start.line`、`extra.secret.credentialName`、`extra.secret.start/end`）；**禁止凭印象跳过读取**；**files[].vulList[].line不是漏洞行号，不要根据这个处理漏洞**
3. **修复硬编码漏洞**：按文件遍历，每个文件内按 `extra.secret.start.line` **降序**处理漏洞；同行多漏洞按 `start.col` **降序**替换；每个漏洞**独立**依据 `credentialName` 是否为空决定托管或删除方式；**仅替换 `[start.col, end.col]` 范围内字符**，行内其余内容一律不动；修复后重新读取文件逐项校验
4. **【强制执行】引入 SDK**：显式检查 `deployment.platform` 的值——`== 4` 时按第 3 节引入 keyless-sdk（`credentialID` 必须与 `deployment.credential_id` 完全一致，已有则不重复引入）；`!= 4` 时明确跳过，**不得静默忽略此步骤**

---

## ⛔ 核心约束：仅处理有 extra.secret.start.line 的漏洞

**此约束为硬性规则，不可违反，不可绕过。**

修复漏洞前，必须逐项检查 `vulList[]` 中的每个漏洞对象是否满足以下条件：

```
✅ 必须存在的字段：
   - extra.secret.start.line（漏洞所在行号）
   - extra.secret.start.col（漏洞起始列）
   - extra.secret.end.line（漏洞结束行号）
   - extra.secret.end.col（漏洞结束列）

❌ 缺失任何字段 → 该漏洞必须跳过，不得修复
```

**处理逻辑**：

```
对于 vulList 中的每个漏洞：
  如果 extra.secret.start.line 不存在：
    → 输出警告："[跳过] 漏洞缺少 extra.secret.start.line 字段，无法定位行号"
    → 继续处理下一个漏洞
    → 禁止尝试猜测或推断行号
  
  如果 extra.secret.start 或 extra.secret.end 不存在：
    → 输出警告："[跳过] 漏洞缺少完整的位置信息（start/end）"
    → 继续处理下一个漏洞
    → 禁止尝试修复
  
  如果上述字段都存在：
    → 执行修复流程（参考第 2 节）
```

**禁止事项**：
- ❌ 禁止修复没有 `extra.secret.start.line` 的漏洞
- ❌ 禁止凭上下文猜测漏洞位置
- ❌ 禁止修复"看起来像硬编码"但不在 `vulList` 中的内容
- ❌ 禁止基于文件内容推断漏洞位置

---

> **稳定性总原则**：
> - **只改必要内容**：仅基于 `extra.secret.start` / `extra.secret.end` 精确定位敏感信息进行替换，禁止改动同一行其他字符（包括空格、引号、注释、分隔符）。
> - **白名单修复**：只修复 `poll_result.json` 中明确列出**且包含完整位置信息**的漏洞，禁止顺带"修复"其他疑似硬编码。
> - **修复顺序**：同一文件内的多个漏洞，**按 `extra.secret.start.line` 降序处理**，避免前面的修改导致后续行号偏移；不同文件之间互不干扰。
> - **每个漏洞独立判断**：必须依据各自的 `credentialName` 决定修复方式，禁止基于内容相似性批量推断。
> - **修复后校验**：修复完毕重新读取文件，逐项确认（a）目标位置已替换；（b）非目标行/列未被改动；（c）文件整体语法/缩进合法。

## 1. 读取漏洞修复配置数据

基于凭证配置网页回传的 `poll_result.json` 数据获取漏洞修复配置，示例如下：

```json
{
  "scanChatID": "从 credential_poll 返回的 chatUUID",
  "deployment": {
    "platform": 2,
    "platformName": "ipipe",
    "credential_id": "appDemo_prod"
  },
  "files": [
    {
      "name": "src/main/resources/application.properties",
      "hash": "文件SHA256",
      "vulList": [
        {
          "ruleID": "codescan_generic_password-config_sensitive",
          "hash": "漏洞hash",
          "extra": {
            "secret": {
              "credentialName": "KL_SPRING_DATASOURCE_PASSWORD",
              "start": {"col": 28, "line": 35},
              "end": {"col": 43, "line": 35}
            }
          }
        }
      ]
    }
  ],
  "trigger": 1,
  "type": 4
}
```

各字段说明如下：

* `deployment`：部署信息
  * `platform`：部署平台，1 表示 cnap，2 表示 ipipe，3 表示 opera，4 表示 datamanage
  * `platformName`：部署平台名称（如 ipipe、cnap、opera、datamanage）
  * `credential_id`：凭证 ID（platform 为 4 时使用，用于引入 keyless-sdk）
* `files`：文件信息
  * `name`：漏洞文件的相对路径
  * `vulList`：漏洞列表
    * `extra.secret.start.line`：漏洞所在行号
    * `extra.secret.start`：敏感信息的开始位置（行、列，列从 1 开始计数）
    * `extra.secret.end`：敏感信息的结束位置（行、列）
    * `extra.secret.credentialName`：建议使用的环境变量名称
      * 不为空 → 修复方式为**托管**
      * 为空 → 修复方式为**删除**

## 2. 修复漏洞

硬编码漏洞修复的本质是把代码中的硬编码敏感信息改为从环境变量读取（托管），或在不需要保留的场景下置空（删除）。

### 2.1 通用修复流程

**⚠️ 前置检查（每个漏洞必须执行）**：

```
步骤 0：验证漏洞位置信息完整性
────────────────────────────────
对于 vulList 中的每个漏洞项：
  检查 extra.secret.start.line 是否存在？
    ❌ 不存在 → 输出警告，跳过该漏洞，继续下一个
    ✅ 存在 → 继续
  
  检查 extra.secret.start 和 extra.secret.end 是否都存在？
    ❌ 不存在 → 输出警告，跳过该漏洞，继续下一个
    ✅ 存在 → 继续
  
  检查 start.col 和 end.col 是否都存在？
    ❌ 不存在 → 输出警告，跳过该漏洞，继续下一个
    ✅ 存在 → 进入修复流程
```

**标准修复流程**（仅对通过前置检查的漏洞）：

1. **按文件聚合**：遍历 `files[]`，处理完一个文件再处理下一个。
2. **过滤无效漏洞**：对每个 `vulList[]` 执行前置检查，筛除缺少位置信息的漏洞。
3. **按行倒序处理**：对通过检查的漏洞按 `extra.secret.start.line` **降序**排序后再修复。
4. **逐漏洞读取关键字段**：`extra.secret.start.line`、`extra.secret.credentialName`、`extra.secret.start`、`extra.secret.end`。
5. **独立判断修复方式**：
   - `credentialName` 不为空 → **托管方式**（参考 2.2 / 2.3）
   - `credentialName` 为空 → **删除方式**（参考 2.4）
6. **精确替换**：仅替换 `[start.col, end.col]` 范围内的字符，行内其余内容保持原样。
7. **同行多漏洞**：若同一行存在多个漏洞，按 `start.col` **降序**依次替换，避免列偏移。
8. **修复后校验**：重新读取文件，确认每个漏洞已按正确方式修复，且无误伤。

> **重要**：即使多行内容完全相同，也必须逐行依据各自的 `credentialName` 判断修复方式；不得基于内容相似性批量推断。仅当确认所有目标行的 `credentialName` 都相同且非空时，才可使用 `replace_all`。

> **白名单约束**：仅修复 `poll_result.json` 中列出且位置信息完整的漏洞，文件中其他可疑硬编码一律不动。

> **无位置信息漏洞处理**：遇到缺少 `extra.secret.start.line` 等关键字段的漏洞时，必须跳过并输出警告日志，禁止尝试修复。

### 2.2 托管方式 - 代码文件

如果漏洞文件是代码文件（如 java、go、php、py、js、c/c++ 等），根据语言生成读取环境变量的代码替换原硬编码值，注意：

1. 与环境变量读取无关的其他代码、注释、空白字符**一律不动**。
2. 如果漏洞行是被引号包裹的纯字符串字面量，输出时保留其结构与可执行语义，避免出现空字符串拼接（如 `"" + os.environ[...]`）。
3. **不要给出环境变量默认值**。
4. 保留原始代码的行首缩进（空格/Tab 数量与种类一致），不要清除两端空白字符。
5. **Python**：使用 `os.environ['XXX']`，**不**使用 `os.getenv`。
6. **Go**：不能在 `const` 中读取环境变量；将 `const` 中该值删除，并在 `const` 块下方用 `var` 重新定义并通过 `os.Getenv` 读取。
7. **Java**：
   - `platform == 4`：使用 `System.getProperty("XXX")`（与 keyless-sdk 注入方式一致）。
   - 其他 platform：使用 `System.getenv("XXX")`。
8. **C++**：使用 `std::getenv("XXX")`，需引入 `<cstdlib>` 头文件。
   - 示例：`const char* raw = std::getenv("KL_PASSWORD");
            std::string password = raw ? std::string(raw) : "";`
   - 注意：`std::getenv` 返回 `char*`, 环境变量不存在时返回 `nullptr`，必须先判空再使用，禁止直接将返回值赋值给 `std::string`。
9. **NodeJS**：使用 `process.env.XXX`，无需额外引入模块。
   - 示例：`const password = process.env.KL_PASSWORD;`
10. **依赖自动补齐**：若读取环境变量所需的标准库未引入，自动补全（如 Python 的 `os`、Go 的 `os`、C++ 的 `<cstdlib>` 等）。

### 2.3 托管方式 - 配置文件

> **修复配置文件前**：先读取 `references/framework_detection.md`，识别配置文件的宿主语言和框架，再选择对应的占位符格式。

**占位符格式规则**（环境变量名取自 `credentialName`，**不写默认值**）：

| 语言 / 框架 / 文件类型 | 占位符格式 | 示例 |
|---|---|---|
| Go + GDP 框架 + toml | `{env.XXX}` | `password={env.KL_PASSWORD}` |
| 其他语言 / 框架 / 配置文件（yml、yaml、properties、ini、toml、xml 等） | `${XXX}` | `password=${KL_PASSWORD}` |

修复示例：

```toml
# 修复前
password=123123123
# 修复后（Go + GDP）
password={env.KL_PASSWORD}
```

```properties
# 修复前
spring.datasource.password=123123123
# 修复后（Spring Boot 等）
spring.datasource.password=${KL_SPRING_DATASOURCE_PASSWORD}
```

### 2.4 删除方式（credentialName 为空）

按文件类型采用合适的"清空"策略，**避免破坏文件语法**：

- **配置文件（properties/ini/yml/toml 等键值型）**：保留键和分隔符，仅清空值。
  - 示例：`password=123123123` → `password=`
  - YAML：`password: 123123123` → `password:`（或 `password: ""`，与文件原有空值风格一致）
- **代码文件中的字符串字面量**：将值清空为对应语言的空字符串字面量，保留引号与上下文语法。
  - 示例：`String pwd = "123";` → `String pwd = "";`
- **代码文件中的非字符串字面量**：若直接置空会导致语法错误，则替换为该语言中合法的空值（如 Java 的 `""`、Go 的 `""`、Python 的 `""`），并附最小必要的类型保持。

修复后必须确保文件可正常解析/编译。

## 3. 引入 SDK（仅 platform == 4）

仅当 `deployment.platform == 4` 时，才需要在项目中引入 keyless-sdk 并添加初始化代码——SDK 负责将凭证注入系统环境变量，使前面（步骤 2）的读取代码能取到值。其他 platform **不要**引入 SDK。

初始化代码中的 `credentialID` / `credential_id` / `CredentialID` 必须与 `deployment.credential_id` 完全一致。已有 SDK 依赖与初始化的项目，**不要重复引入**。

**以下引入client时，一定要检查client的变量引用，保证引用正确**

### 3.1 Java

`pom.xml` 中添加依赖（保留注释）：

```xml
<!-- keyless-sdk dependency -->
<dependency>
    <groupId>com.baidu.xbu-data</groupId>
    <artifactId>keyless-sdk</artifactId>
    <version>1.0.4</version>
</dependency>
```

在项目启动函数中添加初始化代码：

```java
import com.baidu.keyless.KeylessClient;

public class KeylessAppCredentialDemo {
    public static void main(String[] args) {
        // 初始化 keyless-sdk 开始
        // step1: 设置 credentialID 构建 client，详情可参考 https://ku.baidu-int.com/d/BRnvQROwD8akQY
        String credentialID = "appDemo_prod";
        KeylessClient keylessClient = KeylessClient.builder()
            .credentialId(credentialID)
            //.version(1) // 可选参数，不配置时默认拉取最新版本，线上环境建议指定版本保证稳定
            .build();
        // step2: 获取凭证信息并注入到系统环境变量中
        try {
            keylessClient.load();
        } catch (Exception e) {
            System.out.println("load keyless client error: " + e.getMessage());
        }
        // 初始化 keyless-sdk 结束
        // 以下为其他原始代码
        ... ...
    }
}
```
具体账密漏洞变更成从使用keylessClient.getSecret(credentialName)获取，credentialName从poll_result.json获取。代码如下:
```java
    keylessClient.getSecret("credentialName")
```

### 3.2 Go

`go.mod` 中添加依赖（保留注释）：
**require模块引入包和版本保持不变**

```mod
require (
    icode.baidu.com/baidu/xbu-data/things-go v1.3.4 // keyless-sdk dependency
)
```

在项目启动函数中添加初始化代码：
**import模块引入包保持不变**

```go
package main

import (
    "icode.baidu.com/baidu/xbu-data/things-go/pkg/keyless"
)

func main() {
    // 初始化 keyless-sdk 开始
    // step1: 设置 CredentialID 构建 client，详情可参考 https://ku.baidu-int.com/d/G_QqzMQrQR9_K_
    req := &keyless.GetCredentialReq{
        CredentialID: "appDemo_prod", // 凭证标识
        Version: 1,  // 不设置默认值为0，则获取最新版本，线上环境建议指定版本保证稳定
    }
    // step2: 获取凭证信息并注入到系统环境变量中
    client := keyless.NewClient(req)
    err := client.Load()
    if err != nil {
        fmt.Println(err.Error())
        return
    }
    // 初始化 keyless-sdk 结束
    // 以下为其他原始代码
        ... ...
}
```
具体账密漏洞变更成从使用client.GetSecret(credentialName)获取，credentialName从poll_result.json获取。代码如下:
```go
    client.GetSecret("credentialName")
```

### 3.3 Python

`requirements.txt` 中添加依赖（保留注释）：

```txt
// keyless-sdk dependency
credential-vault-sdk=0.1.16（Python3 版本添加此依赖）
credential-vault-sdk-py2=0.1.16（Python2 版本添加此依赖）
```

初始化代码：
- `credential_id` 必须与 `deployment.credential_id` 一致；
- 完整服务/项目：初始化代码加在项目启动函数中；
- 独立 Python 脚本：每个独立脚本都需添加依赖与初始化代码。

```python
from credential_vault_sdk.keyless_client import KeylessClient

def main():
    # 初始化 keyless-sdk 开始
    # step1: 设置 credential_id 构建 client，详情可参考 https://ku.baidu-int.com/d/HWaSIaQSThk88b
    # version: 不设置默认值为-1，则获取最新版本，线上环境建议指定版本保证稳定
    client = KeylessClient(credential_id="appDemo_prod", version=1)
    # step2: 获取凭证信息并注入到系统环境变量中
    client.load()
    # 初始化 keyless-sdk 结束
    # 以下为其他原始代码
    ... ...
```

具体账密漏洞变更成从使用client.get_secret(credentialName)获取, credentialName从poll_result.json获取，代码如下:
```python
    client.get_secret("credentialName")
```

### 3.4 C++

在文件开始引入头文件和命名空间（保留注释）：

```C++
// keyless-sdk dependency
#include "keyless/keyless_client.h"

using namespace baidu::credentialvault::cppsdk;
```

在项目启动函数中添加初始化代码：

```C++
#include "keyless/keyless_client.h"

using namespace baidu::credentialvault::cppsdk;

int main(int argc, char *argv[]) {
    // 初始化 keyless-sdk 开始
    // step1: 设置 credential_id 构建 client，详情可参考 https://ku.baidu-int.com/d/x5s_A3YHll0PvR
    // 默认版本号为 -1 代表拉取最新版本，可以缺省不填写该参数；线上服务建议指定版本，保证稳定
    KeylessClient client = KeylessClient("appDemo_prod", 1);
    // step2: 获取凭证信息并注入到系统环境变量中
    KeylessStatus load_result = client.load();
    if (!load_result.ok()) {
        return 0;
    }
    // 初始化 keyless-sdk 结束
    // 以下为其他原始代码
    ... ...
}
```
硬编码的账密位置替换成从使用client.get_secret(credentialName)获取, credentialName从poll_result.json获取。例如原始代码是
```C++
std::string password = "xxxxxxxxxxx";
```
替换成
```C++

std::string password = client.get_secret("credentialName");
```
