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

  1. Sanitize the uploaded filename client-side to a simple basename with no separators or '..' before sending.
  2. On the server side of your integration, generate a safe random filename (e.g. gulu.Rand.String) instead of trusting the user-supplied name.
  3. 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

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


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/18bc93c80a08a2a7. Report an issue: GitHub.