Fix bd-36870264: Prevent nested .beads directories with path canonicalization
- Add filepath.Abs() + EvalSymlinks() to FindDatabasePath() to normalize all database paths - Add nested .beads directory detection in setupDaemonLock() with helpful error messages - Prevents infinite .beads/.beads/.beads/ recursion when using relative BEADS_DB paths - All acceptance criteria passed: singleton enforcement, lock release, no recursion Amp-Thread-ID: https://ampcode.com/threads/T-c7fc78b8-a935-48dc-8453-a1bd47a14f72 Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
16
beads.go
16
beads.go
@@ -125,11 +125,25 @@ func NewSQLiteStorage(dbPath string) (Storage, error) {
|
|||||||
func FindDatabasePath() string {
|
func FindDatabasePath() string {
|
||||||
// 1. Check environment variable
|
// 1. Check environment variable
|
||||||
if envDB := os.Getenv("BEADS_DB"); envDB != "" {
|
if envDB := os.Getenv("BEADS_DB"); envDB != "" {
|
||||||
return envDB
|
// Canonicalize the path to prevent nested .beads directories
|
||||||
|
if absDB, err := filepath.Abs(envDB); err == nil {
|
||||||
|
if canonical, err := filepath.EvalSymlinks(absDB); err == nil {
|
||||||
|
return canonical
|
||||||
|
}
|
||||||
|
return absDB // Return absolute path even if symlink resolution fails
|
||||||
|
}
|
||||||
|
return envDB // Fallback to original if Abs fails
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Search for .beads/*.db in current directory and ancestors
|
// 2. Search for .beads/*.db in current directory and ancestors
|
||||||
if foundDB := findDatabaseInTree(); foundDB != "" {
|
if foundDB := findDatabaseInTree(); foundDB != "" {
|
||||||
|
// Canonicalize found path
|
||||||
|
if absDB, err := filepath.Abs(foundDB); err == nil {
|
||||||
|
if canonical, err := filepath.EvalSymlinks(absDB); err == nil {
|
||||||
|
return canonical
|
||||||
|
}
|
||||||
|
return absDB
|
||||||
|
}
|
||||||
return foundDB
|
return foundDB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -423,6 +423,16 @@ func startDaemon(interval time.Duration, autoCommit, autoPush bool, logFile, pid
|
|||||||
// setupDaemonLock acquires the daemon lock and writes PID file
|
// setupDaemonLock acquires the daemon lock and writes PID file
|
||||||
func setupDaemonLock(pidFile string, dbPath string, log daemonLogger) (*DaemonLock, error) {
|
func setupDaemonLock(pidFile string, dbPath string, log daemonLogger) (*DaemonLock, error) {
|
||||||
beadsDir := filepath.Dir(pidFile)
|
beadsDir := filepath.Dir(pidFile)
|
||||||
|
|
||||||
|
// Detect nested .beads directories (e.g., .beads/.beads/.beads/)
|
||||||
|
cleanPath := filepath.Clean(beadsDir)
|
||||||
|
if strings.Contains(cleanPath, string(filepath.Separator)+".beads"+string(filepath.Separator)+".beads") {
|
||||||
|
log.log("Error: Nested .beads directory detected: %s", cleanPath)
|
||||||
|
log.log("Hint: Do not run 'bd daemon' from inside .beads/ directory")
|
||||||
|
log.log("Hint: Use absolute paths for BEADS_DB or run from workspace root")
|
||||||
|
return nil, fmt.Errorf("nested .beads directory detected")
|
||||||
|
}
|
||||||
|
|
||||||
lock, err := acquireDaemonLock(beadsDir, dbPath)
|
lock, err := acquireDaemonLock(beadsDir, dbPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == ErrDaemonLocked {
|
if err == ErrDaemonLocked {
|
||||||
|
|||||||
Reference in New Issue
Block a user