{"record":{"id":"ca81f50b42ee58d7","repo":"JuliusBrussee/caveman","slug":"sqlite-parent-q-has-no-restrictive-dacl","errorCode":null,"errorMessage":"sqlite parent %q has no restrictive DACL","messagePattern":"sqlite parent %q has no restrictive DACL","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"engine/ccr/sqlite_parent_security_windows.go","lineNumber":27,"sourceCode":"\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{\n\t\twindows.WinWorldSid,\n\t\twindows.WinAuthenticatedUserSid,\n\t\twindows.WinBuiltinUsersSid,\n\t\twindows.WinBuiltinGuestsSid,","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/engine/ccr/sqlite_parent_security_windows.go#L9-L45","documentation":"engine/ccr on Windows treats a parent directory with no DACL as unsafe for the SQLite database: with no discretionary ACL there is no way to prove other users cannot modify the directory, so it fails closed with 'sqlite parent %q has no restrictive DACL' rather than risk database replacement.","triggerScenarios":"validateSQLiteParentSecurity queries the parent directory's security descriptor and descriptor.DACL() returns nil (or errors) — the directory exists but carries no DACL, typically on volumes or share configurations that don't apply Windows ACLs.","commonSituations":"Directories created on FAT/exFAT drives, some network shares, or by tools that strip ACLs; freshly mounted volumes before ACL inheritance is applied; unusual temp locations set via environment overrides.","solutions":["Point the store at a local NTFS directory under the user's profile (%LOCALAPPDATA%) which always has a DACL","Apply an explicit restrictive ACL to the directory: icacls <dir> /inheritance:r /grant:r \"%USERNAME%\":(OI)(CI)F","Confirm the directory is on NTFS and not a network share; move the data off unsupported filesystems"],"exampleFix":"// before\nstore, _ := engine.OpenCCRStore(os.TempDir() + \"\\\\ccr\\\\db.sqlite\") // exotic temp mount, no DACL\n// after\nos.MkdirAll(filepath.Join(os.Getenv(\"LOCALAPPDATA\"), \"myapp\"), 0o700)\nstore, _ := engine.OpenCCRStore(filepath.Join(os.Getenv(\"LOCALAPPDATA\"), \"myapp\", \"db.sqlite\"))","handlingStrategy":"validation","validationCode":"// pre-check: directory must have a DACL\nsd, _ := windows.GetNamedSecurityInfo(parentDir, windows.SE_FILE_OBJECT, windows.DACL_SECURITY_INFORMATION)\nif sd != nil {\n\tif dacl, _, err := sd.DACL(); err != nil || dacl == nil {\n\t\treturn fmt.Errorf(\"%s has no DACL; apply one via icacls\", parentDir)\n\t}\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Place databases in user-profile NTFS directories, which always carry a DACL","Apply explicit ACLs (icacls <dir> /inheritance:r /grant:r user:F) to custom data dirs","Verify volume type before choosing a storage location for app data"],"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"}