Skip to content

v4 preview#181

Open
boy-hack wants to merge 117 commits intomainfrom
v4
Open

v4 preview#181
boy-hack wants to merge 117 commits intomainfrom
v4

Conversation

@boy-hack
Copy link
Collaborator

@boy-hack boy-hack commented Jan 8, 2026

No description provided.

zhuque and others added 30 commits January 8, 2026 10:32
- Introduced a new skill for detecting sensitive information leakage in AI agent responses, including API keys, credentials, and PII.
- Added comprehensive documentation for the skill, including usage examples and API reference.
- Implemented a data leakage scanning tool with support for static and dynamic test case generation.
- Enhanced the requirements with the addition of PyYAML for YAML file handling.
- Created various prompt sets for testing, including basic and advanced attack vectors.
- Established a modular architecture for security scanning tools, facilitating future enhancements and integrations.
- Introduced a new agent security reviewer skill to aggregate findings from various security detection modules and generate risk reports based on OWASP ASI classification.
- Added detailed documentation for the agent security reviewer, including its purpose, workflow, and integration with existing tools.
- Created a new report generator for producing structured XML reports of vulnerabilities, enhancing the reporting process for agent-based applications.
- Updated data leakage detection documentation to reference the new security reviewer for classification and report aggregation.
- Enhanced the models to include OWASP ASI categories for better organization and clarity in security assessments.
- Updated severity levels in agent security reviewer, data leakage detection, and related documentation to standardize categories as High, Medium, and Low, removing the Critical classification.
- Adjusted output formats and reporting mechanisms to reflect the new severity structure.
- Enhanced models and evaluation criteria to align with the updated severity framework, ensuring consistency across all security scanning tools.
V4 add agent adapter tools & config-scanner agent
- Updated the agent security reviewer documentation to clarify its purpose and workflow, emphasizing the use of OWASP Top 10 for Agentic Applications 2026 classification.
- Enhanced the data leakage detection module with improved integration capabilities, including context validation and LLM-based evaluations.
- Revised output formats for vulnerability reports, ensuring consistency and clarity in XML structure.
- Removed deprecated system prompt references and streamlined the review workflow for better usability.
update tool dialogue & configuration scan

// 创建用户专属目录
userDir := getAgentUserDir(username)
if err := os.MkdirAll(userDir, 0755); err != nil {

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 27 days ago

In general, to fix uncontrolled path usage, you must ensure that any path built from user-controlled data is (1) syntactically safe (no traversal/meta characters) and (2) resolved to an absolute path that is verified to remain under a known safe root directory. This should be done as close as possible to where the path is used with filesystem APIs (os.MkdirAll, os.Open, etc.), or by centralizing path construction in helper functions that enforce these rules.

For this handler, the best fix with minimal functional change is:

  1. Harden getAgentUserDir so that it returns an absolute, normalized path under a fixed safe directory derived from AgentConfigRoot, and refuses (by returning a safe error value) if resolution fails or escapes that directory.
  2. Update HandleSaveAgentConfig to use the (now safer) getAgentUserDir and handle possible errors before calling os.MkdirAll.
  3. Keep the existing validateUsername logic but do not rely on it as the only defense. Instead of silently substituting PublicUser when validation fails, explicitly reject the request, which both eliminates surprising behavior and ensures username is always of a safe form.

Concretely, within common/websocket/knowledge2_api.go:

  • Change getAgentUserDir to return (string, error) and internally:
    • Resolve AgentConfigRoot to an absolute path.
    • Join it with username.
    • Call filepath.Clean and filepath.Abs on the result.
    • Verify the final absPath has the safe-root prefix (using strings.HasPrefix on a normalized root with trailing separator).
    • On any failure, return an error.
  • Update HandleSaveAgentConfig so that:
    • It rejects invalid usernames (using validateUsername) with a 400 instead of forcing PublicUser.
    • It calls the new getAgentUserDir and, if it returns an error, responds with a 400/500 and does not call os.MkdirAll.
    • Use the returned safe directory path for os.MkdirAll.

These changes only touch the shown code and reuse existing imports (filepath, strings, fmt, errors), so no new imports are necessary.

Suggested changeset 1
common/websocket/knowledge2_api.go

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/common/websocket/knowledge2_api.go b/common/websocket/knowledge2_api.go
--- a/common/websocket/knowledge2_api.go
+++ b/common/websocket/knowledge2_api.go
@@ -326,9 +326,28 @@
 const AgentConfigRoot = "data/agents"
 const PublicUser = "public_user"
 
-// getAgentUserDir 获取用户的 agent 配置目录
-func getAgentUserDir(username string) string {
-	return filepath.Join(AgentConfigRoot, username)
+// getAgentUserDir 获取用户的 agent 配置目录(确保路径在 AgentConfigRoot 之内)
+func getAgentUserDir(username string) (string, error) {
+	// 计算安全的根目录绝对路径
+	rootAbs, err := filepath.Abs(AgentConfigRoot)
+	if err != nil {
+		return "", fmt.Errorf("failed to resolve agent config root: %w", err)
+	}
+	// 组合并规范化用户目录
+	userPath := filepath.Join(rootAbs, username)
+	userAbs, err := filepath.Abs(userPath)
+	if err != nil {
+		return "", fmt.Errorf("failed to resolve user path: %w", err)
+	}
+	// 确保最终路径仍然在 AgentConfigRoot 之下,防止路径穿越
+	rootWithSep := rootAbs
+	if !strings.HasSuffix(rootWithSep, string(os.PathSeparator)) {
+		rootWithSep += string(os.PathSeparator)
+	}
+	if userAbs != rootAbs && !strings.HasPrefix(userAbs, rootWithSep) {
+		return "", errors.New("invalid username path")
+	}
+	return userAbs, nil
 }
 
 // validateUsername 验证用户名安全性(防止路径穿越)
@@ -448,7 +467,11 @@
 func HandleSaveAgentConfig(c *gin.Context) {
 	username := c.GetString("username")
 	if !validateUsername(username) {
-		username = PublicUser
+		c.JSON(http.StatusBadRequest, gin.H{
+			"status":  1,
+			"message": "用户名非法",
+		})
+		return
 	}
 
 	name := strings.TrimSpace(c.Param("name"))
@@ -497,7 +520,14 @@
 	}
 
 	// 创建用户专属目录
-	userDir := getAgentUserDir(username)
+	userDir, err := getAgentUserDir(username)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{
+			"status":  1,
+			"message": "用户名目录非法: " + err.Error(),
+		})
+		return
+	}
 	if err := os.MkdirAll(userDir, 0755); err != nil {
 		c.JSON(http.StatusInternalServerError, gin.H{
 			"status":  1,
EOF
@@ -326,9 +326,28 @@
const AgentConfigRoot = "data/agents"
const PublicUser = "public_user"

// getAgentUserDir 获取用户的 agent 配置目录
func getAgentUserDir(username string) string {
return filepath.Join(AgentConfigRoot, username)
// getAgentUserDir 获取用户的 agent 配置目录(确保路径在 AgentConfigRoot 之内)
func getAgentUserDir(username string) (string, error) {
// 计算安全的根目录绝对路径
rootAbs, err := filepath.Abs(AgentConfigRoot)
if err != nil {
return "", fmt.Errorf("failed to resolve agent config root: %w", err)
}
// 组合并规范化用户目录
userPath := filepath.Join(rootAbs, username)
userAbs, err := filepath.Abs(userPath)
if err != nil {
return "", fmt.Errorf("failed to resolve user path: %w", err)
}
// 确保最终路径仍然在 AgentConfigRoot 之下,防止路径穿越
rootWithSep := rootAbs
if !strings.HasSuffix(rootWithSep, string(os.PathSeparator)) {
rootWithSep += string(os.PathSeparator)
}
if userAbs != rootAbs && !strings.HasPrefix(userAbs, rootWithSep) {
return "", errors.New("invalid username path")
}
return userAbs, nil
}

// validateUsername 验证用户名安全性(防止路径穿越)
@@ -448,7 +467,11 @@
func HandleSaveAgentConfig(c *gin.Context) {
username := c.GetString("username")
if !validateUsername(username) {
username = PublicUser
c.JSON(http.StatusBadRequest, gin.H{
"status": 1,
"message": "用户名非法",
})
return
}

name := strings.TrimSpace(c.Param("name"))
@@ -497,7 +520,14 @@
}

// 创建用户专属目录
userDir := getAgentUserDir(username)
userDir, err := getAgentUserDir(username)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"status": 1,
"message": "用户名目录非法: " + err.Error(),
})
return
}
if err := os.MkdirAll(userDir, 0755); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"status": 1,
Copilot is powered by AI and may make mistakes. Always verify output.
return
}

if err := os.WriteFile(targetPath, []byte(content), 0644); err != nil {

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 27 days ago

In general, the fix is to ensure that any user-controlled component used to build a filesystem path is validated or constrained so it cannot escape an intended root directory. That means: (1) validating that username is safe before using it to construct userDir, and/or (2) resolving the final candidate path(s) to absolute paths and checking that they are contained within the intended base directory (derived from AgentConfigRoot).

The most targeted and robust change, without altering higher-level behavior, is to harden resolveAgentConfigPathForWrite. This function is where a user-influenced username is converted into a directory and where the final path is computed before being returned to the caller and passed into os.WriteFile. We can:

  1. Ensure username is valid by reusing the existing validateUsername check and falling back to PublicUser if it fails (mirroring what HandleSaveAgentConfig already does).
  2. Normalize and constrain the generated paths so they always reside under the intended base directory. To do this, we compute a base directory as baseDir := filepath.Join(AgentConfigRoot) (optionally Abs if desired), then for each candidate file (userDir/name.yaml and .yml) we compute the absolute path and verify that it is still inside baseDir using strings.HasPrefix(absPath, baseDir+string(os.PathSeparator)) or an equivalent safe-prefix check. If the check fails, return an error instead of allowing writing outside the base directory.

Concretely, in common/websocket/knowledge2_api.go, you only need to modify the body of resolveAgentConfigPathForWrite. The function will now (a) sanitize username (fallback to PublicUser), (b) compute baseDir and ensure it exists (or at least use it in checks), (c) for each candidate file, Abs it and ensure it is a descendant of baseDir before using it. No new imports are required because os, filepath, strings, and errors are already imported. All callers can stay unchanged, preserving existing functionality except that malicious or malformed usernames/paths will now be rejected safely.

Suggested changeset 1
common/websocket/knowledge2_api.go

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/common/websocket/knowledge2_api.go b/common/websocket/knowledge2_api.go
--- a/common/websocket/knowledge2_api.go
+++ b/common/websocket/knowledge2_api.go
@@ -665,20 +665,46 @@
 
 // resolveAgentConfigPathForWrite 解析写入路径(写入用户目录)
 func resolveAgentConfigPathForWrite(username, name string) (string, error) {
+	// Ensure username is safe; fall back to public user if invalid
+	if !validateUsername(username) {
+		username = PublicUser
+	}
+
 	userDir := getAgentUserDir(username)
+
+	// Compute the absolute base directory for all agent configs
+	baseDir, err := filepath.Abs(AgentConfigRoot)
+	if err != nil {
+		return "", err
+	}
+
 	candidates := []string{
 		filepath.Join(userDir, name+".yaml"),
 		filepath.Join(userDir, name+".yml"),
 	}
-	for _, path := range candidates {
-		_, statErr := os.Stat(path)
+
+	for _, p := range candidates {
+		absPath, err := filepath.Abs(p)
+		if err != nil {
+			return "", err
+		}
+		// Ensure the resolved path is within the baseDir to prevent directory traversal
+		if !strings.HasPrefix(absPath, baseDir+string(os.PathSeparator)) && absPath != baseDir {
+			return "", fmt.Errorf("invalid path resolved for agent config")
+		}
+
+		_, statErr := os.Stat(absPath)
 		if statErr == nil {
-			return path, nil
+			return absPath, nil
 		}
 		if statErr != nil && !errors.Is(statErr, os.ErrNotExist) {
 			return "", statErr
 		}
+
+		// Remember the first valid-in-baseDir candidate to use as default
+		candidates[0] = absPath
 	}
+
 	return candidates[0], nil
 }
 
EOF
@@ -665,20 +665,46 @@

// resolveAgentConfigPathForWrite 解析写入路径(写入用户目录)
func resolveAgentConfigPathForWrite(username, name string) (string, error) {
// Ensure username is safe; fall back to public user if invalid
if !validateUsername(username) {
username = PublicUser
}

userDir := getAgentUserDir(username)

// Compute the absolute base directory for all agent configs
baseDir, err := filepath.Abs(AgentConfigRoot)
if err != nil {
return "", err
}

candidates := []string{
filepath.Join(userDir, name+".yaml"),
filepath.Join(userDir, name+".yml"),
}
for _, path := range candidates {
_, statErr := os.Stat(path)

for _, p := range candidates {
absPath, err := filepath.Abs(p)
if err != nil {
return "", err
}
// Ensure the resolved path is within the baseDir to prevent directory traversal
if !strings.HasPrefix(absPath, baseDir+string(os.PathSeparator)) && absPath != baseDir {
return "", fmt.Errorf("invalid path resolved for agent config")
}

_, statErr := os.Stat(absPath)
if statErr == nil {
return path, nil
return absPath, nil
}
if statErr != nil && !errors.Is(statErr, os.ErrNotExist) {
return "", statErr
}

// Remember the first valid-in-baseDir candidate to use as default
candidates[0] = absPath
}

return candidates[0], nil
}

Copilot is powered by AI and may make mistakes. Always verify output.

// listAgentConfigNamesFromDir 从指定目录读取配置名称列表
func listAgentConfigNamesFromDir(dir string) ([]string, error) {
entries, err := os.ReadDir(dir)

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 27 days ago

In general, to fix uncontrolled path usage, you should normalize and validate any user-derived path components before creating filesystem paths, and enforce that the final absolute path stays within a known, safe base directory. Even if individual call sites validate input, putting the core safety checks close to the actual path construction makes the code resilient to future misuse.

For this specific code, the safest non-breaking fix is:

  • Make AgentConfigRoot an absolute, cleaned path at startup using filepath.Abs and filepath.Clean. This ensures consistent behavior for subsequent safety checks.
  • Change getAgentUserDir so it:
    • Cleans the username component.
    • Rejects any username that would introduce path traversal (e.g., .., path separators), independent of validateUsername.
    • Builds the path with filepath.Join.
    • Resolves it to an absolute path and verifies that the result still lies under the cleaned AgentConfigRoot using strings.HasPrefix.
    • If validation fails for any reason, falls back to the directory for PublicUser, which is safe.
  • This way, any future use of getAgentUserDir automatically benefits from these checks, and the dir argument passed to listAgentConfigNamesFromDir is guaranteed to be within AgentConfigRoot, directly addressing the CodeQL alert without changing the external behavior of the handlers.

Concretely, all changes will be in common/websocket/knowledge2_api.go:

  1. Add a new package-level variable agentConfigRootAbs and an init function that computes its absolute, cleaned value from AgentConfigRoot. If that computation fails (unlikely), we conservatively leave it as the original AgentConfigRoot.
  2. Replace the implementation of getAgentUserDir so it:
    • Uses filepath.Clean on the username.
    • Rejects unsafe usernames: empty, containing path separators (/ or os.PathSeparator), or "..".
    • Joins the cleaned username to agentConfigRootAbs, obtains the absolute path, and checks it starts with agentConfigRootAbs + string(os.PathSeparator) (and also allows exact equality).
    • On any failure, falls back to returning the directory for PublicUser.

No new external packages are required: we reuse os, filepath, and strings which are already imported.

Suggested changeset 1
common/websocket/knowledge2_api.go

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/common/websocket/knowledge2_api.go b/common/websocket/knowledge2_api.go
--- a/common/websocket/knowledge2_api.go
+++ b/common/websocket/knowledge2_api.go
@@ -21,6 +21,23 @@
 const AgentScanDir = "/app/agent-scan"
 const UvBin = "/usr/local/bin/uv"
 
+const AgentConfigRoot = "data/agents"
+const PublicUser = "public_user"
+
+// agentConfigRootAbs stores the absolute, cleaned path of AgentConfigRoot.
+// It is used as the safe base directory for all agent configuration files.
+var agentConfigRootAbs string
+
+func init() {
+	base, err := filepath.Abs(AgentConfigRoot)
+	if err != nil {
+		// Fallback to the original root if Abs fails, though this should be rare.
+		agentConfigRootAbs = filepath.Clean(AgentConfigRoot)
+		return
+	}
+	agentConfigRootAbs = filepath.Clean(base)
+}
+
 func HandleList(root string, loadFile func(filePath string) (interface{}, error)) gin.HandlerFunc {
 	return func(c *gin.Context) {
 		var allItems []interface{}
@@ -323,12 +340,42 @@
 }
 
 // ============== Agent Scan Config Management ==============
-const AgentConfigRoot = "data/agents"
-const PublicUser = "public_user"
 
-// getAgentUserDir 获取用户的 agent 配置目录
+// getAgentUserDir 获取用户的 agent 配置目录(包含安全校验,防止路径穿越)
 func getAgentUserDir(username string) string {
-	return filepath.Join(AgentConfigRoot, username)
+	// 清理用户名中的多余路径元素
+	cleanUser := filepath.Clean(username)
+
+	// 基本校验:不能为空,不能包含路径分隔符或父目录引用
+	if cleanUser == "" ||
+		strings.Contains(cleanUser, "/") ||
+		strings.ContainsRune(cleanUser, os.PathSeparator) ||
+		cleanUser == ".." ||
+		strings.Contains(cleanUser, ".."+string(os.PathSeparator)) {
+		// 如果用户名非法,回退到公共用户目录
+		cleanUser = PublicUser
+	}
+
+	// 在安全根目录下拼接用户子目录
+	userDir := filepath.Join(agentConfigRootAbs, cleanUser)
+
+	absUserDir, err := filepath.Abs(userDir)
+	if err != nil {
+		// 如果解析失败,回退到公共用户目录
+		absUserDir = filepath.Join(agentConfigRootAbs, PublicUser)
+	}
+
+	// 确保生成的绝对路径仍然在安全根目录下
+	prefix := agentConfigRootAbs
+	if !strings.HasSuffix(prefix, string(os.PathSeparator)) {
+		prefix = prefix + string(os.PathSeparator)
+	}
+	if absUserDir != agentConfigRootAbs && !strings.HasPrefix(absUserDir, prefix) {
+		// 如果越界,同样回退到公共用户目录
+		absUserDir = filepath.Join(agentConfigRootAbs, PublicUser)
+	}
+
+	return absUserDir
 }
 
 // validateUsername 验证用户名安全性(防止路径穿越)
EOF
Copilot is powered by AI and may make mistakes. Always verify output.
filepath.Join(userDir, name+".yml"),
}
for _, path := range candidates {
_, statErr := os.Stat(path)

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 27 days ago

In general, to fix uncontrolled path usage, you must ensure that any path derived from user input is properly constrained. This is typically done by (1) validating the input (disallowing separators, .., etc.) when it is meant to be a single component, and/or (2) resolving the final path against a known safe root and verifying that the resulting absolute path still lies under that root before any filesystem operations (read, write, stat, mkdir, etc.) occur.

For this code, the best fix with minimal functional change is:

  1. Keep validateUsername as-is, but add a helper that safely resolves a user’s agent config directory as an absolute path under a fixed safe root.
  2. Use this helper in place of getAgentUserDir(username) wherever we prepare to touch the filesystem, so that:
    • We always compute absUserDir := filepath.Abs(filepath.Join(AgentConfigRoot, username)).
    • We reject any path that does not remain under AgentConfigRoot by checking with strings.HasPrefix(absUserDir, absRoot+string(os.PathSeparator)) (or an equivalent robust check).
  3. Use this safe directory both when creating directories and when enumerating candidate config file paths.

Within common/websocket/knowledge2_api.go, we will:

  • Introduce a new function, e.g. getSafeAgentUserDir(username string) (string, error), placed near getAgentUserDir / validateUsername, that:
    • Resolves AgentConfigRoot to an absolute path once per call.
    • Builds userDir := filepath.Join(AgentConfigRoot, username), gets absUserDir := filepath.Abs(userDir), and verifies absUserDir is still inside the resolved AgentConfigRoot (by prefix check).
    • Returns an error if the check fails.
  • Update the creation of the user directory in HandleSaveAgentConfig to use getSafeAgentUserDir rather than the raw getAgentUserDir, and use the resulting safe directory path for os.MkdirAll.
  • Update the code around line 669–674 (where candidates and path are built and os.Stat(path) is called) to:
    • Use getSafeAgentUserDir(username) to compute userDir.
    • Ensure that candidate paths (filepath.Join(userDir, name+".yaml" / ".yml")) are under the safe root (this will hold automatically if userDir is safe).

These changes stay within the shown file and reuse existing imports (filepath, strings, os, errors), so no new dependencies are required.

Suggested changeset 1
common/websocket/knowledge2_api.go

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/common/websocket/knowledge2_api.go b/common/websocket/knowledge2_api.go
--- a/common/websocket/knowledge2_api.go
+++ b/common/websocket/knowledge2_api.go
@@ -331,6 +331,30 @@
 	return filepath.Join(AgentConfigRoot, username)
 }
 
+// getSafeAgentUserDir 返回安全的 agent 配置目录(绝对路径并确保位于 AgentConfigRoot 下)
+func getSafeAgentUserDir(username string) (string, error) {
+	// 解析根目录为绝对路径
+	absRoot, err := filepath.Abs(AgentConfigRoot)
+	if err != nil {
+		return "", err
+	}
+
+	// 拼接用户目录并解析为绝对路径
+	userDir := filepath.Join(AgentConfigRoot, username)
+	absUserDir, err := filepath.Abs(userDir)
+	if err != nil {
+		return "", err
+	}
+
+	// 确保用户目录仍然在根目录下,防止路径穿越
+	absRootWithSep := absRoot + string(os.PathSeparator)
+	if absUserDir != absRoot && !strings.HasPrefix(absUserDir, absRootWithSep) {
+		return "", fmt.Errorf("invalid user directory")
+	}
+
+	return absUserDir, nil
+}
+
 // validateUsername 验证用户名安全性(防止路径穿越)
 func validateUsername(username string) bool {
 	if username == "" {
@@ -496,8 +520,15 @@
 		return
 	}
 
-	// 创建用户专属目录
-	userDir := getAgentUserDir(username)
+	// 创建用户专属目录(使用安全路径)
+	userDir, err := getSafeAgentUserDir(username)
+	if err != nil {
+		c.JSON(http.StatusInternalServerError, gin.H{
+			"status":  1,
+			"message": "创建目录失败: " + err.Error(),
+		})
+		return
+	}
 	if err := os.MkdirAll(userDir, 0755); err != nil {
 		c.JSON(http.StatusInternalServerError, gin.H{
 			"status":  1,
EOF
@@ -331,6 +331,30 @@
return filepath.Join(AgentConfigRoot, username)
}

// getSafeAgentUserDir 返回安全的 agent 配置目录(绝对路径并确保位于 AgentConfigRoot 下)
func getSafeAgentUserDir(username string) (string, error) {
// 解析根目录为绝对路径
absRoot, err := filepath.Abs(AgentConfigRoot)
if err != nil {
return "", err
}

// 拼接用户目录并解析为绝对路径
userDir := filepath.Join(AgentConfigRoot, username)
absUserDir, err := filepath.Abs(userDir)
if err != nil {
return "", err
}

// 确保用户目录仍然在根目录下,防止路径穿越
absRootWithSep := absRoot + string(os.PathSeparator)
if absUserDir != absRoot && !strings.HasPrefix(absUserDir, absRootWithSep) {
return "", fmt.Errorf("invalid user directory")
}

return absUserDir, nil
}

// validateUsername 验证用户名安全性(防止路径穿越)
func validateUsername(username string) bool {
if username == "" {
@@ -496,8 +520,15 @@
return
}

// 创建用户专属目录
userDir := getAgentUserDir(username)
// 创建用户专属目录(使用安全路径)
userDir, err := getSafeAgentUserDir(username)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"status": 1,
"message": "创建目录失败: " + err.Error(),
})
return
}
if err := os.MkdirAll(userDir, 0755); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"status": 1,
Copilot is powered by AI and may make mistakes. Always verify output.
userDir := getAgentUserDir(username)
for _, ext := range []string{".yaml", ".yml"} {
path := filepath.Join(userDir, name+ext)
err := os.Remove(path)

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 27 days ago

In general, to fix uncontrolled path usage, you should ensure that any path built from user input is constrained to a specific base directory and does not escape it. This is typically done by combining a trusted base directory with the (sanitized) user input, resolving the result to an absolute path, and then verifying that this absolute path still lies within the intended base directory. Only then should you perform file operations like os.Remove.

For this specific code, we already sanitize username with validateUsername and fall back to PublicUser when invalid. However, CodeQL still sees tainted data reaching os.Remove at line 690 through userDir := getAgentUserDir(username) and path := filepath.Join(userDir, name+ext). The minimal, functionality‑preserving hardening is to add a safety check after building userDir and before deleting files: (1) compute the absolute path of userDir, (2) compute the absolute path of each candidate file to delete, and (3) verify that each absolute candidate path has the absolute user directory as its prefix. If any resolution fails or the prefix check fails, we abort deletion and return an error. This prevents any path traversal abuse even if username validation fails elsewhere or changes in the future, and it also addresses the static analysis concern by demonstrating that the sink is reached only with a path within the allowed tree.

Concretely, in deleteAgentConfig (lines 686–699 in common/websocket/knowledge2_api.go), we will:

  • Call filepath.Abs(userDir) to get absUserDir.
  • For each ext, build candidate := filepath.Join(userDir, name+ext), then compute absCandidate := filepath.Abs(candidate).
  • Check that absCandidate starts with absUserDir + string(os.PathSeparator) or equals absUserDir; if not, return an error like errors.New("invalid path") without attempting deletion.
  • Only call os.Remove(absCandidate) after this check.

We can use existing imports (os, filepath, errors, strings are already imported), so no new imports are needed. All changes are confined to the deleteAgentConfig function in this file.

Suggested changeset 1
common/websocket/knowledge2_api.go

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/common/websocket/knowledge2_api.go b/common/websocket/knowledge2_api.go
--- a/common/websocket/knowledge2_api.go
+++ b/common/websocket/knowledge2_api.go
@@ -685,9 +685,24 @@
 // deleteAgentConfig 删除配置(只删除用户目录的配置)
 func deleteAgentConfig(username, name string) (bool, error) {
 	userDir := getAgentUserDir(username)
+	absUserDir, err := filepath.Abs(userDir)
+	if err != nil {
+		return false, err
+	}
+
 	for _, ext := range []string{".yaml", ".yml"} {
-		path := filepath.Join(userDir, name+ext)
-		err := os.Remove(path)
+		candidate := filepath.Join(userDir, name+ext)
+		absCandidate, err := filepath.Abs(candidate)
+		if err != nil {
+			return false, err
+		}
+
+		// 防止路径穿越:确保目标文件在用户目录下
+		if absCandidate != absUserDir && !strings.HasPrefix(absCandidate, absUserDir+string(os.PathSeparator)) {
+			return false, errors.New("invalid agent config path")
+		}
+
+		err = os.Remove(absCandidate)
 		if err == nil {
 			return true, nil
 		}
EOF
@@ -685,9 +685,24 @@
// deleteAgentConfig 删除配置(只删除用户目录的配置)
func deleteAgentConfig(username, name string) (bool, error) {
userDir := getAgentUserDir(username)
absUserDir, err := filepath.Abs(userDir)
if err != nil {
return false, err
}

for _, ext := range []string{".yaml", ".yml"} {
path := filepath.Join(userDir, name+ext)
err := os.Remove(path)
candidate := filepath.Join(userDir, name+ext)
absCandidate, err := filepath.Abs(candidate)
if err != nil {
return false, err
}

// 防止路径穿越:确保目标文件在用户目录下
if absCandidate != absUserDir && !strings.HasPrefix(absCandidate, absUserDir+string(os.PathSeparator)) {
return false, errors.New("invalid agent config path")
}

err = os.Remove(absCandidate)
if err == nil {
return true, nil
}
Copilot is powered by AI and may make mistakes. Always verify output.
zhuque and others added 16 commits February 3, 2026 16:39
…ool usage statistics

- Updated `execute_stage` method to return dialogue statistics for each scan stage.
- Modified `scan` method to accumulate total dialogue counts across stages.
- Introduced tool usage statistics in `BaseAgent` to track tool invocation counts.
- Adjusted report generation to include total tests conducted during the scan.
- Removed optional test count reporting from the security reviewer prompt.
- Updated the agent class to extract both agent_type and agent_name from the provider configuration.
- Changed default risk type from "low" to "safe" when no vulnerabilities are found.
- Enhanced risk type determination logic to account for various vulnerability levels.
y3oZ and others added 9 commits February 6, 2026 16:53
…ration

- Improved user message formatting in `run_agent` to support English and Chinese based on the specified language.
- Expanded documentation in `agent_security_reviewer.md` regarding system prompt disclosure assessment notes for clarity and robustness.
…zation

- Reduced `max_iter` from 80 to 40 for improved performance.
- Added language-specific instructions in `run_agent` to ensure outputs are generated in the correct language (English or Chinese).
…tion

- Improved user message localization in `run_agent` to handle English and Chinese based on the specified language.
- Updated sensitive information detection in `AgentScanner` to utilize regex patterns for better accuracy and severity classification.
- Refactored skill search functionality to fallback to the full skill list when no matches are found for a query.
- Adjusted task execution to use the context's language setting for improved consistency.
…ection

- Introduced asynchronous handling in `BaseAgent` and `dialogue` functions to improve performance during skill execution.
- Expanded the `ScanPipeline` to run detection skills concurrently, enhancing the overall scanning efficiency.
- Updated the `data-leakage-detection` skill documentation for clarity and improved probing strategies.
- Added a new `skill-runner` prompt to streamline single-skill execution and reporting of vulnerabilities.
- Enhanced error handling in the dialogue tool to manage transient failures more effectively.
…gue processing

- Updated `handle_response` in `BaseAgent` to support single tool invocation parsing, improving clarity and efficiency.
- Enhanced JSON escaping in `AIProviderClient` to ensure proper formatting of prompts in API requests.
- Revised documentation in `agent_security_reviewer.md` and `project_summary.md` to clarify processing strategies and optimize information gathering.
- Added strict rules for dialogue calls to prevent unnecessary iterations during report generation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants