-
Notifications
You must be signed in to change notification settings - Fork 26
fix: harden tauri local access and stabilize task lifecycle #50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,12 +12,14 @@ import ( | |
| "net/http" | ||
| "os" | ||
| "path/filepath" | ||
| "runtime" | ||
| "strconv" | ||
| "strings" | ||
| "time" | ||
|
|
||
| "image-gen-service/internal/config" | ||
| "image-gen-service/internal/model" | ||
| "image-gen-service/internal/platform" | ||
| "image-gen-service/internal/provider" | ||
| "image-gen-service/internal/storage" | ||
| "image-gen-service/internal/worker" | ||
|
|
@@ -124,6 +126,77 @@ func defaultTimeoutSecondsForProvider(providerName string) int { | |
| } | ||
| } | ||
|
|
||
| func normalizePathForCheck(path string) string { | ||
| cleaned := filepath.Clean(path) | ||
| cleaned = strings.TrimRight(cleaned, string(filepath.Separator)) | ||
| if cleaned == "" { | ||
| return string(filepath.Separator) | ||
| } | ||
| return cleaned | ||
| } | ||
|
|
||
| func pathWithinRoot(path, root string) bool { | ||
| nPath := normalizePathForCheck(path) | ||
| nRoot := normalizePathForCheck(root) | ||
| if runtime.GOOS == "windows" { | ||
| nPath = strings.ToLower(nPath) | ||
| nRoot = strings.ToLower(nRoot) | ||
| } | ||
| if nPath == nRoot { | ||
| return true | ||
| } | ||
| rel, err := filepath.Rel(nRoot, nPath) | ||
| if err != nil { | ||
| return false | ||
| } | ||
| rel = strings.TrimSpace(rel) | ||
| if rel == "." { | ||
| return true | ||
| } | ||
| if rel == "" { | ||
| return false | ||
| } | ||
| return !strings.HasPrefix(rel, "..") | ||
| } | ||
|
|
||
| func allowedRefPathRoots() []string { | ||
| roots := make([]string, 0, 4) | ||
| if configDir, err := os.UserConfigDir(); err == nil && strings.TrimSpace(configDir) != "" { | ||
| roots = append(roots, filepath.Join(configDir, "com.dztool.banana")) | ||
| } | ||
| if cacheDir, err := os.UserCacheDir(); err == nil && strings.TrimSpace(cacheDir) != "" { | ||
| roots = append(roots, filepath.Join(cacheDir, "com.dztool.banana")) | ||
| } | ||
| roots = append(roots, os.TempDir()) | ||
| return roots | ||
| } | ||
|
|
||
| func validateRefPathForTauri(raw string) (string, error) { | ||
| trimmed := strings.TrimSpace(raw) | ||
| if trimmed == "" { | ||
| return "", fmt.Errorf("empty ref path") | ||
| } | ||
| abs, err := filepath.Abs(trimmed) | ||
| if err != nil { | ||
| return "", fmt.Errorf("invalid ref path: %w", err) | ||
| } | ||
| abs = filepath.Clean(abs) | ||
| resolved, err := filepath.EvalSymlinks(abs) | ||
| if err != nil { | ||
| return "", fmt.Errorf("ref path could not be resolved: %w", err) | ||
| } | ||
| real := filepath.Clean(strings.TrimSpace(resolved)) | ||
| if real == "" { | ||
| return "", fmt.Errorf("ref path could not be resolved") | ||
| } | ||
| for _, root := range allowedRefPathRoots() { | ||
| if pathWithinRoot(real, root) { | ||
| return real, nil | ||
| } | ||
| } | ||
| return "", fmt.Errorf("ref path is outside allowed directories") | ||
| } | ||
|
|
||
| // ProviderConfigRequest 设置 Provider 配置请求 | ||
| type ProviderConfigRequest struct { | ||
| ProviderName string `json:"provider_name" binding:"required"` | ||
|
|
@@ -429,10 +502,23 @@ func GenerateWithImagesHandler(c *gin.Context) { | |
| // 处理本地路径请求 (Tauri 优化) | ||
| for _, path := range req.RefPaths { | ||
| if path != "" { | ||
| content, err := os.ReadFile(path) | ||
| if !platform.IsTauriSidecar() { | ||
| Error(c, http.StatusBadRequest, 400, "refPaths 仅支持桌面端模式") | ||
| return | ||
| } | ||
| targetPath := path | ||
| validatedPath, validateErr := validateRefPathForTauri(path) | ||
| if validateErr != nil { | ||
| log.Printf("[API] 非法本地参考图路径: %s, err: %v\n", path, validateErr) | ||
| Error(c, http.StatusBadRequest, 400, "参考图路径不在允许目录内") | ||
| return | ||
| } | ||
| targetPath = validatedPath | ||
| content, err := os.ReadFile(targetPath) | ||
|
Comment on lines
+509
to
+517
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
| if err != nil { | ||
| log.Printf("[API] 读取本地参考图失败: %s, err: %v\n", path, err) | ||
| continue | ||
| log.Printf("[API] 读取本地参考图失败: %s, err: %v\n", targetPath, err) | ||
| Error(c, http.StatusBadRequest, 400, "读取本地参考图失败") | ||
| return | ||
| } | ||
| refImageBytes = append(refImageBytes, content) | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package platform | ||
|
|
||
| import "os" | ||
|
|
||
| func IsTauriSidecar() bool { | ||
| return os.Getenv("TAURI_PLATFORM") != "" || os.Getenv("TAURI_FAMILY") != "" | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The CORS implementation incorrectly handles the wildcard (
*) in theCORS_ALLOW_ORIGINSallowlist. If the allowlist contains*, theoriginInAllowlistfunction returnstruefor any origin, which is then reflected in theAccess-Control-Allow-Originheader withAccess-Control-Allow-Credentialsset totrue. This effectively bypasses browser security, allowing malicious websites to make authenticated requests if the allowlist is misconfigured with a wildcard. The logic should be updated to ensure that if a wildcard is used, credentials are not permitted, or the origin is not reflected unless it matches a specific trusted domain. Furthermore, the non-Tauri mode logic in this CORS middleware is complex and repetitive, especially whereif trimmedOrigin == ""andelse if len(corsAllowlist) == 0branches perform the samec.Writer.Header().Set("Access-Control-Allow-Origin", "*")operation. Reorganizing this logic to prioritize invalid origins and then handlecorsAllowlistpresence separately would improve clarity and maintainability.