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
- 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
- Retry store creation after the path is stable; the check is intentionally fail-closed
- 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
- One process owns the database path; serialize store creation
- Exclude the data dir from rsync/Dropbox-style tools that replace files via rename
- Keep the store on a stable local filesystem
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
- file changed while opening
- file changed while securing
- inspect sqlite parent ACL
- refusing non-regular file
- sqlite parent has no restrictive DACL
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)