{"record":{"id":"b2cee5cbc9e8b725","repo":"siyuan-note/siyuan","slug":"invalid-layer-source-w","errorCode":null,"errorMessage":"invalid layer source: %w","messagePattern":"invalid layer source: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/boot_appearance.go","lineNumber":465,"sourceCode":"\t\tif _, _, err = validateBootAppearanceResource(pluginDir, appearanceDir, manifest.Style, \"style\"); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid style: %w\", err)\n\t\t}\n\t\tret.Style = bootAppearanceAssetURL(pkg.Name, appearanceID, manifest.Style)\n\t}\n\n\tlayerIDs := map[string]bool{}\n\tfor _, layer := range manifest.Layers {\n\t\tif layer == nil || !isValidBootAppearanceID(layer.ID) || layerIDs[layer.ID] {\n\t\t\terr = errors.New(\"invalid or duplicate layer ID\")\n\t\t\treturn nil, err\n\t\t}\n\t\tlayerIDs[layer.ID] = true\n\t\tif layer.Type != \"image\" && layer.Type != \"video\" {\n\t\t\terr = fmt.Errorf(\"unsupported layer type [%s]\", layer.Type)\n\t\t\treturn nil, err\n\t\t}\n\t\tif _, _, err = validateBootAppearanceResource(pluginDir, appearanceDir, layer.Src, layer.Type); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid layer source: %w\", err)\n\t\t}\n\t\tif layer.Type == \"video\" {\n\t\t\tif layer.Poster == \"\" {\n\t\t\t\terr = errors.New(\"video poster is required\")\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif _, _, err = validateBootAppearanceResource(pluginDir, appearanceDir, layer.Poster, \"image\"); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid video poster: %w\", err)\n\t\t\t}\n\t\t} else if layer.Poster != \"\" {\n\t\t\terr = errors.New(\"image layer cannot declare a poster\")\n\t\t\treturn nil, err\n\t\t}\n\t\tfit := layer.Fit\n\t\tif fit == \"\" {\n\t\t\tfit = \"cover\"\n\t\t}\n\t\tif !isValidBootAppearanceFit(fit) {","sourceCodeStart":447,"sourceCodeEnd":483,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/boot_appearance.go#L447-L483","documentation":"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\".","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\n{\"id\": \"anim\", \"type\": \"video\", \"src\": \"C:\\\\videos\\\\anim.webm\"}\n// after\n{\"id\": \"anim\", \"type\": \"video\", \"src\": \"anim.mp4\"}  // real MP4 inside the appearance dir","handlingStrategy":"validation","validationCode":"import { existsSync, statSync } from \"fs\";\nmanifest.layers?.forEach(l => {\n  if (l.src.startsWith(\"/\") || l.src.includes(\"..\") || l.src.includes(\"\\\\\")) throw new Error(`layer ${l.id}: unsafe src path`);\n  const p = join(appearanceDir, l.src);\n  if (!existsSync(p) || !statSync(p).isFile()) throw new Error(`layer ${l.id}: src missing: ${l.src}`);\n});","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["validation","media","plugin","path"],"backgroundTag":"resource-not-found","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}