{"record":{"id":"7dac842b12387c86","repo":"siyuan-note/siyuan","slug":"encrypted-heif-cache-requires-a-notebook-id","errorCode":null,"errorMessage":"encrypted HEIF cache requires a notebook ID","messagePattern":"encrypted HEIF cache requires a notebook ID","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/heif/cache.go","lineNumber":92,"sourceCode":"\tmemoryCacheItems = map[string]*list.Element{}\n\tmemoryCacheBytes int\n)\n\nfunc IsPath(path string) bool {\n\tpath = strings.ToLower(path)\n\tif index := strings.IndexAny(path, \"?#\"); index >= 0 {\n\t\tpath = path[:index]\n\t}\n\text := filepath.Ext(path)\n\treturn ext == \".heic\" || ext == \".heif\"\n}\n\nfunc GetOrCreate(ctx context.Context, source []byte, options Options) (Result, error) {\n\tif options.Mode != ModePreview && options.Mode != ModeThumbnail {\n\t\treturn Result{}, ErrInvalidMode\n\t}\n\tif options.Encrypted && options.BoxID == \"\" {\n\t\treturn Result{}, errors.New(\"encrypted HEIF cache requires a notebook ID\")\n\t}\n\tif len(source) == 0 {\n\t\treturn Result{}, errors.New(\"empty HEIF image\")\n\t}\n\tif len(source) > MaxInputBytes {\n\t\treturn Result{}, ErrInputTooLarge\n\t}\n\tif err := ctx.Err(); err != nil {\n\t\treturn Result{}, err\n\t}\n\n\tdigest := cacheDigest(source, options.Mode)\n\tetag := `\"heif-` + digest + `\"`\n\tcacheKey := digest\n\tif options.Encrypted {\n\t\tcacheKey = options.BoxID + \":\" + digest\n\t\tif data := getMemoryCache(cacheKey); data != nil {\n\t\t\treturn Result{Data: data, ETag: etag}, nil","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/heif/cache.go#L74-L110","documentation":"The HEIF cache refuses to operate on encrypted images when the Options do not specify a notebook ID. For encrypted notebooks, cache keys and storage must be namespaced per notebook so plaintext previews never leak across notebooks; a missing BoxID would make the cache key ambiguous or unsafe, so GetOrCreate rejects the call up front.","triggerScenarios":"Calling heif.GetOrCreate with Options{Encrypted: true} while leaving Options.BoxID empty. Detected before any decoding happens, alongside the mode and emptiness checks.","commonSituations":"Callers building Options programmatically and forgetting to propagate the notebook ID; refactoring that made Encrypted default to true but left BoxID unset; test harnesses constructing partial Options.","solutions":["Set Options.BoxID to the owning notebook's ID whenever Options.Encrypted is true.","Only set Encrypted=true when the source image really comes from an encrypted notebook; otherwise keep Encrypted=false.","Construct Options through a helper that derives Encrypted/BoxID together from the asset's notebook context to keep them consistent."],"exampleFix":"// before\nopts := heif.Options{Mode: heif.ModePreview, Encrypted: true} // BoxID missing\nres, err := heif.GetOrCreate(ctx, data, opts) // error\n// after\nopts := heif.Options{Mode: heif.ModePreview, Encrypted: true, BoxID: \"20240101120000-abcdefg\"}\nres, err := heif.GetOrCreate(ctx, data, opts)","handlingStrategy":"validation","validationCode":"if options.Encrypted && options.BoxID == \"\" {\n    return errors.New(\"cannot build HEIF cache options: encrypted image requires BoxID\")\n}","typeGuard":null,"tryCatchPattern":"res, err := heif.GetOrCreate(ctx, data, opts)\nif errors.Is(err, errors.New(\"encrypted HEIF cache requires a notebook ID\")) || err != nil && strings.Contains(err.Error(), \"requires a notebook ID\") {\n    return fmt.Errorf(\"fix Options: set BoxID for encrypted image: %w\", err)\n}","preventionTips":["Build Options through a single constructor that always pairs Encrypted with BoxID.","Pass the notebook ID down through the asset-processing call chain explicitly.","Add a unit test asserting every Options built in encrypted mode carries a non-empty BoxID."],"tags":["heif","cache","encryption","validation"],"backgroundTag":"missing-required-argument","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}