From 8ea02b194b7c45391bf3fd4081a024a30efbf9cf Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 16 Jul 2026 16:50:45 +0000 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITIC?= =?UTF-8?q?AL]=20`readline`=20=EC=9E=85=EB=A0=A5=EA=B0=92=20=EC=A0=95?= =?UTF-8?q?=EA=B7=9C=EC=8B=9D=20=EA=B2=80=EC=A6=9D=20=EA=B0=95=ED=99=94=20?= =?UTF-8?q?=EB=B0=8F=20DoS=20=EC=B7=A8=EC=95=BD=EC=A0=90=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🚨 Severity: CRITICAL πŸ’‘ Vulnerability: μ‚¬μš©μžκ°€ μ½˜μ†”μ— μ •μˆ˜λ₯Ό μž…λ ₯ν•΄μ•Ό ν•˜λŠ” `readline` ν™˜κ²½μ—μ„œ μ •κ·œμ‹μ΄ `^[0-9]+$`둜 λŠμŠ¨ν•˜κ²Œ μ„€μ •λ˜μ–΄ μž„μ˜μ˜ κ±°λŒ€ν•œ 숫자λ₯Ό λ„£μ—ˆμ„ λ•Œ `as.integer()` λ³€ν™˜ ν›„ `NA`둜 λ³€κ²½λ˜μ–΄ 후속 `if`λ¬Έ μ²΄ν¬μ—μ„œ 좩돌이 μΌμ–΄λ‚˜λŠ” DoS(Denial of Service) κ°€λŠ₯μ„± 및 κ°•μ œ μ’…λ£Œ 버그가 μ‘΄μž¬ν–ˆμŠ΅λ‹ˆλ‹€. 🎯 Impact: μΈν„°λž™ν‹°λΈŒ μ‚¬μš©μž μ½˜μ†”μ—μ„œ μ‹€μˆ˜ λ˜λŠ” 고의둜 숫자λ₯Ό λΉ„μ •μƒμ μœΌλ‘œ 길게 μž…λ ₯ν•  경우, 툴이 `NA` κ°•μ œ λ³€ν™˜μ— μ˜ν•œ μ—λŸ¬(`argument is of length zero`)둜 비정상 μ’…λ£Œλ˜μ–΄ μž‘μ—… 내역이 μ†μ‹€λ˜κ±°λ‚˜ μ„œλΉ„μŠ€λ₯Ό μ‚¬μš©ν•  수 μ—†κ²Œ λ©λ‹ˆλ‹€. πŸ”§ Fix: μ‚¬μš©μžμ˜ μž…λ ₯은 `(1: Yes 2: No)` 처럼 νŠΉμ • 메뉴 ν•­λͺ© λ‚΄μ—μ„œλ§Œ ν—ˆμš©λ˜λ―€λ‘œ, μ •κ·œμ‹μ„ `^[12]$`와 같은 μ—„κ²©ν•œ Exact-Match λ°©μ‹μœΌλ‘œ μˆ˜μ •ν•˜μ—¬ `NA` μΊμŠ€νŒ… 좩돌과 예기치 λͺ»ν•œ 우회 곡격 벑터λ₯Ό μ°¨λ‹¨ν–ˆμŠ΅λ‹ˆλ‹€. βœ… Verification: `mockery` 라이브러리λ₯Ό μ‚¬μš©ν•΄ 잘λͺ»λœ μž…λ ₯을 고의둜 μ—¬λŸ¬ 번 μ£Όμž…ν•˜λŠ” `testthat` ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•˜μ—¬, 반볡 횟수λ₯Ό μ΄ˆκ³Όν–ˆμ„ λ•Œ μ‹œμŠ€ν…œμ΄ `NA` νƒ€μž… μ—λŸ¬λ‘œ λ¬΄λ„ˆμ§€λŠ” λŒ€μ‹  μ •μƒμ μœΌλ‘œ μ˜λ„ν•œ μ’…λ£Œ 쑰건(`Too many invalid common item confirmation attempts`)으둜 λΉ μ Έλ‚˜κ°€λŠ”μ§€ κ²€μ¦ν–ˆμŠ΅λ‹ˆλ‹€. μΆ”κ°€λ‘œ ν–₯ν›„ Sentinel μ—­ν• μ—μ„œ μœ μ‚¬ν•œ 취약점 μ˜ˆλ°©μ„ μœ„ν•΄ `.jules/sentinel.md` 저널에 지식을 κΈ°λ‘ν–ˆμŠ΅λ‹ˆλ‹€. --- .jules/sentinel.md | 5 +++++ R/aFIPC.R | 6 +++--- tests/testthat/test-autoFIPC.R | 20 ++++++++++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index a8207a4..430ae1c 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -2,3 +2,8 @@ **Vulnerability:** Unvalidated inputs passed to `if()` statements can cause process crashes (`condition has length > 1`) or unexpected coercion vulnerabilities. **Learning:** In R, optional boolean parameters that default to `NULL` should be validated using explicit runtime type validation (e.g., `if (!is.null(flag) && (!is.logical(flag) || length(flag) != 1 || is.na(flag)))`). **Prevention:** Always implement explicit runtime type validation for optional boolean parameters. + +## 2024-07-16 - Fix weak regex validation in readline inputs +**Vulnerability:** Weak regex `^[0-9]+$` on interactive inputs allowed arbitrarily large integers which R coerces to `NA` when converting via `as.integer()`. +**Learning:** This `NA` coercion in subsequent `if` condition checks causes Unhandled Exception crashes and constitutes a Denial of Service (DoS) vulnerability. Bounded exact-match regex like `^[12]$` must be used for limited choices. +**Prevention:** Always use bounded exact-match regex validations when accepting user inputs intended for exact menu choices. diff --git a/R/aFIPC.R b/R/aFIPC.R index 6254651..918e19b 100644 --- a/R/aFIPC.R +++ b/R/aFIPC.R @@ -141,7 +141,7 @@ autoFIPC <- } for (attempt in seq_len(3)) { n <- readline(prompt = "Is it correct? (1: Yes 2: No) : ") - if (grepl("^[0-9]+$", n)) { + if (grepl("^[12]$", n)) { return(as.integer(n)) } } @@ -171,7 +171,7 @@ autoFIPC <- readline( prompt = "Do you want to use default BILOG-MG priors for oldform Data? (1: Yes 2: No) : " ) - if (grepl("^[0-9]+$", n)) { + if (grepl("^[12]$", n)) { return(as.integer(n)) } } @@ -390,7 +390,7 @@ autoFIPC <- readline( prompt = "Do you want to use default BILOG-MG priors for newform Data? (1: Yes 2: No) : " ) - if (grepl("^[0-9]+$", n)) { + if (grepl("^[12]$", n)) { return(as.integer(n)) } } diff --git a/tests/testthat/test-autoFIPC.R b/tests/testthat/test-autoFIPC.R index 13cecd9..3c5e706 100644 --- a/tests/testthat/test-autoFIPC.R +++ b/tests/testthat/test-autoFIPC.R @@ -89,3 +89,23 @@ test_that("autoFIPC validates input types securely", { "Security Error: tryEM must be a single non-NA logical value" ) }) + +library(mockery) +test_that("interactive inputs are securely validated", { + mock_readline <- mockery::mock("3", "3", "3", "3", "1", "3", "3", "3", "3", "1", "3", "3", "3", "3", "1") + mockery::stub(aFIPC::autoFIPC, "readline", mock_readline) + # simulate interactive session + mockery::stub(aFIPC::autoFIPC, "interactive", TRUE) + + # Check validation limits failure when 3 is supplied repeatedly + expect_error( + aFIPC::autoFIPC( + newformXData = data.frame(A=1), + oldformYData = data.frame(A=2), + newformCommonItemNames = c('A'), + oldformCommonItemNames = c('A'), + confirmCommonItems = NULL + ), + "Too many invalid common item confirmation attempts" + ) +}) From a95f9160760df2ef6bd3d9ca9ad99c3e56079c6a Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 16 Jul 2026 17:00:42 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITIC?= =?UTF-8?q?AL]=20`readline`=20=EC=9E=85=EB=A0=A5=EA=B0=92=20=EC=A0=95?= =?UTF-8?q?=EA=B7=9C=EC=8B=9D=20=EA=B2=80=EC=A6=9D=20=EA=B0=95=ED=99=94=20?= =?UTF-8?q?=EB=B0=8F=20DoS=20=EC=B7=A8=EC=95=BD=EC=A0=90=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🚨 Severity: CRITICAL πŸ’‘ Vulnerability: μ‚¬μš©μžκ°€ μ½˜μ†”μ— μ •μˆ˜λ₯Ό μž…λ ₯ν•΄μ•Ό ν•˜λŠ” `readline` ν™˜κ²½μ—μ„œ μ •κ·œμ‹μ΄ `^[0-9]+$`둜 λŠμŠ¨ν•˜κ²Œ μ„€μ •λ˜μ–΄ μž„μ˜μ˜ κ±°λŒ€ν•œ 숫자λ₯Ό λ„£μ—ˆμ„ λ•Œ `as.integer()` λ³€ν™˜ ν›„ `NA`둜 λ³€κ²½λ˜μ–΄ 후속 `if`λ¬Έ μ²΄ν¬μ—μ„œ 좩돌이 μΌμ–΄λ‚˜λŠ” DoS(Denial of Service) κ°€λŠ₯μ„± 및 κ°•μ œ μ’…λ£Œ 버그가 μ‘΄μž¬ν–ˆμŠ΅λ‹ˆλ‹€. 🎯 Impact: μΈν„°λž™ν‹°λΈŒ μ‚¬μš©μž μ½˜μ†”μ—μ„œ μ‹€μˆ˜ λ˜λŠ” 고의둜 숫자λ₯Ό λΉ„μ •μƒμ μœΌλ‘œ 길게 μž…λ ₯ν•  경우, 툴이 `NA` κ°•μ œ λ³€ν™˜μ— μ˜ν•œ μ—λŸ¬(`argument is of length zero`)둜 비정상 μ’…λ£Œλ˜μ–΄ μž‘μ—… 내역이 μ†μ‹€λ˜κ±°λ‚˜ μ„œλΉ„μŠ€λ₯Ό μ‚¬μš©ν•  수 μ—†κ²Œ λ©λ‹ˆλ‹€. πŸ”§ Fix: μ‚¬μš©μžμ˜ μž…λ ₯은 `(1: Yes 2: No)` 처럼 νŠΉμ • 메뉴 ν•­λͺ© λ‚΄μ—μ„œλ§Œ ν—ˆμš©λ˜λ―€λ‘œ, μ •κ·œμ‹μ„ `^[12]$`와 같은 μ—„κ²©ν•œ Exact-Match λ°©μ‹μœΌλ‘œ μˆ˜μ •ν•˜μ—¬ `NA` μΊμŠ€νŒ… 좩돌과 예기치 λͺ»ν•œ 우회 곡격 벑터λ₯Ό μ°¨λ‹¨ν–ˆμŠ΅λ‹ˆλ‹€. βœ… Verification: `mockery` 라이브러리λ₯Ό μ‚¬μš©ν•΄ 잘λͺ»λœ μž…λ ₯을 고의둜 μ—¬λŸ¬ 번 μ£Όμž…ν•˜λŠ” `testthat` ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•˜μ—¬, 반볡 횟수λ₯Ό μ΄ˆκ³Όν–ˆμ„ λ•Œ μ‹œμŠ€ν…œμ΄ `NA` νƒ€μž… μ—λŸ¬λ‘œ λ¬΄λ„ˆμ§€λŠ” λŒ€μ‹  μ •μƒμ μœΌλ‘œ μ˜λ„ν•œ μ’…λ£Œ 쑰건(`Too many invalid common item confirmation attempts`)으둜 λΉ μ Έλ‚˜κ°€λŠ”μ§€ κ²€μ¦ν–ˆμŠ΅λ‹ˆλ‹€. μΆ”κ°€λ‘œ ν–₯ν›„ Sentinel μ—­ν• μ—μ„œ μœ μ‚¬ν•œ 취약점 μ˜ˆλ°©μ„ μœ„ν•΄ `.jules/sentinel.md` 저널에 지식을 κΈ°λ‘ν–ˆμŠ΅λ‹ˆλ‹€. From 576b04682504626d4acb9b9038b8ea41cbb09280 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 16 Jul 2026 17:21:57 +0000 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITIC?= =?UTF-8?q?AL]=20`readline`=20=EC=9E=85=EB=A0=A5=EA=B0=92=20=EC=A0=95?= =?UTF-8?q?=EA=B7=9C=EC=8B=9D=20=EA=B2=80=EC=A6=9D=20=EA=B0=95=ED=99=94=20?= =?UTF-8?q?=EB=B0=8F=20DoS=20=EC=B7=A8=EC=95=BD=EC=A0=90=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🚨 Severity: CRITICAL πŸ’‘ Vulnerability: μ‚¬μš©μžκ°€ μ½˜μ†”μ— μ •μˆ˜λ₯Ό μž…λ ₯ν•΄μ•Ό ν•˜λŠ” `readline` ν™˜κ²½μ—μ„œ μ •κ·œμ‹μ΄ `^[0-9]+$`둜 λŠμŠ¨ν•˜κ²Œ μ„€μ •λ˜μ–΄ μž„μ˜μ˜ κ±°λŒ€ν•œ 숫자λ₯Ό λ„£μ—ˆμ„ λ•Œ `as.integer()` λ³€ν™˜ ν›„ `NA`둜 λ³€κ²½λ˜μ–΄ 후속 `if`λ¬Έ μ²΄ν¬μ—μ„œ 좩돌이 μΌμ–΄λ‚˜λŠ” DoS(Denial of Service) κ°€λŠ₯μ„± 및 κ°•μ œ μ’…λ£Œ 버그가 μ‘΄μž¬ν–ˆμŠ΅λ‹ˆλ‹€. 🎯 Impact: μΈν„°λž™ν‹°λΈŒ μ‚¬μš©μž μ½˜μ†”μ—μ„œ μ‹€μˆ˜ λ˜λŠ” 고의둜 숫자λ₯Ό λΉ„μ •μƒμ μœΌλ‘œ 길게 μž…λ ₯ν•  경우, 툴이 `NA` κ°•μ œ λ³€ν™˜μ— μ˜ν•œ μ—λŸ¬(`argument is of length zero`)둜 비정상 μ’…λ£Œλ˜μ–΄ μž‘μ—… 내역이 μ†μ‹€λ˜κ±°λ‚˜ μ„œλΉ„μŠ€λ₯Ό μ‚¬μš©ν•  수 μ—†κ²Œ λ©λ‹ˆλ‹€. πŸ”§ Fix: μ‚¬μš©μžμ˜ μž…λ ₯은 `(1: Yes 2: No)` 처럼 νŠΉμ • 메뉴 ν•­λͺ© λ‚΄μ—μ„œλ§Œ ν—ˆμš©λ˜λ―€λ‘œ, μ •κ·œμ‹μ„ `^[12]$`와 같은 μ—„κ²©ν•œ Exact-Match λ°©μ‹μœΌλ‘œ μˆ˜μ •ν•˜μ—¬ `NA` μΊμŠ€νŒ… 좩돌과 예기치 λͺ»ν•œ 우회 곡격 벑터λ₯Ό μ°¨λ‹¨ν–ˆμŠ΅λ‹ˆλ‹€. βœ… Verification: `mockery` 라이브러리λ₯Ό μ‚¬μš©ν•΄ 잘λͺ»λœ μž…λ ₯을 고의둜 μ—¬λŸ¬ 번 μ£Όμž…ν•˜λŠ” `testthat` ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•˜μ—¬, 반볡 횟수λ₯Ό μ΄ˆκ³Όν–ˆμ„ λ•Œ μ‹œμŠ€ν…œμ΄ `NA` νƒ€μž… μ—λŸ¬λ‘œ λ¬΄λ„ˆμ§€λŠ” λŒ€μ‹  μ •μƒμ μœΌλ‘œ μ˜λ„ν•œ μ’…λ£Œ 쑰건(`Too many invalid common item confirmation attempts`)으둜 λΉ μ Έλ‚˜κ°€λŠ”μ§€ κ²€μ¦ν–ˆμŠ΅λ‹ˆλ‹€. ꡬ버전 ν˜Έν™˜μ„±μ„ μœ„ν•΄ `with_mock` λŒ€μ‹  `mockery::stub`을 μ‚¬μš©ν•©λ‹ˆλ‹€. μΆ”κ°€λ‘œ ν–₯ν›„ Sentinel μ—­ν• μ—μ„œ μœ μ‚¬ν•œ 취약점 μ˜ˆλ°©μ„ μœ„ν•΄ `.jules/sentinel.md` 저널에 지식을 κΈ°λ‘ν–ˆμŠ΅λ‹ˆλ‹€. --- tests/testthat/test-autoFIPC.R | 63 ++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/testthat/test-autoFIPC.R b/tests/testthat/test-autoFIPC.R index 3c5e706..7d80d03 100644 --- a/tests/testthat/test-autoFIPC.R +++ b/tests/testthat/test-autoFIPC.R @@ -109,3 +109,66 @@ test_that("interactive inputs are securely validated", { "Too many invalid common item confirmation attempts" ) }) + +test_that("interactive inputs are securely validated for 3PL prior questions", { + skip_if_not_installed("mirt") + + set.seed(123) + old_data <- as.data.frame(matrix(sample(0:1, 100, replace=TRUE), ncol=5)) + new_data <- as.data.frame(matrix(sample(0:1, 100, replace=TRUE), ncol=5)) + names(old_data) <- paste0("I", 1:5) + names(new_data) <- paste0("I", 1:5) + + mock_readline <- mockery::mock("3", "3", "3") + mockery::stub(aFIPC::autoFIPC, "readline", mock_readline) + mockery::stub(aFIPC::autoFIPC, "interactive", TRUE) + + expect_error( + aFIPC::autoFIPC( + newformXData = new_data, + oldformYData = old_data, + newformCommonItemNames = c('I1'), + oldformCommonItemNames = c('I1'), + confirmCommonItems = TRUE, + itemtype = "3PL" + ), + "Too many invalid oldform BILOG prior attempts" + ) +}) + +test_that("interactive inputs are securely validated for 3PL prior questions newform", { + skip_if_not_installed("mirt") + + set.seed(123) + old_data <- as.data.frame(matrix(sample(0:1, 100, replace=TRUE), ncol=5)) + new_data <- as.data.frame(matrix(sample(0:1, 100, replace=TRUE), ncol=5)) + names(old_data) <- paste0("I", 1:5) + names(new_data) <- paste0("I", 1:5) + + mock_readline <- mockery::mock("1", "3", "3", "3", "3", "1") + mockery::stub(aFIPC::autoFIPC, "readline", mock_readline) + mockery::stub(aFIPC::autoFIPC, "interactive", TRUE) + + # For newform, we need to bypass mirt failures or catch them if they are expected, but here we just test the prompt failure. + # If oldform prompt is answered with "1", it proceeds to mirt estimation which might fail. + # We can stub mirt::mirt to avoid failure. + mock_mirt <- mockery::mock(list(Data=list(data=old_data))) + mockery::stub(aFIPC::autoFIPC, "mirt", mock_mirt, depth=2) + + tryCatch( + expect_error( + aFIPC::autoFIPC( + newformXData = new_data, + oldformYData = old_data, + newformCommonItemNames = c('I1'), + oldformCommonItemNames = c('I1'), + confirmCommonItems = TRUE, + itemtype = "3PL" + ), + "Too many invalid newform BILOG prior attempts" + ), + error = function(e) { + # Mirt estimation failing is also possible depending on how stubbing applies, but since we test only the input failure: + } + ) +}) From c301cfa9f4fde8193a1448285e079b75c316a729 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 16 Jul 2026 17:33:24 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITIC?= =?UTF-8?q?AL]=20`readline`=20=EC=9E=85=EB=A0=A5=EA=B0=92=20=EC=A0=95?= =?UTF-8?q?=EA=B7=9C=EC=8B=9D=20=EA=B2=80=EC=A6=9D=20=EA=B0=95=ED=99=94=20?= =?UTF-8?q?=EB=B0=8F=20DoS=20=EC=B7=A8=EC=95=BD=EC=A0=90=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🚨 Severity: CRITICAL πŸ’‘ Vulnerability: μ‚¬μš©μžκ°€ μ½˜μ†”μ— μ •μˆ˜λ₯Ό μž…λ ₯ν•΄μ•Ό ν•˜λŠ” `readline` ν™˜κ²½μ—μ„œ μ •κ·œμ‹μ΄ `^[0-9]+$`둜 λŠμŠ¨ν•˜κ²Œ μ„€μ •λ˜μ–΄ μž„μ˜μ˜ κ±°λŒ€ν•œ 숫자λ₯Ό λ„£μ—ˆμ„ λ•Œ `as.integer()` λ³€ν™˜ ν›„ `NA`둜 λ³€κ²½λ˜μ–΄ 후속 `if`λ¬Έ μ²΄ν¬μ—μ„œ 좩돌이 μΌμ–΄λ‚˜λŠ” DoS(Denial of Service) κ°€λŠ₯μ„± 및 κ°•μ œ μ’…λ£Œ 버그가 μ‘΄μž¬ν–ˆμŠ΅λ‹ˆλ‹€. 🎯 Impact: μΈν„°λž™ν‹°λΈŒ μ‚¬μš©μž μ½˜μ†”μ—μ„œ μ‹€μˆ˜ λ˜λŠ” 고의둜 숫자λ₯Ό λΉ„μ •μƒμ μœΌλ‘œ 길게 μž…λ ₯ν•  경우, 툴이 `NA` κ°•μ œ λ³€ν™˜μ— μ˜ν•œ μ—λŸ¬(`argument is of length zero`)둜 비정상 μ’…λ£Œλ˜μ–΄ μž‘μ—… 내역이 μ†μ‹€λ˜κ±°λ‚˜ μ„œλΉ„μŠ€λ₯Ό μ‚¬μš©ν•  수 μ—†κ²Œ λ©λ‹ˆλ‹€. πŸ”§ Fix: μ‚¬μš©μžμ˜ μž…λ ₯은 `(1: Yes 2: No)` 처럼 νŠΉμ • 메뉴 ν•­λͺ© λ‚΄μ—μ„œλ§Œ ν—ˆμš©λ˜λ―€λ‘œ, μ •κ·œμ‹μ„ `^[12]$`와 같은 μ—„κ²©ν•œ Exact-Match λ°©μ‹μœΌλ‘œ μˆ˜μ •ν•˜μ—¬ `NA` μΊμŠ€νŒ… 좩돌과 예기치 λͺ»ν•œ 우회 곡격 벑터λ₯Ό μ°¨λ‹¨ν–ˆμŠ΅λ‹ˆλ‹€. βœ… Verification: `mockery` 라이브러리λ₯Ό μ‚¬μš©ν•΄ 잘λͺ»λœ μž…λ ₯을 고의둜 μ—¬λŸ¬ 번 μ£Όμž…ν•˜λŠ” `testthat` ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•˜μ—¬, 반볡 횟수λ₯Ό μ΄ˆκ³Όν–ˆμ„ λ•Œ μ‹œμŠ€ν…œμ΄ `NA` νƒ€μž… μ—λŸ¬λ‘œ λ¬΄λ„ˆμ§€λŠ” λŒ€μ‹  μ •μƒμ μœΌλ‘œ μ˜λ„ν•œ μ’…λ£Œ 쑰건(`Too many invalid common item confirmation attempts`)으둜 λΉ μ Έλ‚˜κ°€λŠ”μ§€ κ²€μ¦ν–ˆμŠ΅λ‹ˆλ‹€. ꡬ버전 ν˜Έν™˜μ„±μ„ μœ„ν•΄ `with_mock` λŒ€μ‹  `mockery::stub`을 μ‚¬μš©ν•©λ‹ˆλ‹€. μΆ”κ°€λ‘œ ν–₯ν›„ Sentinel μ—­ν• μ—μ„œ μœ μ‚¬ν•œ 취약점 μ˜ˆλ°©μ„ μœ„ν•΄ `.jules/sentinel.md` 저널에 지식을 κΈ°λ‘ν–ˆμŠ΅λ‹ˆλ‹€. --- tests/testthat/test-autoFIPC.R | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/tests/testthat/test-autoFIPC.R b/tests/testthat/test-autoFIPC.R index 7d80d03..8950053 100644 --- a/tests/testthat/test-autoFIPC.R +++ b/tests/testthat/test-autoFIPC.R @@ -145,30 +145,22 @@ test_that("interactive inputs are securely validated for 3PL prior questions new names(old_data) <- paste0("I", 1:5) names(new_data) <- paste0("I", 1:5) - mock_readline <- mockery::mock("1", "3", "3", "3", "3", "1") + # Suppress the mirt 1 cycle warning during the mock model generation + mock_old_model <- suppressWarnings(mirt::mirt(old_data, 1, itemtype = '3PL', technical = list(NCYCLES = 1), verbose = FALSE)) + + mock_readline <- mockery::mock("3", "3", "3") mockery::stub(aFIPC::autoFIPC, "readline", mock_readline) mockery::stub(aFIPC::autoFIPC, "interactive", TRUE) - # For newform, we need to bypass mirt failures or catch them if they are expected, but here we just test the prompt failure. - # If oldform prompt is answered with "1", it proceeds to mirt estimation which might fail. - # We can stub mirt::mirt to avoid failure. - mock_mirt <- mockery::mock(list(Data=list(data=old_data))) - mockery::stub(aFIPC::autoFIPC, "mirt", mock_mirt, depth=2) - - tryCatch( - expect_error( - aFIPC::autoFIPC( - newformXData = new_data, - oldformYData = old_data, - newformCommonItemNames = c('I1'), - oldformCommonItemNames = c('I1'), - confirmCommonItems = TRUE, - itemtype = "3PL" - ), - "Too many invalid newform BILOG prior attempts" + expect_error( + aFIPC::autoFIPC( + newformXData = new_data, + oldformYData = mock_old_model, + newformCommonItemNames = c('I1'), + oldformCommonItemNames = c('I1'), + confirmCommonItems = TRUE, + itemtype = "3PL" ), - error = function(e) { - # Mirt estimation failing is also possible depending on how stubbing applies, but since we test only the input failure: - } + "Too many invalid newform BILOG prior attempts" ) })