Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@
^\.jules(/.*)?$
^\.trivyignore\.yaml$
^trivy\.yaml$
^\.semgrepignore$
^test_validation\.R$
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
**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-30 - Integer Overflow Coercion Vulnerability with as.integer()
**Vulnerability:** Interactive `readline()` prompts validated input using unbounded regex `^[0-9]+$` before coercion with `as.integer()`.
**Learning:** This introduces a potential integer overflow coercion vulnerability (DoS). Excessively large numeric strings (e.g. `9999999999999999999999`) pass the regex check but overflow the max integer limit when evaluated by `as.integer()`, resulting in `NA` and causing downstream type errors or process crashes.
**Prevention:** Strictly match against exact expected values using bounded regular expressions (e.g. `^[12]$` instead of `^[0-9]+$`) when validating input choices prior to integer coercion.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Description: Automates fixed item parameter linking for test linking under
the item response theory paradigm using mirt package estimates.
License: GPL-3 | file LICENSE
Imports: mirt, methods
Suggests: testthat (>= 3.0.0)
Suggests: testthat (>= 3.0.0), mockery
Encoding: UTF-8
Config/testthat/edition: 3
Config/roxygen2/version: 8.0.0
6 changes: 3 additions & 3 deletions R/aFIPC.R
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}
Expand Down Expand Up @@ -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))
}
}
Expand Down Expand Up @@ -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))
}
}
Expand Down
2 changes: 0 additions & 2 deletions test_dummy.R

This file was deleted.

105 changes: 105 additions & 0 deletions tests/testthat/test-sentinel-validation.R
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,108 @@ test_that("autoFIPC validates boolean flags for newformBILOGprior, oldformBILOGp
"Security Error: confirmCommonItems must be a single non-NA logical value or NULL"
)
})

test_that("interactive prompt readline validates inputs strictly", {
# Mock interactive to return TRUE and mock readline input
# We test that an invalid input (e.g. '3' or '9999999999') will cause the retry loop to fail

mock_interactive <- mockery::mock(TRUE, cycle=TRUE)
mock_readline_fail <- mockery::mock("3", "999", "a", cycle=TRUE)

mockery::stub(aFIPC::autoFIPC, "interactive", mock_interactive)
mockery::stub(aFIPC::autoFIPC, "readline", mock_readline_fail)

expect_error(
aFIPC::autoFIPC(
newformXData = data.frame(A=c(1,0,1)),
oldformYData = data.frame(A=c(1,1,0)),
newformCommonItemNames = c('A'),
oldformCommonItemNames = c('A')
),
"Too many invalid common item confirmation attempts"
)
})

test_that("interactive prompt readline validates inputs strictly for BILOG priors", {

mock_interactive <- mockery::mock(TRUE, cycle=TRUE)
mock_readline_fail <- mockery::mock("3", "999", "a", cycle=TRUE)

mockery::stub(aFIPC::autoFIPC, "interactive", mock_interactive)
mockery::stub(aFIPC::autoFIPC, "readline", mock_readline_fail)

# For oldformBILOGprior
expect_error(
aFIPC::autoFIPC(
newformXData = data.frame(A=c(1,0,1)),
oldformYData = data.frame(A=c(1,1,0)),
newformCommonItemNames = c('A'),
oldformCommonItemNames = c('A'),
confirmCommonItems = TRUE,
itemtype = '3PL'
),
"Too many invalid oldform BILOG prior attempts"
)
})

test_that("interactive prompt readline validates inputs strictly for newform BILOG priors", {

mock_interactive <- mockery::mock(TRUE, cycle=TRUE)
mock_readline_fail <- mockery::mock("3", "999", "a", cycle=TRUE)

mockery::stub(aFIPC::autoFIPC, "interactive", mock_interactive)
mockery::stub(aFIPC::autoFIPC, "readline", mock_readline_fail)

# To reach newformBILOGprior we need oldformBILOGprior to pass, so we pass it explicitly
# We also need enough data to not fail the mirt 3PL estimation
set.seed(123)
N <- 100
new_data <- data.frame(
A = rbinom(N, 1, 0.5),
B = rbinom(N, 1, 0.5),
C = rbinom(N, 1, 0.5),
D = rbinom(N, 1, 0.5),
E = rbinom(N, 1, 0.5)
)
old_data <- data.frame(
A = rbinom(N, 1, 0.5),
B = rbinom(N, 1, 0.5),
C = rbinom(N, 1, 0.5),
D = rbinom(N, 1, 0.5),
F = rbinom(N, 1, 0.5)
)
expect_error(
aFIPC::autoFIPC(
newformXData = new_data,
oldformYData = old_data,
newformCommonItemNames = c('A', 'B', 'C', 'D'),
oldformCommonItemNames = c('A', 'B', 'C', 'D'),
confirmCommonItems = TRUE,
itemtype = '3PL',
oldformBILOGprior = FALSE
),
"Too many invalid newform BILOG prior attempts"
)
Comment on lines +90 to +119

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | πŸ—οΈ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate relevant test and implementation files
git ls-files | rg 'tests/testthat/test-sentinel-validation\.R|R/|inst/' || true

# Show the test file around the cited lines
file="tests/testthat/test-sentinel-validation.R"
wc -l "$file"
cat -n "$file" | sed -n '1,220p'

# Search for autoFIPC and BILOG prior handling
rg -n "autoFIPC|oldformBILOGprior|newformBILOGprior|Too many invalid newform BILOG prior attempts|Too many invalid oldform BILOG prior attempts" .

Repository: ContextualWisdomLab/aFIPC

Length of output: 17841


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the implementation around the BILOG prior / validation branches.
impl_files=$(git ls-files | rg '^(R|src)/.*\.(R|cpp|cc|c|h|hpp)$' || true)
printf "%s\n" "$impl_files" | rg 'autoFIPC|BILOGprior|prior' || true

Repository: ContextualWisdomLab/aFIPC

Length of output: 163


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Fallback focused inspection in case the first search is too broad.
for f in tests/testthat/test-sentinel-validation.R $(git ls-files 'R/*.R' 'R/**/*.R' 'src/*' 2>/dev/null || true); do
  [ -f "$f" ] || continue
  if rg -n "autoFIPC|oldformBILOGprior|newformBILOGprior|Too many invalid newform BILOG prior attempts|Too many invalid oldform BILOG prior attempts" "$f" >/dev/null; then
    echo "### $f"
    rg -n -A4 -B4 "autoFIPC|oldformBILOGprior|newformBILOGprior|Too many invalid newform BILOG prior attempts|Too many invalid oldform BILOG prior attempts" "$f"
  fi
done

Repository: ContextualWisdomLab/aFIPC

Length of output: 10180


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Print only the exact implementation slice around the likely validation branch.
file=$(git ls-files | rg '/autoFIPC.*\.R$|^R/.*autoFIPC.*\.R$|^R/.*\.R$' | head -n 1 || true)
printf 'candidate=%s\n' "$file"
if [ -n "${file:-}" ] && [ -f "$file" ]; then
  rg -n -A3 -B3 "oldformBILOGprior|newformBILOGprior|3PL|mirt|Too many invalid" "$file"
fi

Repository: ContextualWisdomLab/aFIPC

Length of output: 13650


🌐 Web query:

aFIPC autoFIPC oldformBILOGprior newformBILOGprior 3PL mirt source code

πŸ’‘ Result:

The terms aFIPC, autoFIPC, oldformBILOGprior, and newformBILOGprior appear in the source code of the R package mirt, which is a comprehensive framework for Multidimensional Item Response Theory (IRT) [1][2]. These specific names are internal parameters or function arguments used within the mirt package to control estimation procedures, particularly concerning Fixed Item Parameter Calibration (FIPC) and the handling of priors in IRT models [3][4]. FIPC refers to the methodology of calibrating new items while holding the parameters of existing (common) items fixed, a common technique for test equating or linking [3][4]. The terms oldformBILOGprior and newformBILOGprior relate to how prior distributions are specified for item parameters, often mimicking historical conventions from legacy software like BILOG-MG to ensure consistency in Bayesian estimation or to accommodate different algorithmic implementations [3]. As an open-source project, the mirt package source code is hosted on GitHub, and the package provides extensive functionality for fitting IRT models, including 3PL models, using advanced estimation techniques such as the EM algorithm and MHRM [1][5][6]. The package is maintained by Phil Chalmers and is available on CRAN [1]. Users interested in the exact implementation details can examine the source code in the package repository [1][2].

Citations:


μƒˆ 폼 검증을 이전 폼 3PL μΆ”μ •μ—μ„œ λΆ„λ¦¬ν•˜μ„Έμš”.
oldformBILOGprior = FALSEλŠ” 이전 폼 ν”„λ‘¬ν”„νŠΈλ§Œ κ±΄λ„ˆλ›°κ³ , 이전 폼 3PL 좔정은 κ·ΈλŒ€λ‘œ μ‹€ν–‰λ©λ‹ˆλ‹€. κ·Έλž˜μ„œ 이 ν…ŒμŠ€νŠΈλŠ” μƒˆ 폼 μž…λ ₯ 였λ₯˜λ³΄λ‹€ λ¨Όμ € μΆ”μ • 성곡에 μ˜μ‘΄ν•΄ λŠλ €μ§€κ±°λ‚˜ 깨질 수 μžˆμŠ΅λ‹ˆλ‹€. μƒˆ 폼 λΆ„κΈ°λ§Œ 직접 ν…ŒμŠ€νŠΈν•˜λ„λ‘ fixture/mock으둜 λΆ„λ¦¬ν•˜μ„Έμš”.

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/testthat/test-sentinel-validation.R` around lines 90 - 119, λΆ„λ¦¬λœ μƒˆ 폼 검증
λΆ„κΈ°λ§Œ ν…ŒμŠ€νŠΈν•˜λ„λ‘ ν˜„μž¬ autoFIPC ν…ŒμŠ€νŠΈλ₯Ό μˆ˜μ •ν•˜μ„Έμš”. oldformBILOGprior = FALSEκ°€ 이전 폼 3PL 좔정을 κ±΄λ„ˆλ›°μ§€
μ•ŠμœΌλ―€λ‘œ μ‹€μ œ 좔정에 μ˜μ‘΄ν•˜λŠ” new_data와 old_data fixtureλ₯Ό μ œκ±°ν•˜κ±°λ‚˜ mock μ²˜λ¦¬ν•˜κ³ , κΈ°μ‘΄ β€œToo many
invalid newform BILOG prior attempts” 였λ₯˜ 검증은 μœ μ§€ν•˜μ„Έμš”.

})

test_that("integer overflow via interactive readline coercion is prevented", {

mock_interactive <- mockery::mock(TRUE, cycle=TRUE)
# This uses a value that would cause integer overflow if not strictly matched
mock_readline_overflow <- mockery::mock("99999999999999999999", cycle=TRUE)

mockery::stub(aFIPC::autoFIPC, "interactive", mock_interactive)
mockery::stub(aFIPC::autoFIPC, "readline", mock_readline_overflow)

# It should fail validation with "Too many invalid common item confirmation attempts"
# instead of crashing with coercion/type errors
expect_error(
aFIPC::autoFIPC(
newformXData = data.frame(A=c(1,0,1)),
oldformYData = data.frame(A=c(1,1,0)),
newformCommonItemNames = c('A'),
oldformCommonItemNames = c('A')
),
"Too many invalid common item confirmation attempts"
)
})
Loading