From 0adaed3b93f00dd6ef91de720039c8c8adc10f69 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Tue, 25 Aug 2026 16:08:50 +0000 Subject: [PATCH 1/6] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITIC?= =?UTF-8?q?AL]=20Fix=20unbounded=20numeric=20input=20validation=20(DoS=20v?= =?UTF-8?q?ector)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🚨 Severity: CRITICAL 💡 Vulnerability: `readline` inputs using generic numeric regex `^[0-9]+$` allow massive numbers that coercion functions like `as.integer()` map to `NA`, breaking downstream binary `if (x == 1)` logic and resulting in uncaught `length > 1` exception crashes (DoS). 🎯 Impact: Attackers or malformed inputs in interactive console sessions can cause unhandled application crashes by providing excessively large integers to binary boolean confirmation prompts. 🔧 Fix: Updated the `readline` verification regex from `^[0-9]+$` to strictly `^[12]$` across `R/aFIPC.R`. This prevents oversized numbers from passing string-validation prior to coercion. Also added mocking tests to `test-sentinel-validation.R` and updated `.jules/sentinel.md` journal. ✅ Verification: Tested via local testthat execution (`run_tests.R`) targeting specific coercion boundaries using `mockery`. --- .jules/sentinel.md | 5 +++++ R/aFIPC.R | 6 +++--- tests/testthat/test-sentinel-validation.R | 26 ++++++++++++++++++++--- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index a8207a48..61ee4344 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-08-25 - Fix missing bounded validation for readline inputs +**Vulnerability:** Unbounded numeric regex validation (e.g., `^[0-9]+$`) for `readline()` allows large inputs that coerce to `NA` via `as.integer()`. This causes downstream `if (variable == 1)` conditions to fail with a `missing value where TRUE/FALSE needed` error, resulting in unhandled exception crashes. +**Learning:** When reading integer inputs for binary choices, using bounded exact-match regex like `^[12]$` is necessary to prevent coercion crashes and Denial of Service (DoS) vulnerabilities in interactive environments. +**Prevention:** Always use strict and bounded regex validation for `readline()` input instead of broad numeric matching. diff --git a/R/aFIPC.R b/R/aFIPC.R index 62546519..918e19b1 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-sentinel-validation.R b/tests/testthat/test-sentinel-validation.R index 900f0ee3..b38d275a 100644 --- a/tests/testthat/test-sentinel-validation.R +++ b/tests/testthat/test-sentinel-validation.R @@ -1,7 +1,7 @@ test_that("autoFIPC validates boolean flags for newformBILOGprior, oldformBILOGprior, and confirmCommonItems", { # newformBILOGprior expect_error( - aFIPC::autoFIPC( + autoFIPC( newformXData = data.frame(A=1), oldformYData = data.frame(A=2), newformCommonItemNames = c('A'), @@ -13,7 +13,7 @@ test_that("autoFIPC validates boolean flags for newformBILOGprior, oldformBILOGp # oldformBILOGprior expect_error( - aFIPC::autoFIPC( + autoFIPC( newformXData = data.frame(A=1), oldformYData = data.frame(A=2), newformCommonItemNames = c('A'), @@ -25,7 +25,7 @@ test_that("autoFIPC validates boolean flags for newformBILOGprior, oldformBILOGp # confirmCommonItems expect_error( - aFIPC::autoFIPC( + autoFIPC( newformXData = data.frame(A=1), oldformYData = data.frame(A=2), newformCommonItemNames = c('A'), @@ -35,3 +35,23 @@ test_that("autoFIPC validates boolean flags for newformBILOGprior, oldformBILOGp "Security Error: confirmCommonItems must be a single non-NA logical value or NULL" ) }) + +test_that("large numbers do not cause NA coercion errors in autoFIPC readline prompt", { + + # Give a huge number that passes ^[0-9]+$ but fails as.integer() + # Then give a valid '2' to continue if it rejects the first one + m <- mockery::mock("999999999999999999999999999999", "2", "2", "2") + mockery::stub(autoFIPC, 'readline', m) + mockery::stub(autoFIPC, 'interactive', TRUE) + + # Check that it rejects the large number correctly by prompting again (which gives 2) and thus stopping + expect_error({ + autoFIPC( + newformXData = data.frame(A=c(1,0,1,0), B=c(1,1,0,0)), + oldformYData = data.frame(A=c(1,0,1,0), B=c(1,1,0,0)), + newformCommonItemNames = c('A'), + oldformCommonItemNames = c('A'), + confirmCommonItems = NULL + ) + }, "Please write down pairs correctly") +}) From 3f08a911c64d9520d280cda13fe0ae9956f5220c Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Tue, 25 Aug 2026 16:43:29 +0000 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20Fix=20s?= =?UTF-8?q?ecurity-audit=20CI=20workflow=20connection=20failure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `secret-and-workflow-audit` job failed with `curl: (35) Recv failure: Connection reset by peer` while downloading the gitleaks binary from GitHub Releases. Added `--retry 5 --retry-connrefused` flags to the `curl` commands in `.github/workflows/security-audit.yml` to automatically retry on transient network errors. Also explicitly permitted GitHub endpoints in `harden-runner` policy. --- .github/workflows/security-audit.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml index b6e46e2a..ec0413c5 100644 --- a/.github/workflows/security-audit.yml +++ b/.github/workflows/security-audit.yml @@ -18,6 +18,10 @@ jobs: uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 with: egress-policy: audit + allowed-endpoints: > + github.com:443 + objects.githubusercontent.com:443 + release-assets.githubusercontent.com:443 - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd @@ -25,8 +29,8 @@ jobs: - name: Install gitleaks run: | GITLEAKS_FILE="gitleaks_8.24.2_linux_x64.tar.gz" - curl -sSLo "$GITLEAKS_FILE" "https://github.com/gitleaks/gitleaks/releases/download/v8.24.2/$GITLEAKS_FILE" - curl -sSLo gitleaks_checksums.txt "https://github.com/gitleaks/gitleaks/releases/download/v8.24.2/gitleaks_8.24.2_checksums.txt" + curl --retry 5 --retry-connrefused -sSLo "$GITLEAKS_FILE" "https://github.com/gitleaks/gitleaks/releases/download/v8.24.2/$GITLEAKS_FILE" + curl --retry 5 --retry-connrefused -sSLo gitleaks_checksums.txt "https://github.com/gitleaks/gitleaks/releases/download/v8.24.2/gitleaks_8.24.2_checksums.txt" grep "$GITLEAKS_FILE" gitleaks_checksums.txt | sha256sum -c - tar -xzf "$GITLEAKS_FILE" gitleaks chmod +x gitleaks @@ -40,8 +44,8 @@ jobs: ACTIONLINT_VERSION="1.7.10" ACTIONLINT_FILE="actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" ACTIONLINT_BASE_URL="https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}" - curl -sSLo "$ACTIONLINT_FILE" "${ACTIONLINT_BASE_URL}/${ACTIONLINT_FILE}" - curl -sSLo actionlint_checksums.txt "${ACTIONLINT_BASE_URL}/actionlint_${ACTIONLINT_VERSION}_checksums.txt" + curl --retry 5 --retry-connrefused -sSLo "$ACTIONLINT_FILE" "${ACTIONLINT_BASE_URL}/${ACTIONLINT_FILE}" + curl --retry 5 --retry-connrefused -sSLo actionlint_checksums.txt "${ACTIONLINT_BASE_URL}/actionlint_${ACTIONLINT_VERSION}_checksums.txt" grep "$ACTIONLINT_FILE" actionlint_checksums.txt | sha256sum -c - tar -xzf "$ACTIONLINT_FILE" actionlint chmod +x actionlint From c3f118a9ad89720354ccdb738b7355debba99bb8 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Tue, 25 Aug 2026 17:06:16 +0000 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20Fix=20y?= =?UTF-8?q?amllint=20line=20length=20errors=20in=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split long curl commands over multiple lines using backslashes (`\`) to comply with the 140 character line-length limit enforced by `yamllint` during the CI `quality` check. --- .github/workflows/security-audit.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml index ec0413c5..17bb5955 100644 --- a/.github/workflows/security-audit.yml +++ b/.github/workflows/security-audit.yml @@ -29,8 +29,10 @@ jobs: - name: Install gitleaks run: | GITLEAKS_FILE="gitleaks_8.24.2_linux_x64.tar.gz" - curl --retry 5 --retry-connrefused -sSLo "$GITLEAKS_FILE" "https://github.com/gitleaks/gitleaks/releases/download/v8.24.2/$GITLEAKS_FILE" - curl --retry 5 --retry-connrefused -sSLo gitleaks_checksums.txt "https://github.com/gitleaks/gitleaks/releases/download/v8.24.2/gitleaks_8.24.2_checksums.txt" + curl --retry 5 --retry-connrefused -sSLo "$GITLEAKS_FILE" \ + "https://github.com/gitleaks/gitleaks/releases/download/v8.24.2/$GITLEAKS_FILE" + curl --retry 5 --retry-connrefused -sSLo gitleaks_checksums.txt \ + "https://github.com/gitleaks/gitleaks/releases/download/v8.24.2/gitleaks_8.24.2_checksums.txt" grep "$GITLEAKS_FILE" gitleaks_checksums.txt | sha256sum -c - tar -xzf "$GITLEAKS_FILE" gitleaks chmod +x gitleaks @@ -44,8 +46,10 @@ jobs: ACTIONLINT_VERSION="1.7.10" ACTIONLINT_FILE="actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" ACTIONLINT_BASE_URL="https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}" - curl --retry 5 --retry-connrefused -sSLo "$ACTIONLINT_FILE" "${ACTIONLINT_BASE_URL}/${ACTIONLINT_FILE}" - curl --retry 5 --retry-connrefused -sSLo actionlint_checksums.txt "${ACTIONLINT_BASE_URL}/actionlint_${ACTIONLINT_VERSION}_checksums.txt" + curl --retry 5 --retry-connrefused -sSLo "$ACTIONLINT_FILE" \ + "${ACTIONLINT_BASE_URL}/${ACTIONLINT_FILE}" + curl --retry 5 --retry-connrefused -sSLo actionlint_checksums.txt \ + "${ACTIONLINT_BASE_URL}/actionlint_${ACTIONLINT_VERSION}_checksums.txt" grep "$ACTIONLINT_FILE" actionlint_checksums.txt | sha256sum -c - tar -xzf "$ACTIONLINT_FILE" actionlint chmod +x actionlint From 8a19598785a01dbc5a803c2810147b28df71e299 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Tue, 25 Aug 2026 17:24:51 +0000 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20Fix=20R?= =?UTF-8?q?=20CMD=20check=20missing=20mockery=20dependency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `check` CI workflow failed because the `mockery` package was used in `test-sentinel-validation.R` via `mockery::stub` and `mockery::mock`, but it was not declared in the `DESCRIPTION` file. Added `mockery` to the `Suggests` field. --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index f31d3e1a..c90753c5 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -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 From 1f24c2492b3578c2c9021b526e83fd98147d75b6 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Tue, 25 Aug 2026 18:30:32 +0000 Subject: [PATCH 5/6] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITIC?= =?UTF-8?q?AL]=20CI=20=EC=97=90=EB=9F=AC=20=EB=B0=8F=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=20=EA=B0=92=20=EA=B2=80=EC=A6=9D=20=EA=B0=95=ED=99=94=EB=A5=BC?= =?UTF-8?q?=20=ED=86=B5=ED=95=9C=20DoS(NA=20Coercion)=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🚨 Severity: CRITICAL 💡 Vulnerability: `readline` inputs using generic numeric regex `^[0-9]+$` allow massive numbers that coercion functions like `as.integer()` map to `NA`, breaking downstream binary `if (x == 1)` logic and resulting in uncaught `length > 1` exception crashes (DoS). 🎯 Impact: Attackers or malformed inputs in interactive console sessions can cause unhandled application crashes by providing excessively large integers to binary boolean confirmation prompts. 🔧 Fix: Updated the `readline` verification regex from `^[0-9]+$` to strictly `^[12]$` across `R/aFIPC.R`. This prevents oversized numbers from passing string-validation prior to coercion. Also added mocking tests to `test-sentinel-validation.R` and updated `.jules/sentinel.md` journal. - CI fix: Added `--retry 5 --retry-connrefused` to curl downloads in GitHub Actions to fix exit code 35 (`Connection reset by peer`). - CI fix: Allowed Strix 127.0.0.1:48080 and github asset endpoints in `harden-runner` policy. - CI fix: Split long lines in yamllint config for code-quality checks. - CI fix: Added missing `mockery` package to `Suggests` in `DESCRIPTION` to fix R CMD check. ✅ Verification: Tested via local testthat execution targeting specific coercion boundaries using `mockery`. --- .github/workflows/code-quality.yml | 11 +++++++++-- .github/workflows/r.yml | 5 +++++ .github/workflows/security-audit.yml | 1 + 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index e58bb331..027a8a64 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -18,6 +18,11 @@ jobs: uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 with: egress-policy: audit + allowed-endpoints: > + github.com:443 + objects.githubusercontent.com:443 + release-assets.githubusercontent.com:443 + 127.0.0.1:48080 - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd @@ -37,8 +42,10 @@ jobs: ACTIONLINT_VERSION="1.7.10" ACTIONLINT_FILE="actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" ACTIONLINT_BASE_URL="https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}" - curl -sSLo "$ACTIONLINT_FILE" "${ACTIONLINT_BASE_URL}/${ACTIONLINT_FILE}" - curl -sSLo actionlint_checksums.txt "${ACTIONLINT_BASE_URL}/actionlint_${ACTIONLINT_VERSION}_checksums.txt" + curl --retry 5 --retry-connrefused -sSLo "$ACTIONLINT_FILE" \ + "${ACTIONLINT_BASE_URL}/${ACTIONLINT_FILE}" + curl --retry 5 --retry-connrefused -sSLo actionlint_checksums.txt \ + "${ACTIONLINT_BASE_URL}/actionlint_${ACTIONLINT_VERSION}_checksums.txt" grep "$ACTIONLINT_FILE" actionlint_checksums.txt | sha256sum -c - tar -xzf "$ACTIONLINT_FILE" actionlint chmod +x actionlint diff --git a/.github/workflows/r.yml b/.github/workflows/r.yml index 54eef61a..d40516ee 100644 --- a/.github/workflows/r.yml +++ b/.github/workflows/r.yml @@ -25,6 +25,11 @@ jobs: uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 with: egress-policy: audit + allowed-endpoints: > + github.com:443 + objects.githubusercontent.com:443 + release-assets.githubusercontent.com:443 + 127.0.0.1:48080 - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml index 17bb5955..787b02ed 100644 --- a/.github/workflows/security-audit.yml +++ b/.github/workflows/security-audit.yml @@ -22,6 +22,7 @@ jobs: github.com:443 objects.githubusercontent.com:443 release-assets.githubusercontent.com:443 + 127.0.0.1:48080 - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd From 94964a1ea6e45d3577aca9d134b1efeb633842d5 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Tue, 25 Aug 2026 19:03:05 +0000 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITIC?= =?UTF-8?q?AL]=20CI=20=EC=97=90=EB=9F=AC=20=EB=B0=8F=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=20=EA=B0=92=20=EA=B2=80=EC=A6=9D=20=EA=B0=95=ED=99=94=EB=A5=BC?= =?UTF-8?q?=20=ED=86=B5=ED=95=9C=20DoS=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🚨 Severity: CRITICAL 💡 Vulnerability: `readline` inputs using generic numeric regex `^[0-9]+$` allow massive numbers that coercion functions like `as.integer()` map to `NA`, breaking downstream binary `if (x == 1)` logic and resulting in uncaught `length > 1` exception crashes (DoS). 🎯 Impact: Attackers or malformed inputs in interactive console sessions can cause unhandled application crashes by providing excessively large integers to binary boolean confirmation prompts. 🔧 Fix: Updated the `readline` verification regex from `^[0-9]+$` to strictly `^[12]$` across `R/aFIPC.R`. This prevents oversized numbers from passing string-validation prior to coercion. Also added mocking tests to `test-sentinel-validation.R` and updated `.jules/sentinel.md` journal. - CI fix: Added `--retry 5 --retry-connrefused` to curl downloads in GitHub Actions to fix exit code 35 (`Connection reset by peer`). - CI fix: Allowed Strix 127.0.0.1:48080 and localhost:48080 and github asset endpoints in `harden-runner` policy. - CI fix: Split long lines in yamllint config for code-quality checks. - CI fix: Added missing `mockery` package to `Suggests` in `DESCRIPTION` to fix R CMD check. ✅ Verification: Tested via local testthat execution targeting specific coercion boundaries using `mockery`. --- .github/workflows/code-quality.yml | 1 + .github/workflows/r.yml | 1 + .github/workflows/security-audit.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 027a8a64..765722f7 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -23,6 +23,7 @@ jobs: objects.githubusercontent.com:443 release-assets.githubusercontent.com:443 127.0.0.1:48080 + localhost:48080 - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd diff --git a/.github/workflows/r.yml b/.github/workflows/r.yml index d40516ee..39723443 100644 --- a/.github/workflows/r.yml +++ b/.github/workflows/r.yml @@ -30,6 +30,7 @@ jobs: objects.githubusercontent.com:443 release-assets.githubusercontent.com:443 127.0.0.1:48080 + localhost:48080 - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml index 787b02ed..dbb60546 100644 --- a/.github/workflows/security-audit.yml +++ b/.github/workflows/security-audit.yml @@ -23,6 +23,7 @@ jobs: objects.githubusercontent.com:443 release-assets.githubusercontent.com:443 127.0.0.1:48080 + localhost:48080 - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd