siyuan-note/siyuan · error
import path is not sub path of import dir
Error message
import path is not sub path of import dir
What it means
Returned by saveImportUpload (import.go:331) when, after taking filepath.Base(files[0].Filename) and joining it with the random importDir, the resulting writePath is NOT a subpath of importDir. This is a path-traversal guard: a filename containing path separators or '..' could escape the temp import directory. gulu.File.IsSubPath(importDir, writePath) fails, so the upload is refused and the temp dir cleaned up.
Source
Thrown at kernel/api/import.go:331
func saveImportUpload(c *gin.Context) (form *multipart.Form, writePath string, cleanup func(), err error) {
form, err = c.MultipartForm()
if err != nil {
return
}
files := form.File["file"]
if len(files) < 1 {
err = errors.New("no file found")
return
}
importDir := filepath.Join(util.TempDir, "import", gulu.Rand.String(7))
if err = os.MkdirAll(importDir, 0755); err != nil {
return
}
cleanup = func() { _ = os.RemoveAll(importDir) }
writePath = filepath.Join(importDir, filepath.Base(files[0].Filename))
if !gulu.File.IsSubPath(importDir, writePath) {
err = errors.New("import path is not sub path of import dir")
cleanup()
return
}
if err = c.SaveUploadedFile(files[0], writePath); err != nil {
cleanup()
}
return
}
func importData(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
util.PushEndlessProgress(model.Conf.Language(73))
defer util.ClearPushProgress(100)
form, err := c.MultipartForm()View on GitHub (pinned to 251596fc0d)
Solutions
- Sanitize the uploaded filename client-side to a simple basename with no separators or '..' before sending.
- On the server side of your integration, generate a safe random filename (e.g. gulu.Rand.String) instead of trusting the user-supplied name.
- If you control the client, set the FormData filename to a plain slug like 'import.zip'.
Defensive patterns
Strategy: validation
Validate before calling
// Strip traversal/separator chars from the filename before upload
const safeName = uploadedFile.name.replace(/[^A-Za-z0-9._-]/g, '_');
fd.append('file', blob, safeName); Try / catch
try { await importUpload(fd); }
catch (e) { if (/not sub path/.test(e.msg)) { fd.set('file', blob, 'import.zip'); await importUpload(fd); } else throw e; } Prevention
- Always sanitize uploaded filenames to a plain basename.
- Prefer generating a random server-side filename over trusting client input.
- Reject filenames containing path separators or '..' client-side.
When it happens
Trigger: Uploading a file whose Filename (content-disposition) contains directory traversal sequences like '../escape.zip', absolute paths like '/etc/x', or backslashes that filepath.Base does not fully neutralize on the running OS. The check at import.go:330 fails and cleanup() runs at line 332.
Common situations: Malicious or malformed client sending a crafted filename. A reverse-proxy or middleware rewriting the filename. Edge cases where filepath.Base returns a segment that still resolves outside importDir on the host OS (e.g. Windows drive letters, UNC paths).
Related errors
- invalid import token
- refuse to write decrypted asset inside workspace
- source is not an encrypted asset
- import task not found or expired
- no file found
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/18bc93c80a08a2a7.
Report an issue: GitHub.