diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 77c2daa..891765a 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -32,3 +32,10 @@ 1. Always enforce explicit boundary checks on public-facing functions (e.g., verifying `is.data.frame`, `is.matrix`, or custom class types). 2. Fail fast and securely with explicit "Security Error" messages when the data contract is violated, before passing data to third-party statistical engines. 3. When using `try()` to swallow errors on initial setup, immediately verify the expected object (`exists("model")`) was actually created before accessing its slots or attributes. + +## 2024-07-07 - Unhandled Exception Leakage Downstream +**Vulnerability:** `try()` block 이후에 반환된 객체가 실제로 존재하는지(성공했는지) 검증하지 않고 해당 객체의 프로퍼티(`@OptimInfo$secondordertest`)에 바로 접근하는 패턴이 여러 곳에 존재했습니다. 추정이 실패하여 에러가 발생한 경우 변수가 생성되지 않거나 기존 변수가 유지되어 의도치 않은 예외나 내부 상태 노출을 발생시킵니다. +**Learning:** `try()`를 통한 예외 처리는 에러를 억제할 뿐, 결과 객체의 존재를 보장하지 않습니다. 실패한 동작의 결과를 가정하고 후속 코드를 실행하면 치명적인 예외가 발생할 수 있습니다. +**Prevention:** +1. `try()` 블록 외부에서 결과 객체를 사용할 때는 항상 해당 객체가 생성되었는지 확인해야 합니다 (`exists('model')`). +2. 객체의 프로퍼티에 안전하게 접근하려면, 객체가 존재하고 예상되는 타입인지 검증하는 로직을 결합해야 합니다 (예: `(!exists('model') || !isTRUE(model@OptimInfo$secondordertest))`). diff --git a/R/aFIPC.R b/R/aFIPC.R index 25dfcd1..ba4215b 100644 --- a/R/aFIPC.R +++ b/R/aFIPC.R @@ -219,7 +219,7 @@ autoFIPC <- if (tryFitwholeOldItems == T) { if ( - (!exists("oldFormModel", inherits = FALSE)) || (!oldFormModel@OptimInfo$secondordertest && + (!exists("oldFormModel", inherits = FALSE)) || (!isTRUE(oldFormModel@OptimInfo$secondordertest) && itemtype != 'ideal') ) { message( @@ -243,7 +243,7 @@ autoFIPC <- } if ( - (!exists("oldFormModel", inherits = FALSE)) || (!oldFormModel@OptimInfo$secondordertest && + (!exists("oldFormModel", inherits = FALSE)) || (!isTRUE(oldFormModel@OptimInfo$secondordertest) && itemtype != 'ideal') ) { message( @@ -272,7 +272,7 @@ autoFIPC <- } if ( - (!exists("oldFormModel", inherits = FALSE)) || (!oldFormModel@OptimInfo$secondordertest && + (!exists("oldFormModel", inherits = FALSE)) || (!isTRUE(oldFormModel@OptimInfo$secondordertest) && itemtype != 'ideal') ) { message( @@ -290,7 +290,7 @@ autoFIPC <- } if ( - (!exists("oldFormModel", inherits = FALSE)) || (!oldFormModel@OptimInfo$secondordertest && + (!exists("oldFormModel", inherits = FALSE)) || (!isTRUE(oldFormModel@OptimInfo$secondordertest) && itemtype != 'ideal') ) { message( @@ -309,7 +309,7 @@ autoFIPC <- } if ( - (!exists("oldFormModel", inherits = FALSE)) || (!oldFormModel@OptimInfo$secondordertest && + (!exists("oldFormModel", inherits = FALSE)) || (!isTRUE(oldFormModel@OptimInfo$secondordertest) && itemtype != 'ideal') ) { message( @@ -328,7 +328,7 @@ autoFIPC <- } if ( - (!exists("oldFormModel", inherits = FALSE)) || (!oldFormModel@OptimInfo$secondordertest && + (!exists("oldFormModel", inherits = FALSE)) || (!isTRUE(oldFormModel@OptimInfo$secondordertest) && itemtype != 'ideal') ) { message( @@ -347,7 +347,7 @@ autoFIPC <- } if ( - (!exists("oldFormModel", inherits = FALSE)) || (!oldFormModel@OptimInfo$secondordertest && + (!exists("oldFormModel", inherits = FALSE)) || (!isTRUE(oldFormModel@OptimInfo$secondordertest) && itemtype != 'ideal') ) { stop('Estimation failed. Please check test quality.') @@ -437,7 +437,7 @@ autoFIPC <- if (tryFitwholeNewItems) { if ( - (!exists("newFormModel", inherits = FALSE)) || (!newFormModel@OptimInfo$secondordertest && + (!exists("newFormModel", inherits = FALSE)) || (!isTRUE(newFormModel@OptimInfo$secondordertest) && itemtype != 'ideal') ) { message( @@ -461,7 +461,7 @@ autoFIPC <- } if ( - (!exists("newFormModel", inherits = FALSE)) || (!newFormModel@OptimInfo$secondordertest && + (!exists("newFormModel", inherits = FALSE)) || (!isTRUE(newFormModel@OptimInfo$secondordertest) && itemtype != 'ideal') ) { message( @@ -490,7 +490,7 @@ autoFIPC <- } if ( - (!exists("newFormModel", inherits = FALSE)) || (!newFormModel@OptimInfo$secondordertest && + (!exists("newFormModel", inherits = FALSE)) || (!isTRUE(newFormModel@OptimInfo$secondordertest) && itemtype != 'ideal') ) { message( @@ -508,7 +508,7 @@ autoFIPC <- } if ( - (!exists("newFormModel", inherits = FALSE)) || (!newFormModel@OptimInfo$secondordertest && + (!exists("newFormModel", inherits = FALSE)) || (!isTRUE(newFormModel@OptimInfo$secondordertest) && itemtype != 'ideal') ) { message( @@ -527,7 +527,7 @@ autoFIPC <- } if ( - (!exists("newFormModel", inherits = FALSE)) || (!newFormModel@OptimInfo$secondordertest && + (!exists("newFormModel", inherits = FALSE)) || (!isTRUE(newFormModel@OptimInfo$secondordertest) && itemtype != 'ideal') ) { message( @@ -546,7 +546,7 @@ autoFIPC <- } if ( - (!exists("newFormModel", inherits = FALSE)) || (!newFormModel@OptimInfo$secondordertest && + (!exists("newFormModel", inherits = FALSE)) || (!isTRUE(newFormModel@OptimInfo$secondordertest) && itemtype != 'ideal') ) { message( @@ -565,7 +565,7 @@ autoFIPC <- } if ( - (!exists("newFormModel", inherits = FALSE)) || (!newFormModel@OptimInfo$secondordertest && + (!exists("newFormModel", inherits = FALSE)) || (!isTRUE(newFormModel@OptimInfo$secondordertest) && itemtype != 'ideal') ) { stop('Estimation failed. Please check test quality.') @@ -647,7 +647,7 @@ autoFIPC <- message('Discovering IPD') if (itemtype == 'nominal' | tryEM == T) { if (empiricalhist == T) { - modIPD_MG <- multipleGroup( + modIPD_MG <- mirt::multipleGroup( IPDData, model = 1, group = IPDgroup, @@ -659,7 +659,7 @@ autoFIPC <- ) try( modIPD_DIF <- - DIF( + mirt::DIF( modIPD_MG, IPDParmNames, scheme = 'drop_sequential', @@ -669,7 +669,7 @@ autoFIPC <- ) ) } else { - modIPD_MG <- multipleGroup( + modIPD_MG <- mirt::multipleGroup( IPDData, model = 1, group = IPDgroup, @@ -681,7 +681,7 @@ autoFIPC <- ) try( modIPD_DIF <- - DIF( + mirt::DIF( modIPD_MG, IPDParmNames, scheme = 'drop_sequential', @@ -692,7 +692,7 @@ autoFIPC <- ) } } else { - modIPD_MG <- multipleGroup( + modIPD_MG <- mirt::multipleGroup( IPDData, model = 1, group = IPDgroup, @@ -703,7 +703,7 @@ autoFIPC <- ) try( modIPD_DIF <- - DIF( + mirt::DIF( modIPD_MG, IPDParmNames, scheme = 'drop_sequential', @@ -1009,9 +1009,9 @@ autoFIPC <- # } # calculate theta - ThetaOldform <- fscores(oldFormModel, method = 'MAP') - ThetaLinkedform <- fscores(LinkedModel, method = 'MAP') - ThetaNewform <- fscores(newFormModel, method = 'MAP') + ThetaOldform <- mirt::fscores(oldFormModel, method = 'MAP') + ThetaLinkedform <- mirt::fscores(LinkedModel, method = 'MAP') + ThetaNewform <- mirt::fscores(newFormModel, method = 'MAP') # calculate expected score ExpectedScoreOldform <-