Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## 2024-05-15 - Command Injection Vulnerability via String Interpolation
**Vulnerability:** The `toolExists` function in `CacheCategory.swift` executes a shell command using string interpolation: `shell("/usr/bin/which \(tool)")`. If the `tool` string contains malicious shell characters (like `;`, `&`, or backticks), it allows arbitrary command execution because the wrapper function (`shell(_:)`) passes the entire string to `/bin/bash -c`.
**Learning:** Using string interpolation to inject dynamic variables into shell wrapper commands (e.g., `shell("cmd \(variable)")`) introduces severe command injection vulnerabilities. The shell interprets the interpolated content before the actual binary is executed.
**Prevention:** Pass dynamic inputs strictly as elements in the `Process().arguments` array for direct binary execution. However, in this specific case where `which` is needed, we can eliminate the shell entirely and rewrite `toolExists` to use native Swift logic or execute `/usr/bin/which` directly via `Process` with `[tool]` as the argument.
17 changes: 15 additions & 2 deletions Sources/Cacheout/Models/CacheCategory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,21 @@ struct CacheCategory: Identifiable, Hashable {
}

private func toolExists(_ tool: String) -> Bool {
let result = shell("/usr/bin/which \(tool)")
return result != nil && !result!.isEmpty
let process = Process()
let pipe = Pipe()

process.executableURL = URL(fileURLWithPath: "/usr/bin/which")
process.arguments = [tool]
process.standardOutput = pipe
process.standardError = FileHandle.nullDevice
Comment on lines +192 to +195
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve PATH when checking tool availability

toolExists now runs /usr/bin/which without setting process.environment, so it relies on the app’s inherited PATH. In macOS GUI launches (e.g., from Finder), PATH often omits Homebrew locations like /opt/homebrew/bin, which makes which brew/which npm fail even when installed. Because resolvedPaths skips .probed entries when requiresTool is missing (CacheCategory.swift around lines 149-153), this regression can hide entire cache categories and prevents fallback paths from being considered for common setups.

Useful? React with 👍 / 👎.


do {
try process.run()
process.waitUntilExit()
return process.terminationStatus == 0
} catch {
return false
}
}

private func runProbe(_ command: String) -> String? {
Expand Down