Skip to content

Commit 1cacca5

Browse files
authored
fix: prevent running air in dangerous root directories (#851)
Add safety check to refuse running in home directory, system root (/), or /root to prevent excessive file watching that could impact performance or system stability. - Add isDangerousRoot() utility function to detect dangerous paths - Integrate check in config.preprocess() with clear error message - Add comprehensive tests for all dangerous path scenarios
1 parent 0745289 commit 1cacca5

File tree

3 files changed

+106
-0
lines changed

3 files changed

+106
-0
lines changed

runner/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,12 @@ func (c *Config) preprocess(args map[string]TomlInfo) error {
368368
if err != nil {
369369
return err
370370
}
371+
372+
// Check for dangerous root directories that could cause excessive file watching
373+
if isDangerous, dirName := isDangerousRoot(c.Root); isDangerous {
374+
return fmt.Errorf("refusing to run in %s - this would watch too many files. Please run air in a project directory", dirName)
375+
}
376+
371377
if c.TmpDir == "" {
372378
c.TmpDir = "tmp"
373379
}

runner/util.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,3 +500,36 @@ func formatPath(path string) string {
500500

501501
return quotedPath
502502
}
503+
504+
// isDangerousRoot checks if the given path is a dangerous root directory
505+
// that could cause excessive file watching (home dir, root dir, etc.)
506+
// Returns true and a description if the path is dangerous.
507+
func isDangerousRoot(path string) (bool, string) {
508+
// Get absolute path
509+
absPath, err := filepath.Abs(path)
510+
if err != nil {
511+
return false, ""
512+
}
513+
absPath = filepath.Clean(absPath)
514+
515+
// Check root directory
516+
if absPath == "/" {
517+
return true, "root directory (/)"
518+
}
519+
520+
// Check home directory
521+
home, err := os.UserHomeDir()
522+
if err == nil {
523+
home = filepath.Clean(home)
524+
if absPath == home {
525+
return true, "home directory (~)"
526+
}
527+
}
528+
529+
// Check /root (root user's home, in case UserHomeDir returns something else)
530+
if absPath == "/root" {
531+
return true, "/root directory"
532+
}
533+
534+
return false, ""
535+
}

runner/util_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,3 +739,70 @@ func Test_killCmd_SendInterrupt_SlowGracefulExit(t *testing.T) {
739739
t.Logf("✅ PASS: Process exited gracefully in %v after cleanup (kill_delay was 1s, saved ~%.1fs)",
740740
elapsed, 1.0-elapsed.Seconds())
741741
}
742+
743+
func TestIsDangerousRoot(t *testing.T) {
744+
t.Parallel()
745+
746+
homeDir, err := os.UserHomeDir()
747+
require.NoError(t, err, "failed to get user home directory")
748+
749+
tests := []struct {
750+
name string
751+
path string
752+
isDangerous bool
753+
description string
754+
}{
755+
{
756+
name: "root directory",
757+
path: "/",
758+
isDangerous: true,
759+
description: "root directory (/)",
760+
},
761+
{
762+
name: "root user home",
763+
path: "/root",
764+
isDangerous: true,
765+
description: "/root directory",
766+
},
767+
{
768+
name: "user home directory",
769+
path: homeDir,
770+
isDangerous: true,
771+
description: "home directory (~)",
772+
},
773+
{
774+
name: "normal project directory",
775+
path: "/home/user/myproject",
776+
isDangerous: false,
777+
description: "",
778+
},
779+
{
780+
name: "tmp directory",
781+
path: "/tmp/test-project",
782+
isDangerous: false,
783+
description: "",
784+
},
785+
{
786+
name: "current directory in project",
787+
path: ".",
788+
isDangerous: false,
789+
description: "",
790+
},
791+
{
792+
name: "subdirectory of home",
793+
path: filepath.Join(homeDir, "projects", "myapp"),
794+
isDangerous: false,
795+
description: "",
796+
},
797+
}
798+
799+
for _, tt := range tests {
800+
t.Run(tt.name, func(t *testing.T) {
801+
isDangerous, desc := isDangerousRoot(tt.path)
802+
assert.Equal(t, tt.isDangerous, isDangerous, "isDangerous mismatch for path %s", tt.path)
803+
if tt.isDangerous {
804+
assert.Equal(t, tt.description, desc, "description mismatch for path %s", tt.path)
805+
}
806+
})
807+
}
808+
}

0 commit comments

Comments
 (0)