siyuan-note/siyuan · error
invalid layer source: %w
Error message
invalid layer source: %w
What it means
The layer's src must pass validateBootAppearanceResource for its declared type: safe relative path, no symlink escape, file exists, allowed extension (.png/.jpg/.jpeg/.webp for image, .mp4 for video), size cap, and content sniffing must match the declared type. Failures are wrapped as "invalid layer source".
Source
Thrown at kernel/model/boot_appearance.go:465
if _, _, err = validateBootAppearanceResource(pluginDir, appearanceDir, manifest.Style, "style"); err != nil {
return nil, fmt.Errorf("invalid style: %w", err)
}
ret.Style = bootAppearanceAssetURL(pkg.Name, appearanceID, manifest.Style)
}
layerIDs := map[string]bool{}
for _, layer := range manifest.Layers {
if layer == nil || !isValidBootAppearanceID(layer.ID) || layerIDs[layer.ID] {
err = errors.New("invalid or duplicate layer ID")
return nil, err
}
layerIDs[layer.ID] = true
if layer.Type != "image" && layer.Type != "video" {
err = fmt.Errorf("unsupported layer type [%s]", layer.Type)
return nil, err
}
if _, _, err = validateBootAppearanceResource(pluginDir, appearanceDir, layer.Src, layer.Type); err != nil {
return nil, fmt.Errorf("invalid layer source: %w", err)
}
if layer.Type == "video" {
if layer.Poster == "" {
err = errors.New("video poster is required")
return nil, err
}
if _, _, err = validateBootAppearanceResource(pluginDir, appearanceDir, layer.Poster, "image"); err != nil {
return nil, fmt.Errorf("invalid video poster: %w", err)
}
} else if layer.Poster != "" {
err = errors.New("image layer cannot declare a poster")
return nil, err
}
fit := layer.Fit
if fit == "" {
fit = "cover"
}
if !isValidBootAppearanceFit(fit) {View on GitHub (pinned to 8641553a1f)
Solutions
- Ensure the file referenced by src exists inside the appearance directory and the path is relative with '/' separators
- Use an allowed extension and matching real content: .png/.jpg/.jpeg/.webp for image layers, .mp4 (H.264/AAC) for video layers
- Re-encode or compress the asset so it is under maxBootAppearanceImageSize / maxBootAppearanceVideoSize
- Replace any symlinks in the appearance directory with real files
Example fix
// before
{"id": "anim", "type": "video", "src": "C:\\videos\\anim.webm"}
// after
{"id": "anim", "type": "video", "src": "anim.mp4"} // real MP4 inside the appearance dir Defensive patterns
Strategy: validation
Validate before calling
import { existsSync, statSync } from "fs";
manifest.layers?.forEach(l => {
if (l.src.startsWith("/") || l.src.includes("..") || l.src.includes("\\")) throw new Error(`layer ${l.id}: unsafe src path`);
const p = join(appearanceDir, l.src);
if (!existsSync(p) || !statSync(p).isFile()) throw new Error(`layer ${l.id}: src missing: ${l.src}`);
}); Prevention
- Package all media referenced by src inside the appearance directory
- Use forward slashes and relative paths only
- Match extension to real content: real .mp4 for video, real png/jpg/webp for images
- Compress assets below the kernel size caps before release
When it happens
Trigger: GetBootAppearances/getBootAppearanceByID loading a manifest whose layer.src is missing on disk, uses a forbidden path (absolute, '..' traversal, backslashes, leading '/'), has a mismatched extension or actual MIME type (e.g. .mp4 that is really a WebM), or exceeds the size cap.
Common situations: Asset not shipped with the plugin (missing file in the appearance directory); renamed media without updating src; a video renamed to .mp4 without re-encoding (content sniff fails); oversized video or image; path written on Windows with backslashes.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- invalid style: %w
- Plugin ${pluginLabel} returned an invalid action
- Plugin ${pluginLabel} returned invalid input: ${targetValida
- invalid path
- asset path must be absolute
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/b2cee5cbc9e8b725.
Report an issue: GitHub.