{"record":{"id":"513a4f0b993d9662","repo":"JuliusBrussee/caveman","slug":"sqlite-parent-q-has-no-security-descriptor","errorCode":null,"errorMessage":"sqlite parent %q has no security descriptor","messagePattern":"sqlite parent %q has no security descriptor","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"engine/ccr/sqlite_parent_security_windows.go","lineNumber":23,"sourceCode":"import (\n\t\"fmt\"\n\t\"os\"\n\t\"unsafe\"\n\n\t\"golang.org/x/sys/windows\"\n)\n\nfunc validateSQLiteParentSecurity(path string, _ os.FileInfo) error {\n\tdescriptor, err := windows.GetNamedSecurityInfo(\n\t\tpath,\n\t\twindows.SE_FILE_OBJECT,\n\t\twindows.DACL_SECURITY_INFORMATION,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"inspect sqlite parent ACL %q: %w\", path, err)\n\t}\n\tif descriptor == nil {\n\t\treturn fmt.Errorf(\"sqlite parent %q has no security descriptor\", path)\n\t}\n\tdacl, _, err := descriptor.DACL()\n\tif err != nil || dacl == nil {\n\t\treturn fmt.Errorf(\"sqlite parent %q has no restrictive DACL\", path)\n\t}\n\tbroad, err := windowsACLGrantsBroadWrite(dacl)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"inspect sqlite parent ACL %q: %w\", path, err)\n\t}\n\tif broad {\n\t\treturn fmt.Errorf(\"sqlite parent %q grants broad Windows write access\", path)\n\t}\n\treturn nil\n}\n\nfunc windowsACLGrantsBroadWrite(dacl *windows.ACL) (bool, error) {\n\tbroadSIDs := make([]*windows.SID, 0, 4)\n\tfor _, sidType := range []windows.WELL_KNOWN_SID_TYPE{","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/engine/ccr/sqlite_parent_security_windows.go#L5-L41","documentation":"engine/ccr on Windows requires the SQLite parent directory to expose a security descriptor with a DACL so it can prove other users cannot write there. A nil descriptor means the OS returned no security information at all, which the library treats as unverifiable and fails closed with 'sqlite parent %q has no security descriptor'.","triggerScenarios":"validateSQLiteParentSecurity receives a nil SECURITY_DESCRIPTOR from GetNamedSecurityInfo despite no error — seen with unusual filesystem objects, non-NTFS volumes, or virtualized/placeholder paths that report no security info.","commonSituations":"Databases on FAT32/exFAT or RAM disks without ACL support; WSL-mounted or container-mapped paths presenting no Windows security descriptor; paths inside isolated user-profile virtualization layers.","solutions":["Relocate the database to a standard local NTFS directory (e.g. %LOCALAPPDATA%\\myapp) that reports a normal security descriptor","Verify the path is a real directory on an NTFS volume (fsutil fsinfo volumeinfo <drive>) rather than a network, FAT, or virtual mount","If a placeholder/virtualization layer strips descriptors, exclude the app data dir from it or run with it disabled"],"exampleFix":"// before\nstore, _ := engine.OpenCCRStore(`Z:\\ramdisk\\db.sqlite`) // non-NTFS volume, no descriptor\n// after\nstore, _ := engine.OpenCCRStore(filepath.Join(os.Getenv(\"LOCALAPPDATA\"), \"myapp\", \"db.sqlite\"))","handlingStrategy":"validation","validationCode":"// pre-check: directory must report a security descriptor\nsd, err := windows.GetNamedSecurityInfo(parentDir, windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION)\nif err != nil || sd == nil {\n\treturn fmt.Errorf(\"%s does not expose a security descriptor; use a local NTFS dir\", parentDir)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Avoid FAT32/exFAT, RAM disks, and WSL/container-mapped paths for app data","Use %LOCALAPPDATA% (NTFS) for the database location","Exclude the data dir from virtualization layers that strip security descriptors"],"tags":["go","windows","sqlite","security","acl","filesystem"],"backgroundTag":"insufficient-permissions","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}