siyuan-note/siyuan · warning

asset path [%s] does not match data path [%s]

Error message

asset path [%s] does not match data path [%s]

What it means

When an asset request is resolved by dataPath, the handler converts the data-relative path back to the canonical asset path and requires that it equals the requested cleanPath. If they differ (or the conversion fails), the request is rejected. This is a safety check preventing a dataPath parameter from being used to smuggle access to a different file than the URL path names.

Source

Thrown at kernel/server/serve.go:994

	if err != nil {
		return false
	}
	_, validatedAbsPath, err := model.ResolveDataAssetPath(filepath.ToSlash(dataRelativePath))
	return err == nil && filepath.Clean(validatedAbsPath) == filepath.Clean(assetAbsPath)
}

func resolveAssetRequestPath(cleanPath, boxID, dataPath string) (string, error) {
	if dataPath != "" {
		if boxID != "" {
			return "", errors.New("box and dataPath cannot be used together")
		}
		dataRelativePath, assetAbsPath, err := model.ResolveDataAssetPath(dataPath)
		if err != nil {
			return "", err
		}
		assetPath, _, ok := model.AssetPathFromDataRelativePath(dataRelativePath)
		if !ok || assetPath != cleanPath {
			return "", fmt.Errorf("asset path [%s] does not match data path [%s]", cleanPath, dataPath)
		}
		return assetAbsPath, nil
	}
	if boxID != "" {
		return model.GetAssetAbsPathInBox(cleanPath, boxID)
	}
	return model.GetAssetAbsPath(cleanPath)
}

func serveAssets(ginServer *gin.Engine) {
	ginServer.POST("/upload", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, model.Upload)

	ginServer.GET("/assets/*path", model.CheckAuth, func(context *gin.Context) {
		requestPath := context.Param("path")
		if "/" == requestPath || "" == requestPath {
			// 禁止访问根目录 Disable HTTP access to the /assets/ path https://github.com/siyuan-note/siyuan/issues/15257
			context.Status(http.StatusForbidden)
			return

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Make the URL path and the `dataPath` parameter refer to the same file (dataPath is workspace-data-relative, e.g. assets/foo.png)
  2. Regenerate the URL if the asset was moved or renamed
  3. If you only know the data-relative path, request the asset directly by that path without a conflicting URL path
  4. Do not attempt to reach files outside the data directory via dataPath — model.ResolveDataAssetPath validates and rejects path traversal separately

Example fix

// before
GET /assets/wrong-name.png?dataPath=assets/correct-name.png
// after
GET /assets/correct-name.png?dataPath=assets/correct-name.png
Defensive patterns

Strategy: validation

Validate before calling

// client-side: ensure URL path and dataPath refer to the same file
const derived = dataPath.replace(/^assets\//, "");
if (derived !== name) throw new Error("URL path and dataPath must reference the same asset");

Try / catch

const res = await fetch(url);
if (!res.ok) {
  const msg = await res.text();
  if (msg.includes("does not match data path")) {
    // regenerate URL from the canonical dataPath
  }
}

Prevention

When it happens

Trigger: resolveAssetRequestPath called with dataPath set where model.AssetPathFromDataRelativePath(dataRelativePath) returns ok=false, or the derived assetPath differs from the cleanPath taken from the URL (kernel/server/serve.go:994). E.g. URL /assets/a.png with dataPath=/etc/passwd or with a dataPath pointing at another asset.

Common situations: Hand-crafted or proxied URLs where the path segment and dataPath were generated at different times; path manipulation attempts caught by the guard; assets moved/renamed so the dataPath no longer maps to the URL path.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/c96d3a08c6169e0d. Report an issue: GitHub.