fix: replace system() with system2() to prevent command injection in beep notifications
R-CMD-check / R CMD check (pull_request) Successful in 3m50s

Use system2() with separate args instead of shell-interpolated system()
calls in .send_desktop_notify() and .send_webhook(). This eliminates
command injection risk when msg, status, url, or payload contain shell
metacharacters.
This commit is contained in:
2026-06-04 13:17:06 +00:00
parent fc8bf1866b
commit 366ac39fac
+24 -22
View File
@@ -84,20 +84,18 @@ beep <- function(msg = "done",
.send_desktop_notify <- function(msg, status) { .send_desktop_notify <- function(msg, status) {
# Linux: notify-send # Linux: notify-send
if (.has_command("notify-send")) { if (.has_command("notify-send")) {
cmd <- paste0("notify-send '", status, "' '", gsub("'", "'\\''", msg), "'") system2("notify-send", args = c(status, msg),
system(cmd, ignore.stdout = TRUE, ignore.stderr = TRUE) stdout = TRUE, stderr = TRUE)
return(invisible(NULL)) return(invisible(NULL))
} }
# macOS: osascript # macOS: osascript
if (.has_command("osascript")) { if (.has_command("osascript")) {
escaped_msg <- gsub('"', '\\"', msg) system2("osascript",
escaped_status <- gsub('"', '\\"', status) args = c("-e",
cmd <- paste0( paste0("display notification \"", msg,
"osascript -e 'display notification \"", escaped_msg, "\" with title \"", status, "\"")),
"\" with title \"", escaped_status, "\"'" stdout = TRUE, stderr = TRUE)
)
system(cmd, ignore.stdout = TRUE, ignore.stderr = TRUE)
return(invisible(NULL)) return(invisible(NULL))
} }
@@ -120,21 +118,25 @@ beep <- function(msg = "done",
timestamp = format(Sys.time(), "%Y-%m-%dT%H:%M:%S%z") timestamp = format(Sys.time(), "%Y-%m-%dT%H:%M:%S%z")
), auto_unbox = TRUE) ), auto_unbox = TRUE)
# Use curl via system() for maximum compatibility (no extra R deps) # Use curl via system2() for maximum compatibility (no extra R deps).
# system2() passes arguments directly to the executable without shell
# interpolation, avoiding command injection.
if (.has_command("curl")) { if (.has_command("curl")) {
cmd <- paste0( system2("curl",
"curl -s -X POST -H 'Content-Type: application/json' ", args = c("-s", "-X", "POST",
"-d '", payload, "' '", url, "' ", "-H", "Content-Type: application/json",
"--max-time ", timeout "-d", payload,
) url,
system(cmd, ignore.stdout = TRUE, ignore.stderr = TRUE) "--max-time", as.character(timeout)),
stdout = TRUE, stderr = TRUE)
} else if (.has_command("wget")) { } else if (.has_command("wget")) {
cmd <- paste0( system2("wget",
"wget -q -O /dev/null --post-data='", payload, args = c("-q", "-O", "/dev/null",
"' --header='Content-Type: application/json' ", paste0("--post-data=", payload),
"--timeout=", timeout, " '", url, "'" paste0("--header=Content-Type: application/json"),
) paste0("--timeout=", timeout),
system(cmd, ignore.stdout = TRUE, ignore.stderr = TRUE) url),
stdout = TRUE, stderr = TRUE)
} }
invisible(NULL) invisible(NULL)