siyuan-note/siyuan · error

boot appearance asset forbidden

Error message

boot appearance asset forbidden

What it means

ErrBootAppearanceAssetForbidden guards serving boot appearance asset files. ResolveBootAppearanceAsset refuses paths whose provider/appearance don't match the persisted selection, and validateBootAppearanceResource rejects files that escape the appearance directory (filepath.Rel fails or the relative path leaves the root) — blocking path traversal and cross-plugin asset access.

Solutions

  1. Only request assets under the currently selected provider/appearance (re-resolve after any selection change)
  2. Ensure request paths are relative and contain no '..' or leading '/' components
  3. Remove or avoid symlinks escaping the appearance directory
  4. Clear stale selection state via SetBootAppearance("") so resolution matches the assets being served

Example fix

// before
url := "/boot-appearance/other-plugin/other-appearance/logo.svg" // mismatch or traversal
// after
sel := model.GetSelectedBootAppearance()
url := fmt.Sprintf("/boot-appearance/%s/%s/logo.svg", sel.Provider, sel.Appearance)
Defensive patterns

Strategy: validation

Validate before calling

clean := func(p string) bool { return p == path.Clean("/"+p)[1:] && !strings.Contains(p, "..") }
if !clean(assetPath) { return errors.New("illegal asset path") }

Type guard

func safeRelPath(p string) bool {
  rel, err := filepath.Rel(base, filepath.Join(base, p)); return err == nil && rel == p && !strings.HasPrefix(rel, "..")
}

Try / catch

data, err := model.ResolveBootAppearanceAsset(p, id, rel)
if errors.Is(err, model.ErrBootAppearanceAssetForbidden) {
  http.Error(w, "forbidden", http.StatusForbidden)
}

Prevention

When it happens

Trigger: Requesting a boot appearance asset for a plugin/appearance that is not the currently selected one (line 264); requesting a path that resolves outside the appearance directory via ../ traversal or symlinks (line 526).

Common situations: Crafted or stale URLs pointing at another plugin's assets; paths containing '..' segments or absolute components; symlinks inside an appearance dir pointing outward; serving cached asset URLs after the selection changed.

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@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/f42c92aea9a39d40. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/boot_appearance.go:59

	bootAppearanceSchemaVersion = 1
	bootAppearanceDirName       = "boot-appearances"
	bootAppearanceConfigName    = "boot-appearance.json"
	bootAppearanceManifestName  = "boot.json"

	maxBootAppearanceManifestSize = 200 * 1024
	maxBootAppearanceStyleSize    = 200 * 1024
	maxBootAppearanceImageSize    = 5 * 1024 * 1024
	maxBootAppearanceVideoSize    = 20 * 1024 * 1024
	maxBootAppearanceTotalSize    = 50 * 1024 * 1024
	maxBootAppearanceLayers       = 8
	maxBootAppearanceEntries      = 256
	maxBootAppearancePathDepth    = 16
	maxBootAppearancePathLength   = 512
)

var (
	ErrBootAppearanceNotFound       = errors.New("boot appearance not found")
	ErrBootAppearanceAssetForbidden = errors.New("boot appearance asset forbidden")

	bootAppearanceIDPattern    = regexp.MustCompile(`^[a-z0-9]+(?:-[a-z0-9]+)*$`)
	bootAppearanceColorPattern = regexp.MustCompile(`^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$`)
	bootAppearanceConfLock     sync.RWMutex
)

// BootAppearanceSelection 表示当前工作空间选择的启动页外观。
type BootAppearanceSelection struct {
	SchemaVersion int    `json:"schemaVersion"`
	Provider      string `json:"provider"`
	Appearance    string `json:"appearance"`
}

// BootAppearance 描述已经校验且可安全交给启动页渲染的外观。
type BootAppearance struct {
	Enabled         bool                      `json:"enabled"`
	Provider        string                    `json:"provider,omitempty"`
	Appearance      string                    `json:"appearance,omitempty"`

View on GitHub (pinned to 9f775e8a12)