siyuan-note/siyuan · error
asset path must be under assets
Error message
asset path must be under assets
What it means
Thrown by CreateAssetHistory when the cleaned assetPath does not start with the prefix "assets/". The function first cleans the path (filepath.Clean, ToSlash, trim leading "/"), then checks the prefix. This is the first line of defense ensuring only paths under the assets/ directory are accepted for history snapshotting. The check rejects paths like "data/file.txt", "temp/x", or any path not rooted under assets/.
Source
Thrown at kernel/model/history.go:869
})
return
}
func generateAssetsHistory() {
assets := recentModifiedAssets()
if 1 > len(assets) {
return
}
if err := createAssetsHistory(assets); err != nil {
logging.LogErrorf("generate assets history failed: %s", err)
}
}
// CreateAssetHistory 为指定资源文件创建历史快照。
func CreateAssetHistory(assetPath string) (err error) {
assetPath = strings.TrimPrefix(filepath.ToSlash(filepath.Clean(filepath.FromSlash(assetPath))), "/")
if !strings.HasPrefix(assetPath, "assets/") {
return errors.New("asset path must be under assets")
}
assetAbsPath := filepath.Join(util.DataDir, filepath.FromSlash(assetPath))
assetsDir := filepath.Join(util.DataDir, "assets")
if !gulu.File.IsSubPath(assetsDir, assetAbsPath) {
return errors.New("asset path must be under assets")
}
info, statErr := os.Stat(assetAbsPath)
if statErr != nil {
return statErr
}
if info.IsDir() {
return errors.New("asset path must be a file")
}
return createAssetsHistory([]string{assetAbsPath})
}
func createAssetsHistory(assets []string) (err error) {View on GitHub (pinned to 251596fc0d)
Solutions
- Ensure the assetPath argument is a relative path starting with "assets/" (e.g. "assets/image-202401010000.png").
- If the caller has only a filename, prefix it with "assets/" before calling the API.
- As an API client, validate the path starts with "assets/" before submitting.
Example fix
// before
await post('/api/history/createAssetHistory', { assetPath: 'image-202401010000.png' })
// after
await post('/api/history/createAssetHistory', { assetPath: 'assets/image-202401010000.png' }) Defensive patterns
Strategy: validation
Validate before calling
// Validate the asset path starts with 'assets/' before calling the API
function isValidAssetPath(p) {
const cleaned = path.posix.normalize(p).replace(/^\//, '')
return cleaned.startsWith('assets/')
}
if (isValidAssetPath(assetPath)) {
await post('/api/history/createAssetHistory', { assetPath })
} Type guard
// Type guard: check if a string is a valid asset path
function isAssetPath(p: string): boolean {
const cleaned = p.replace(/^\//, '')
return cleaned.startsWith('assets/')
} Prevention
- Always prefix asset paths with 'assets/' — the API expects relative paths from the workspace data root.
- Do not pass full filesystem paths, document paths (.sy), or internal kernel paths.
- Validate the path prefix in the caller before submitting.
When it happens
Trigger: Calling POST /api/history/createAssetHistory (or equivalent) with an assetPath that does not begin with "assets/" after cleaning. For example: "file.txt", "/etc/passwd", "data/20240101/file.sy", or any non-asset path. The prefix check is literal string comparison after path normalization.
Common situations: An API client passes a full filesystem path or a SiYuan-internal path instead of the relative assets/ path; a plugin constructs the path incorrectly by omitting the "assets/" prefix; the user or tool references a document path (.sy) instead of an asset path.
Related errors
- asset path must be a file
- invalid import token
- import path is not sub path of import dir
- invalid custom emoji URL
- invalid asset type [%s]
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/1fb5707693ba9110.
Report an issue: GitHub.