{"record":{"id":"33935d20727789ef","repo":"siyuan-note/siyuan","slug":"asset-path-escapes-data-directory-s","errorCode":null,"errorMessage":"asset path escapes data directory: %s","messagePattern":"asset path escapes data directory: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/assets.go","lineNumber":909,"sourceCode":"func ResolveDataAssetPath(assetPath string) (relativePath, absPath string, err error) {\n\tif assetPath == \"\" {\n\t\terr = errors.New(\"asset path is required\")\n\t\treturn\n\t}\n\n\tnativePath := filepath.FromSlash(assetPath)\n\tif filepath.IsAbs(nativePath) || filepath.VolumeName(nativePath) != \"\" ||\n\t\t(len(nativePath) > 0 && os.IsPathSeparator(nativePath[0])) {\n\t\terr = fmt.Errorf(\"asset path must be relative to data directory: %s\", assetPath)\n\t\treturn\n\t}\n\n\tnativePath = filepath.Clean(nativePath)\n\tabsPath = filepath.Join(util.DataDir, nativePath)\n\tdataRelativePath, relErr := filepath.Rel(util.DataDir, absPath)\n\tif relErr != nil || dataRelativePath == \".\" || dataRelativePath == \"..\" ||\n\t\tstrings.HasPrefix(dataRelativePath, \"..\"+string(filepath.Separator)) {\n\t\terr = fmt.Errorf(\"asset path escapes data directory: %s\", assetPath)\n\t\treturn\n\t}\n\n\tparts := strings.Split(filepath.ToSlash(dataRelativePath), \"/\")\n\tassetDirIndex := -1\n\tswitch {\n\tcase len(parts) > 1 && parts[0] == \"assets\":\n\t\tassetDirIndex = 0\n\tcase len(parts) > 2 && ast.IsNodeIDPattern(parts[0]):\n\t\tfor i := 1; i < len(parts)-1; i++ {\n\t\t\tif parts[i] == \"assets\" {\n\t\t\t\tassetDirIndex = i\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif assetDirIndex > 0 {\n\t\t\tboxConfPath := filepath.Join(util.DataDir, parts[0], \".siyuan\", \"conf.json\")\n\t\t\tif !filelock.IsExist(boxConfPath) {","sourceCodeStart":891,"sourceCodeEnd":927,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/assets.go#L891-L927","documentation":"Returned by ResolveDataAssetPath when, after filepath.Join(DataDir, nativePath) and filepath.Rel, the relative result is \".\", \"..\", or begins with \"../\" — i.e. the input escapes util.DataDir via parent-directory segments. This is the second traversal guard and catches \"../\"-based escapes that survive filepath.Clean.","triggerScenarios":"Passing \"../../../../etc/passwd\", \"..\\\\..\\\\secret\", or any path whose cleaned, joined form resolves above the data directory. The check fires after Clean, so obfuscations like \"a/../../..\" are normalized first.","commonSituations":"Malicious user-controlled path input; a buggy path-join upstream that prepended the wrong base; symlink-unaware code that fed a relative-escape path.","solutions":["Never forward raw user input as the asset path; whitelist via an asset catalog.","If building the path programmatically, assert it stays under the data dir using filepath.Rel before calling."],"exampleFix":"// before\nrel, abs, err := model.ResolveDataAssetPath(userInput) // userInput = \"../../etc/passwd\"\n\n// after — reject escapes before calling\nrel, abs, err := model.ResolveDataAssetPath(userInput)\nif err != nil { /* user input was unsafe */ }","handlingStrategy":"validation","validationCode":"// Pre-check containment before calling the resolver.\nabs := filepath.Join(util.DataDir, filepath.Clean(filepath.FromSlash(assetPath)))\nrel, err := filepath.Rel(util.DataDir, abs)\nif err != nil || rel == \".\" || rel == \"..\" || strings.HasPrefix(rel, \"..\"+string(filepath.Separator)) {\n    return fmt.Errorf(\"asset path escapes data directory: %s\", assetPath)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never forward raw user input as the asset path.","Maintain an allow-list of asset paths from the asset catalog.","Treat escape attempts as security events, not transient errors."],"tags":["security","validation","assets","path-traversal"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}