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
This commit is contained in:
xiantang
2026-01-11 14:53:32 +08:00
committed by GitHub
parent 0745289867
commit 1cacca5a8c
3 changed files with 106 additions and 0 deletions
+6
View File
@@ -368,6 +368,12 @@ func (c *Config) preprocess(args map[string]TomlInfo) error {
if err != nil {
return err
}
// Check for dangerous root directories that could cause excessive file watching
if isDangerous, dirName := isDangerousRoot(c.Root); isDangerous {
return fmt.Errorf("refusing to run in %s - this would watch too many files. Please run air in a project directory", dirName)
}
if c.TmpDir == "" {
c.TmpDir = "tmp"
}
+33
View File
@@ -500,3 +500,36 @@ func formatPath(path string) string {
return quotedPath
}
// isDangerousRoot checks if the given path is a dangerous root directory
// that could cause excessive file watching (home dir, root dir, etc.)
// Returns true and a description if the path is dangerous.
func isDangerousRoot(path string) (bool, string) {
// Get absolute path
absPath, err := filepath.Abs(path)
if err != nil {
return false, ""
}
absPath = filepath.Clean(absPath)
// Check root directory
if absPath == "/" {
return true, "root directory (/)"
}
// Check home directory
home, err := os.UserHomeDir()
if err == nil {
home = filepath.Clean(home)
if absPath == home {
return true, "home directory (~)"
}
}
// Check /root (root user's home, in case UserHomeDir returns something else)
if absPath == "/root" {
return true, "/root directory"
}
return false, ""
}
+67
View File
@@ -739,3 +739,70 @@ func Test_killCmd_SendInterrupt_SlowGracefulExit(t *testing.T) {
t.Logf("✅ PASS: Process exited gracefully in %v after cleanup (kill_delay was 1s, saved ~%.1fs)",
elapsed, 1.0-elapsed.Seconds())
}
func TestIsDangerousRoot(t *testing.T) {
t.Parallel()
homeDir, err := os.UserHomeDir()
require.NoError(t, err, "failed to get user home directory")
tests := []struct {
name string
path string
isDangerous bool
description string
}{
{
name: "root directory",
path: "/",
isDangerous: true,
description: "root directory (/)",
},
{
name: "root user home",
path: "/root",
isDangerous: true,
description: "/root directory",
},
{
name: "user home directory",
path: homeDir,
isDangerous: true,
description: "home directory (~)",
},
{
name: "normal project directory",
path: "/home/user/myproject",
isDangerous: false,
description: "",
},
{
name: "tmp directory",
path: "/tmp/test-project",
isDangerous: false,
description: "",
},
{
name: "current directory in project",
path: ".",
isDangerous: false,
description: "",
},
{
name: "subdirectory of home",
path: filepath.Join(homeDir, "projects", "myapp"),
isDangerous: false,
description: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
isDangerous, desc := isDangerousRoot(tt.path)
assert.Equal(t, tt.isDangerous, isDangerous, "isDangerous mismatch for path %s", tt.path)
if tt.isDangerous {
assert.Equal(t, tt.description, desc, "description mismatch for path %s", tt.path)
}
})
}
}