From 0b3add0841997f7242047df21ea67decfe915101 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Tue, 30 Jun 2026 10:49:36 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[MEDIUM]=20?= =?UTF-8?q?Prevent=20Infinite=20Loop=20DoS=20in=20interactive=20prompts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - added max retries to `readline()` while loops to prevent infinite loops when invalid input is fed repeatedly. --- .jules/sentinel.md | 5 +++++ R/aFIPC.R | 12 ++++++++++++ tests/testthat/test-security.R | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) 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"),