{"record":{"id":"f6518fa9b72d8396","repo":"usememos/memos","slug":"internal-f6518f","errorCode":"Internal","errorMessage":"invalid uid","messagePattern":"invalid uid","errorType":"exception","errorClass":null,"httpStatus":500,"severity":"error","filePath":"store/attachment.go","lineNumber":92,"sourceCode":"\nconst (\n\tthumbnailCacheFolder = \".thumbnail_cache\"\n\tmotionCacheFolder    = \".motion_cache\"\n)\n\ntype deleteAttachmentStorageFailpointKey struct{}\n\n// ErrDeleteAttachmentStorageFailpoint is returned by the test-only attachment storage failpoint.\nvar ErrDeleteAttachmentStorageFailpoint = errors.New(\"delete attachment storage failpoint\")\n\n// WithDeleteAttachmentStorageFailpoint forces DeleteAttachmentStorage to return a failpoint error.\nfunc WithDeleteAttachmentStorageFailpoint(ctx context.Context) context.Context {\n\treturn context.WithValue(ctx, deleteAttachmentStorageFailpointKey{}, true)\n}\n\nfunc (s *Store) CreateAttachment(ctx context.Context, create *Attachment) (*Attachment, error) {\n\tif !base.UIDMatcher.MatchString(create.UID) {\n\t\treturn nil, errors.New(\"invalid uid\")\n\t}\n\treturn s.driver.CreateAttachment(ctx, create)\n}\n\nfunc (s *Store) ListAttachments(ctx context.Context, find *FindAttachment) ([]*Attachment, error) {\n\t// Set default limits to prevent loading too many attachments at once\n\tshouldApplyDefaultLimit := find.Limit == nil && find.MemoID == nil && len(find.MemoIDList) == 0 && !find.SkipDefaultLimit\n\tif shouldApplyDefaultLimit && find.GetBlob {\n\t\t// When fetching blobs, we should be especially careful with limits\n\t\tdefaultLimit := 10\n\t\tfind.Limit = &defaultLimit\n\t} else if shouldApplyDefaultLimit {\n\t\t// Even without blobs, let's default to a reasonable limit\n\t\tdefaultLimit := 100\n\t\tfind.Limit = &defaultLimit\n\t}\n\n\treturn s.driver.ListAttachments(ctx, find)","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/usememos/memos/blob/14d757ce1fb31c78590f374bc042f8dbedbc20d7/store/attachment.go#L74-L110","documentation":"Store.CreateAttachment validates the attachment UID against base.UIDMatcher (^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,34}[a-zA-Z0-9])?$) before delegating to the DB driver. The error means create.UID is empty, longer than 36 chars, or contains characters other than alphanumerics and inner hyphens. It fails fast before any SQL runs.","triggerScenarios":"CreateAttachment called with an unset UID, a UID starting or ending with '-', exceeding 36 characters, or containing '_', '.', '/', spaces, or other special characters.","commonSituations":"Using a UUID with dashes at the edges or underscores instead of hyphens; passing a filename as UID; generating IDs with a random-string helper that includes symbols; forgetting to set UID before calling the store.","solutions":["Generate UIDs as short alphanumeric strings, e.g. uuid.NewString() with hyphens removed/encoded or a nanoid restricted to [a-zA-Z0-9-]","Validate the UID with the same regex before calling CreateAttachment","Trim and re-check length (1-36 chars) if UIDs come from user input"],"exampleFix":"// before\nuid := rawFilename // \"my file (1).png\"\n// after\nuid := shortuuid.New() // alphanumeric, matches ^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,34}[a-zA-Z0-9])?$","handlingStrategy":"validation","validationCode":"uidRe := regexp.MustCompile(`^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,34}[a-zA-Z0-9])?$`)\nif !uidRe.MatchString(att.UID) {\n    return fmt.Errorf(\"invalid attachment uid %q\", att.UID)\n}\n_, err := store.CreateAttachment(ctx, att)","typeGuard":"func isValidUID(uid string) bool {\n    return base.UIDMatcher.MatchString(uid)\n}","tryCatchPattern":null,"preventionTips":["Generate UIDs from a restricted alphabet (alphanumerics plus inner hyphens, max 36 chars)","Never derive UIDs from raw filenames; sanitize first"],"tags":["store","attachment","uid","validation"],"backgroundTag":null,"analyzedSha":"14d757ce1fb31c78590f374bc042f8dbedbc20d7","analyzedAt":"2026-08-15T09:27:36.538Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}