diff --git a/.jules/sentinel.md b/.jules/sentinel.md index bc5c6e1..1a76cd5 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -12,3 +12,7 @@ **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-08-15 - Missing Argument Validation Exposing Internal Context +**Vulnerability:** The top-level functions `vuongtest()` and `icci()` accepted user inputs (e.g. vectors for boolean or choice parameters) without validation, which bypassed initial code guards and resulted in deep internal R errors like "the condition has length > 1". This exposes internal execution stack frames and implementation details to the user. +**Learning:** In R codebases, it is critical to actively validate the types and lengths of arguments passed to exported functions immediately upon entry to prevent unexpected error behaviors down the call stack. +**Prevention:** Always strictly validate argument types, lengths, and valid choice sets right at the start of any exported function. Ensure failure conditions use `stop(msg, call. = FALSE)` to fail securely. diff --git a/R/icci.R b/R/icci.R index f22278a..140485c 100644 --- a/R/icci.R +++ b/R/icci.R @@ -65,6 +65,8 @@ #' @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 numeric value 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..6010f6f 100644 --- a/R/vuongtest.R +++ b/R/vuongtest.R @@ -98,6 +98,9 @@ #' @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 logical value", call. = FALSE) + if(!is.character(adj) || length(adj) != 1 || !(adj %in% c("none", "aic", "bic"))) stop("adj must be one of '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