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:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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, ""
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user