Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
9c49752
test: add aFIPC numerical regression fixtures
seonghobae Jul 1, 2026
e9fceef
🛡️ Sentinel: [CRITICAL] 무한 루프 DoS 취약점 수정 (추정 실패 시 무한 재시도 방지)
seonghobae Jun 30, 2026
21a7480
docs: add aFIPC maintenance planning package
seonghobae Jul 1, 2026
695a681
docs: clarify aFIPC maintenance triage flow
seonghobae Jul 2, 2026
4d4e5ce
docs: define aFIPC program completion baseline
seonghobae Jul 2, 2026
5f1b0f5
test: harden model existence guards
seonghobae Jul 2, 2026
87fe653
feat: add bounded surveyFA fallback for sale-ready baseline
seonghobae Jul 2, 2026
e081f05
chore: strengthen sale-ready docs and unrecoverable surveyFA test
seonghobae Jul 2, 2026
756ee41
fix(surveyFA): stabilize bounded fallback acceptance
seonghobae Jul 2, 2026
bce7a9e
docs: add KRW 2B sale-readiness diligence pack
seonghobae Jul 2, 2026
875a098
chore: add single-command sale-readiness gate
seonghobae Jul 2, 2026
196c82b
docs: plan plugin-driven KRW 2B sale readiness
seonghobae Jul 2, 2026
50e9a3e
docs: finalize KRW 2B sale-room package
seonghobae Jul 2, 2026
bb40dca
docs: satisfy sale-room markdown lint
seonghobae Jul 2, 2026
f187bf0
docs: refresh sale-readiness PR evidence
seonghobae Jul 2, 2026
e92d536
docs: clarify queued PR evidence state
seonghobae Jul 2, 2026
c4bba57
docs: refresh sale-readiness check evidence
seonghobae Jul 2, 2026
511fcff
docs: clarify queued check evidence
seonghobae Jul 2, 2026
90e4ccf
docs: clarify sale-readiness evidence terms
seonghobae Jul 2, 2026
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 @@ -12,6 +12,8 @@
^CONTRIBUTING\.md$
^docs$
^docs/.*
^scripts$
^scripts/.*
^registered_agents\.json$
^task_agent_mapping\.json$
^\.gitleaks\.toml$
Expand Down
9 changes: 9 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@
- What changed?
- Why is this needed?

## Change Category

- [ ] Regression evidence
- [ ] Security / DoS guardrail
- [ ] Performance optimization
- [ ] CI / governance
- [ ] Documentation only
- [ ] Other:

## Validation

- [ ] `R CMD check` (or equivalent CI) succeeded
Expand Down
7 changes: 7 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,10 @@
5. DoS 완화를 위해 `return(1L)` 같은 기본 승인값을 넣을 때는 추정 기준척도, anchor/common item, true parameter 재현 계약을 우회하지 않는지 먼저 검증합니다.
6. Fail-secure 에러 메시지는 테스트의 일부로 취급합니다. 보안 테스트는 실제 구현 메시지와 맞아야 하며, 오래된 `"Interactive prompt is not available"` 같은 별도 문구를 새로 만들지 않습니다.
7. Prompt DoS 회귀 테스트는 모델 추정 실패에 기대지 말고, common-item confirmation guard처럼 취약한 입력 경계에서 바로 발생하는 fail-secure 에러를 검증합니다.

## 2024-06-30 - 추정 실패 시 무한 재시도로 인한 리소스 고갈(DoS) 취약점
**Vulnerability:** 데이터 처리 과정 중 모델 추정이 실패했을 때(예: `mirt` 패키지의 MHRM 알고리즘을 이용한 oldFormModel, newFormModel 추정), 변수가 성공적으로 생성될 때까지 `while(!exists('oldFormModel'))`과 같은 탈출 조건(exit condition)이 없는 무한 루프가 코드 내에 존재했습니다.
**Learning:** 실패 시 무한 루프는 자동화된 환경(CI/CD, 묶음 처리 서버 등)에서 작업이 절대 끝나지 않고 CPU와 메모리 등 시스템 리소스를 계속 점유하게 만들어 서비스 거부(DoS) 상태를 유발합니다. 외부 라이브러리(패키지) 호출 실패를 무한정 재시도하는 것은 매우 위험합니다.
**Prevention:**
1. 어떠한 형태의 재시도 루프든 간에 반드시 최대 재시도 횟수(`max_retries`) 제한을 두어 무한 루프에 빠지지 않도록 방어적인 코드를 작성해야 합니다.
2. 최대 재시도 횟수에 도달했을 때에는 `stop()` 함수 등을 사용해 명시적인 예외를 발생시키고 안전하게 실패하도록(fail-secure) 처리해야 합니다.
6 changes: 3 additions & 3 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,16 @@ package metadata, and CI workflow definitions in Git.

## 9. Future Considerations / Roadmap

- Add non-interactive regression fixtures for historically trusted
FIPC results.
- Maintain non-interactive regression fixtures for historically trusted
FIPC results and extend them before behavior-changing calibration work.
- Reduce interactive prompts in `autoFIPC()` for automation friendliness.
- Evaluate migration path from historical `packrat/` to a modern
lock workflow.

## 10. Project Identification

- Project Name: aFIPC
- Repository URL: `https://github.com/seonghobae/aFIPC`
- Repository URL: `https://github.com/ContextualWisdomLab/aFIPC`
- Primary Contact: Seongho Bae
- Date of Last Update: 2026-02-15

Expand Down
2 changes: 1 addition & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
export(autoFIPC)
export(surveyFA)
import(mirt)
importFrom(stats,factanal)
importFrom(stats,na.omit)
87 changes: 61 additions & 26 deletions R/aFIPC.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
.fit_mhrm_with_retries <- function(model_name, max_retries, fit) {
for (attempt in seq_len(max_retries)) {
result <- try(fit(), silent = TRUE)
if (!inherits(result, "try-error")) {
return(result)
}
}

stop(
"Estimation failed for ",
model_name,
" after ",
max_retries,
" MHRM retries. Please check test quality.",
call. = FALSE
)
}

#' automated fixed item parameter linking
#'
#' @import mirt
#' @importFrom stats na.omit

#' @param newformXData new form data X
#' @param oldformYData old form (base form) data Y
Expand Down Expand Up @@ -184,8 +203,9 @@ autoFIPC <-

if (tryFitwholeOldItems == T) {
if (
exists("oldFormModel", inherits = FALSE) &&
!oldFormModel@OptimInfo$secondordertest &&
!itemtype == 'ideal'
itemtype != 'ideal'
Comment thread
seonghobae marked this conversation as resolved.
) {
message(
'Estimation failed. estimating new parameters with no prior distribution using quasi-Monte Carlo EM estimation. please be patient.'
Expand All @@ -208,17 +228,20 @@ autoFIPC <-
}

if (
exists("oldFormModel", inherits = FALSE) &&
!oldFormModel@OptimInfo$secondordertest &&
!itemtype == 'ideal'
itemtype != 'ideal'
) {
message(
'Estimation failed. estimating new parameters with no prior distribution using Cai\'s (2010) Metropolis-Hastings Robbins-Monro (MHRM) algorithm. please be patient.'
)

try(rm(oldFormModel))
while (!exists('oldFormModel')) {
try(
oldFormModel <-
max_retries <- 3L
oldFormModel <-
.fit_mhrm_with_retries(
"oldFormModel",
max_retries,
function() {
mirt::mirt(
data = oldformYDataK,
1,
Expand All @@ -229,14 +252,15 @@ autoFIPC <-
technical = list(NCYCLES = 1e+5, MHRM_SE_draws = 200000),
GenRandomPars = F
)
}
)
}
}
}

if (
exists("oldFormModel", inherits = FALSE) &&
!oldFormModel@OptimInfo$secondordertest &&
!itemtype == 'ideal'
itemtype != 'ideal'
) {
message(
'Estimation failed. trying to remove weird items by itemfit statistics'
Expand All @@ -253,8 +277,9 @@ autoFIPC <-
}

if (
exists("oldFormModel", inherits = FALSE) &&
!oldFormModel@OptimInfo$secondordertest &&
!itemtype == 'ideal'
itemtype != 'ideal'
) {
message(
'Estimation failed. trying to remove weird items by itemfit statistics by normal MMLE/EM'
Expand All @@ -272,8 +297,9 @@ autoFIPC <-
}

if (
exists("oldFormModel", inherits = FALSE) &&
!oldFormModel@OptimInfo$secondordertest &&
!itemtype == 'ideal'
itemtype != 'ideal'
) {
message(
'Estimation failed. trying to remove weird items by itemfit statistics by MMLE/QMCEM'
Expand All @@ -291,8 +317,9 @@ autoFIPC <-
}

if (
exists("oldFormModel", inherits = FALSE) &&
!oldFormModel@OptimInfo$secondordertest &&
!itemtype == 'ideal'
itemtype != 'ideal'
) {
message(
'Estimation failed. trying to remove weird items by itemfit statistics by MMLE/MHRM'
Expand All @@ -310,8 +337,8 @@ autoFIPC <-
}

if (
!oldFormModel@OptimInfo$secondordertest &&
!itemtype == 'ideal'
(!exists("oldFormModel", inherits = FALSE) || !oldFormModel@OptimInfo$secondordertest) &&
itemtype != 'ideal'
) {
stop('Estimation failed. Please check test quality.')
Comment thread
seonghobae marked this conversation as resolved.
}
Expand Down Expand Up @@ -396,8 +423,9 @@ autoFIPC <-

if (tryFitwholeNewItems) {
if (
exists("newFormModel", inherits = FALSE) &&
!newFormModel@OptimInfo$secondordertest &&
!itemtype == 'ideal'
itemtype != 'ideal'
Comment thread
seonghobae marked this conversation as resolved.
) {
message(
'Estimation failed. estimating new parameters with no prior distribution using quasi-Monte Carlo EM estimation. please be patient.'
Expand All @@ -420,17 +448,20 @@ autoFIPC <-
}

if (
exists("newFormModel", inherits = FALSE) &&
!newFormModel@OptimInfo$secondordertest &&
!itemtype == 'ideal'
itemtype != 'ideal'
) {
message(
'Estimation failed. estimating new parameters with no prior distribution using Cai\'s (2010) Metropolis-Hastings Robbins-Monro (MHRM) algorithm. please be patient.'
)

try(rm(newFormModel))
while (!exists('newFormModel')) {
try(
newFormModel <-
max_retries <- 3L
newFormModel <-
.fit_mhrm_with_retries(
"newFormModel",
max_retries,
function() {
mirt::mirt(
data = newformXDataK,
1,
Expand All @@ -441,14 +472,15 @@ autoFIPC <-
technical = list(NCYCLES = 1e+5, MHRM_SE_draws = 200000),
GenRandomPars = F
)
}
)
}
}
}

if (
exists("newFormModel", inherits = FALSE) &&
!newFormModel@OptimInfo$secondordertest &&
!itemtype == 'ideal'
itemtype != 'ideal'
) {
message(
'Estimation failed. trying to remove weird items by itemfit statistics'
Expand All @@ -465,8 +497,9 @@ autoFIPC <-
}

if (
exists("newFormModel", inherits = FALSE) &&
!newFormModel@OptimInfo$secondordertest &&
!itemtype == 'ideal'
itemtype != 'ideal'
) {
message(
'Estimation failed. trying to remove weird items by itemfit statistics again by normal MMLE/EM'
Expand All @@ -484,8 +517,9 @@ autoFIPC <-
}

if (
exists("newFormModel", inherits = FALSE) &&
!newFormModel@OptimInfo$secondordertest &&
!itemtype == 'ideal'
itemtype != 'ideal'
) {
message(
'Estimation failed. trying to remove weird items by itemfit statistics again by MMLE/QMCEM'
Expand All @@ -503,8 +537,9 @@ autoFIPC <-
}

if (
exists("newFormModel", inherits = FALSE) &&
!newFormModel@OptimInfo$secondordertest &&
!itemtype == 'ideal'
itemtype != 'ideal'
) {
message(
'Estimation failed. trying to remove weird items by itemfit statistics again by MMLE/MHRM'
Expand All @@ -522,8 +557,8 @@ autoFIPC <-
}

if (
!newFormModel@OptimInfo$secondordertest &&
!itemtype == 'ideal'
(!exists("newFormModel", inherits = FALSE) || !newFormModel@OptimInfo$secondordertest) &&
itemtype != 'ideal'
) {
stop('Estimation failed. Please check test quality.')
Comment thread
seonghobae marked this conversation as resolved.
}
Expand Down
Loading
Loading