JuliusBrussee/caveman · error

sqlite parent is group/world writable

Error message

sqlite parent %q is group/world writable

What it means

engine/ccr hardens its SQLite database by requiring the parent directory to prevent other local users from replacing the (0600) database file. On POSIX, a parent directory that is group- or world-writable throws this error unless it is sticky (like /tmp), where the sticky bit already blocks replacement by other users.

Solutions

  1. Move the database into a private directory owned by the running user, e.g. chmod 700 ~/.local/state/myapp or the app's own data dir
  2. Fix the parent directory permissions: chmod go-w <parent> (e.g. 0755 or 0700) before starting the store
  3. If you intentionally use a shared dir like /tmp, ensure it has the sticky bit (chmod +t) or preferably use a per-user directory instead

Example fix

// before
store, _ := engine.OpenCCRStore("/var/tmp/shared/ccr.sqlite") // /var/tmp/shared is 0777
// after
os.Chmod("/var/tmp/shared", 0o700) // or use a per-user data dir
store, _ := engine.OpenCCRStore("/var/tmp/shared/ccr.sqlite")
Defensive patterns

Strategy: validation

Validate before calling

info, err := os.Stat(filepath.Dir(path))
if err != nil { return err }
perm := info.Mode().Perm()
if perm&0o022 != 0 && info.Mode()&os.ModeSticky == 0 {
	return fmt.Errorf("parent %s must not be group/world writable", filepath.Dir(path))
}

Prevention

When it happens

Trigger: Opening/creating a CCR SQLite store whose parent directory has write permission for group or other (perm bits & 0o022 nonzero) without the sticky bit — e.g. a shared /var/tmp-style directory or a data dir chmod'ed 0777 or 0775.

Common situations: Databases placed in shared temp directories lacking the sticky bit; deployment scripts chmod'ing data dirs 0777 'to fix permissions'; containers running with permissive umask; multi-user hosts where the app writes to a group-shared directory.

Understand the failure class

Background: "You do not have permission" / 403 Forbidden errors: authenticated but not allowed — causes and fixes across open-source libraries — this error's family across 31 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/535ff223efa397b2. Report an issue: GitHub.

Appendix: source

Thrown at engine/ccr/sqlite_parent_security_posix.go:15

//go:build !windows && !js

package ccr

import (
	"fmt"
	"os"
	"path/filepath"
)

func validateSQLiteParentSecurity(path string, info os.FileInfo) error {
	// Sticky shared temp directories (/tmp, macOS /private/tmp) prevent another
	// user from replacing the 0600 file. Other group/world-writable parents do not.
	if info.Mode().Perm()&0o022 != 0 && info.Mode()&os.ModeSticky == 0 {
		return fmt.Errorf("sqlite parent %q is group/world writable", path)
	}
	return nil
}

func createSQLiteFile(path string) error {
	// Close before publishing: another Store in this process may open the
	// database as soon as its name exists. Closing even a creation descriptor
	// after that point would discard SQLite's process-wide POSIX locks.
	file, err := os.CreateTemp(filepath.Dir(path), ".caveman-sqlite-*")
	if err != nil {
		return err
	}
	defer os.Remove(file.Name())
	if err := file.Close(); err != nil {
		return err
	}
	// Link is atomic and refuses to replace an existing file or symlink.
	return os.Link(file.Name(), path)

View on GitHub (pinned to 3ee70a1026)