diff --git a/cmd/analyze/delete.go b/cmd/analyze/delete.go
index 3b5ff1e..2855a28 100644
--- a/cmd/analyze/delete.go
+++ b/cmd/analyze/delete.go
@@ -8,6 +8,7 @@ import (
"os"
"os/exec"
"path/filepath"
+ "slices"
"sort"
"strings"
"sync/atomic"
@@ -165,10 +166,8 @@ func validatePath(path string) error {
return fmt.Errorf("path contains null bytes")
}
// Check for path traversal attempts (.. components).
- for _, component := range strings.Split(path, string(filepath.Separator)) {
- if component == ".." {
- return fmt.Errorf("path contains traversal components: %s", path)
- }
+ if slices.Contains(strings.Split(path, string(filepath.Separator)), "..") {
+ return fmt.Errorf("path contains traversal components: %s", path)
}
return nil
}
diff --git a/cmd/status/metrics_disk.go b/cmd/status/metrics_disk.go
index 9f2fc48..d4cf39f 100644
--- a/cmd/status/metrics_disk.go
+++ b/cmd/status/metrics_disk.go
@@ -275,23 +275,22 @@ func getAPFSContainerFreeBytes(mountpoint string) (uint64, error) {
}
const key = "APFSContainerFree"
- idx := strings.Index(out, key)
- if idx == -1 {
+ _, rest, found := strings.Cut(out, key)
+ if !found {
return 0, fmt.Errorf("APFSContainerFree not found")
}
- rest := out[idx+len(key):]
- start := strings.Index(rest, "")
- if start == -1 {
+ _, rest, found = strings.Cut(rest, "")
+ if !found {
return 0, fmt.Errorf("APFSContainerFree value not found")
}
- rest = rest[start+len(""):]
- end := strings.Index(rest, "")
- if end == -1 {
+
+ value, _, found := strings.Cut(rest, "")
+ if !found {
return 0, fmt.Errorf("APFSContainerFree end tag not found")
}
- val, err := strconv.ParseUint(strings.TrimSpace(rest[:end]), 10, 64)
+ val, err := strconv.ParseUint(strings.TrimSpace(value), 10, 64)
if err != nil {
return 0, fmt.Errorf("failed to parse APFSContainerFree: %v", err)
}
diff --git a/lib/core/timeout.sh b/lib/core/timeout.sh
index edd7051..06bde93 100644
--- a/lib/core/timeout.sh
+++ b/lib/core/timeout.sh
@@ -55,6 +55,11 @@ if [[ -z "${MO_TIMEOUT_INITIALIZED:-}" ]]; then
echo "[TIMEOUT] Install coreutils for better reliability: brew install coreutils" >&2
fi
+ # Export so child processes inherit detected values and skip re-detection.
+ # Without this, children that inherit MO_TIMEOUT_INITIALIZED=1 skip the init
+ # block but have empty bin vars, forcing the slow shell fallback.
+ export MO_TIMEOUT_BIN
+ export MO_TIMEOUT_PERL_BIN
export MO_TIMEOUT_INITIALIZED=1
fi
@@ -181,7 +186,10 @@ run_with_timeout() {
"$@" &
local cmd_pid=$!
- # Start timeout killer in background
+ # Start timeout killer in background.
+ # Redirect all FDs to /dev/null so orphaned child processes (e.g. sleep $duration)
+ # do not inherit open file descriptors from the caller and block output pipes
+ # (notably bats output capture pipes that wait for all writers to close).
(
# Wait for timeout duration
sleep "$duration"
@@ -200,7 +208,7 @@ run_with_timeout() {
kill -KILL -"$cmd_pid" 2> /dev/null || kill -KILL "$cmd_pid" 2> /dev/null || true
fi
fi
- ) &
+ ) < /dev/null > /dev/null 2>&1 &
local killer_pid=$!
# Wait for command to complete