diff --git a/.jules/sentinel.md b/.jules/sentinel.md index c5b38cb..88b14f5 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -12,3 +12,8 @@ **Vulnerability:** The code uses `readline()` in `R/aFIPC.R` to prompt the user. In non-interactive environments (e.g., CI/CD, automation scripts, agents), this can block execution or leave the process consuming resources. **Learning:** `readline()` should only be used when `interactive()` is true. Interactive prompts are insecure and unstable in automated contexts because they block execution unconditionally. **Prevention:** Always wrap `readline()` logic in an `if (interactive())` check or provide sensible defaults for non-interactive execution. + +## 2024-06-30 - Prevent Infinite Loop DoS in User Prompts +**Vulnerability:** The code used `readline()` inside an unbounded `while` loop to validate user inputs. In environments where standard input is continuously piped or automated incorrectly, `readline()` repeatedly returns invalid inputs (like `""`), causing the loop to run infinitely. This leads to 100% CPU utilization and eventual service denial (DoS). +**Learning:** Even when `interactive()` safeguards are present, user-provided loops over `readline()` are dangerous without an upper bound, as inputs can still be automated or maliciously piped in interactive-like environments. +**Prevention:** Always include a `max_retries` counter (e.g., 5) and break or `stop()` when the limit is reached to prevent unbounded looping. diff --git a/R/aFIPC.R b/R/aFIPC.R index ecb5d05..a383c5d 100644 --- a/R/aFIPC.R +++ b/R/aFIPC.R @@ -89,8 +89,12 @@ autoFIPC <- ) } + max_retries <- 5 + retries <- 0 n <- readline(prompt = "Is it correct? (1: Yes 2: No) : ") while (!grepl("^[0-9]+$", n)) { + retries <- retries + 1 + if (retries >= max_retries) stop("Too many invalid inputs, preventing infinite loop.") n <- readline(prompt = "Is it correct? (1: Yes 2: No) : ") } @@ -115,11 +119,15 @@ autoFIPC <- if (itemtype == '3PL' && length(oldformBILOGprior) == 0) { checkoldformBILOGprior <- function() { if (!interactive()) return(1L) + max_retries <- 5 + retries <- 0 n <- readline( prompt = "Do you want to use default BILOG-MG priors for oldform Data? (1: Yes 2: No) : " ) while (!grepl("^[0-9]+$", n)) { + retries <- retries + 1 + if (retries >= max_retries) stop("Too many invalid inputs, preventing infinite loop.") n <- readline( prompt = "Do you want to use default BILOG-MG priors for oldform Data? (1: Yes 2: No) : " @@ -330,11 +338,15 @@ autoFIPC <- if (itemtype == '3PL' && length(newformBILOGprior) == 0) { checknewformBILOGprior <- function() { if (!interactive()) return(1L) + max_retries <- 5 + retries <- 0 n <- readline( prompt = "Do you want to use default BILOG-MG priors for newform Data? (1: Yes 2: No) : " ) while (!grepl("^[0-9]+$", n)) { + retries <- retries + 1 + if (retries >= max_retries) stop("Too many invalid inputs, preventing infinite loop.") n <- readline( prompt = "Do you want to use default BILOG-MG priors for newform Data? (1: Yes 2: No) : " diff --git a/tests/testthat/test-security.R b/tests/testthat/test-security.R index f7061aa..4cbbc6c 100644 --- a/tests/testthat/test-security.R +++ b/tests/testthat/test-security.R @@ -1,6 +1,6 @@ test_that("autoFIPC handles non-interactive sessions securely to prevent DoS (checkCorrect)", { expect_error( - autoFIPC( + aFIPC::autoFIPC( newformXData = matrix(0, nrow=10, ncol=10), oldformYData = matrix(0, nrow=10, ncol=10), newformCommonItemNames = c("Item1"),