JuliusBrussee/caveman · error

file changed while securing

Error message

file changed while securing

What it means

On non-Linux Unix systems, engine/ccr secures the SQLite file by re-Lstat'ing the path and verifying it is still a regular file with the same inode as originally observed, then chmod'ing 0600 with AT_SYMLINK_NOFOLLOW (never opening the inode, to preserve SQLite's process-wide POSIX locks). If the path changed identity in between, it throws 'file changed while securing' instead of chmod'ing the wrong file.

Solutions

  1. Ensure nothing else writes to or replaces the database path while the store is open — one owner process per path, no in-place rewriting by sync tools
  2. Retry store creation after the path is stable; the check is intentionally fail-closed
  3. If it reproduces spuriously, look for a second process (stale daemon, test parallelism) operating on the same ccr sqlite path and eliminate it

Example fix

// before
// rsync/dropbox syncing the directory replaces db.sqlite mid-open
// after
// exclude the ccr data dir from sync tools, or relocate the store:
store, err := engine.OpenCCRStore(filepath.Join(privateDataDir, "ccr.sqlite"))
Defensive patterns

Strategy: try-catch

Try / catch

store, err := engine.OpenCCRStore(path)
if err != nil && strings.Contains(err.Error(), "file changed while securing") {
	// path was swapped: ensure single owner/no sync tools, then retry once
	store, err = engine.OpenCCRStore(path)
}

Prevention

When it happens

Trigger: Between the original file creation/Stat and the chmodSQLiteFile call on the unix path, the database path is renamed-over, symlink-swapped, deleted and recreated, or replaced by a non-regular file, so os.SameFile(info, current) fails.

Common situations: Symlink attacks against the db path; concurrent Store instances where one replaces the file; backup/sync tools (rsync, Dropbox) rewriting the file in place via rename; tests exercising TOCTOU hardening.

Related errors


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

Appendix: source

Thrown at engine/ccr/sqlite_file_security_unix.go:18

//go:build !linux && !windows && !js

package ccr

import (
	"fmt"
	"os"

	"golang.org/x/sys/unix"
)

func chmodSQLiteFile(path string, info os.FileInfo) error {
	current, err := os.Lstat(path)
	if err != nil {
		return err
	}
	if !current.Mode().IsRegular() || !os.SameFile(info, current) {
		return fmt.Errorf("file changed while securing")
	}
	// Do not open/close the inode: close would discard SQLite's process-wide
	// POSIX locks. Native nofollow chmod cannot follow a swapped symlink; the
	// caller also checks inode identity afterwards. Unsupported systems fail
	// closed rather than using a path-following chmod or an ordinary descriptor.
	return unix.Fchmodat(unix.AT_FDCWD, path, 0o600, unix.AT_SYMLINK_NOFOLLOW)
}

View on GitHub (pinned to 3ee70a1026)