From b450f8b92f26ba33f4e0ff43948fd2a81fe615c3 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 27 Aug 2026 04:12:30 +0000 Subject: [PATCH] Add input validation to exported functions - Added strict type, length, and bounds checking to `vuongtest` and `icci` arguments (`nested`, `adj`, `conf.level`). - This prevents malformed inputs from bypassing top-level `stop` safeguards and triggering raw R errors deep inside internal logic, which could leak internal execution contexts. - Updated Sentinel journal with the learning. --- .jules/sentinel.md | 5 +++++ R/icci.R | 4 ++++ R/vuongtest.R | 7 +++++++ 3 files changed, 16 insertions(+) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index bc5c6e1..0d3716c 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -12,3 +12,8 @@ **Vulnerability:** Raw `stop()` and `warning()` calls without `call. = FALSE` in `llcont.R` and `vuongtest.R` exposed execution stack/call details when raised. **Learning:** While some instances of `stop()` inside `tryCatch()` were previously fixed to hide the call stack, other standalone exceptions and warnings still leaked call context. Security must be consistently applied across the entire codebase. **Prevention:** Always set `call. = FALSE` when using `stop()` or `warning()` to enforce a secure-by-default boundary and prevent internal execution paths from being disclosed to the end user. + +## 2024-07-25 - Prevent Information Disclosure via Unvalidated Arguments +**Vulnerability:** Top-level exported functions (`vuongtest`, `icci`) did not validate user arguments (like `nested`, `adj`, `conf.level`). Passing malformed inputs bypassed the secure `stop(..., call. = FALSE)` checks and caused raw R exceptions to be thrown deep inside nested calls (e.g., matrix operations or internal conditional evaluations), leaking the internal execution context/call stack. +**Learning:** Security boundaries (like `call. = FALSE`) are only effective if inputs are sanitized before they trigger internal system errors. Unvalidated inputs can act as an attack vector to force internal state disclosure. +**Prevention:** Always validate the type, length, and valid bounds of user-supplied arguments at the very beginning of exported functions to fail fast and securely with a controlled error message. diff --git a/R/icci.R b/R/icci.R index f22278a..843affd 100644 --- a/R/icci.R +++ b/R/icci.R @@ -65,6 +65,10 @@ #' @export icci <- function(object1, object2, conf.level=.95, ll1=llcont, ll2=llcont) { + if (!is.numeric(conf.level) || length(conf.level) != 1 || is.na(conf.level) || conf.level <= 0 || conf.level >= 1) { + stop("conf.level must be a single number between 0 and 1", call. = FALSE) + } + ## check objects, issue warnings/errors, get classes/calls obinfo <- check.obj(object1, object2) callA <- obinfo$callA; classA <- obinfo$classA diff --git a/R/vuongtest.R b/R/vuongtest.R index 2bdfbf6..4ea75fc 100644 --- a/R/vuongtest.R +++ b/R/vuongtest.R @@ -98,6 +98,13 @@ #' @export vuongtest <- function(object1, object2, nested=FALSE, adj="none", ll1=llcont, ll2=llcont, score1=NULL, score2=NULL, vc1=vcov, vc2=vcov) { + if (!is.logical(nested) || length(nested) != 1 || is.na(nested)) { + stop("nested must be a single TRUE or FALSE value", call. = FALSE) + } + if (!is.character(adj) || length(adj) != 1 || is.na(adj) || !(adj %in% c("none", "aic", "bic"))) { + stop("adj must be 'none', 'aic', or 'bic'", call. = FALSE) + } + ## check objects, issue warnings/errors, get classes/calls obinfo <- check.obj(object1, object2) callA <- obinfo$callA; classA <- obinfo$classA