Commit Graph
10 Commits
Author SHA1 Message Date
HeuJung 0c4f52fc96 fix(core): 업데이트 트리 argv 판정을 명령줄 SAPI 로 한정하고 매니페스트 삭제 실패를 로그에 남김
7.0.11 인스톨러·코어 업데이트 변경(e60a82d73)에 대해 과거 회귀 22건을 부류별로 대조한
결과, 신규 노출면 1건과 테스트 위생 1건이 나와 인터뷰 결정대로 조치했다.

1. argv 채널의 SAPI 게이트 — CGI/FPM 은 register_argc_argv=On 이면 $_SERVER['argv'] 를
 쿼리스트링을 '+' 로 쪼개 채우므로(`GET /?x+core:update` → argv[1]==='core:update', php-cgi
 실측) 비인증 웹 요청이 업데이트 트리로 판정되어 bootstrap/app.php 자가 치유가 요청마다
 패키지 매니페스트를 지우고 다시 만들었다. CoreUpdateContext 와 bootstrap/app.php 복제본
 모두 argv 를 cli·phpdbg 에서만 읽는다. env 플래그 채널은 웹에서 주입할 수 없으므로 그대로
 두어 웹 요청 안에서 시작하는 업데이트 흐름(7.1.0)에 영향이 없다. 동형성 테스트에 SAPI 축을
 더했다.

2. 매니페스트 삭제 실패 기록 — PackageManifestCacheHelper::clear 가 지우지 못한 파일의
 경로를 돌려주고, spawn 직전 호출부가 업그레이드 로그·콘솔에 경고로 남긴다. 권한·소유권
 불일치면 자식의 자가 치유도 같은 이유로 실패해 증상은 제보와 같은 「Class not found」 인데,
 이 경고가 원인이 권한이라는 유일한 흔적이다.

3. 테스트 격리 — tests/bootstrap.php 가 APP_PACKAGES_CACHE/APP_SERVICES_CACHE 를 테스트
 전용 경로로 돌린다. proc_open 으로 자식을 띄우는 기존 테스트 2종의 자식이 개발 클론의
 실제 bootstrap/cache 매니페스트를 지우고 다시 쓰던 것(stat 실측)을 부모·자식 함께 막는다.

관리자 [시스템 최적화] 경로(withPreservedContainer 파사드 복원의 미실측 형제 호출처)는
임시 설치본에서 API 로 실측했다 — 200, 설정·라우트 캐시 재생성, 후속 요청 200, 로그 오류 0.

4. 트러블슈팅 사례 ↔ 회귀 테스트 앵커 계약 — 신규 사례는 헤딩에 <!-- case:{영역}-{번호} -->
 앵커를 달고 같은 문자열을 그 사례를 잠그는 회귀 테스트에도 남겨야 한다. 사례 번호는
 문서마다 1부터 재시작하고 병합으로 중복되므로(이번 리베이스에서도 우리 사례가 develop 과
 같은 29 였다가 31 로 밀렸다), 개수만 대조하면 다른 사례를 덮는 테스트도 초록이 된다.

 그런데 판정기 check-troubleshooting-test-coverage.cjs 를 부르는 지점이 저장소에 하나도
 없었다 — 스크립트 자체 주석에만 실행법이 적혀 있어 아무도 부르지 않으면 영원히 돌지
 않았고, 그 사이 위반이 9건 쌓였다(backend 26~31, cache 17~19). 돌지 않는 대조는 아무것도
 잠그지 못하므로 위반 해소와 실행 지점 부여를 함께 한다.

 9건 전부에 앵커를 부착하고(각 사례가 선언한 회귀 테스트 중 가장 구체적인 파일에 배치,
 한 파일이 두 사례에 선언된 경우는 갈라 배치), stop-guard 7.2 에 앵커 계약 + 미커버
 baseline ratchet 두 축으로 등록했다. 트러블슈팅 사례 추가 프로토콜에 6단계를
 더하고 coverage 에 troubleshooting-case-anchor-contract(manual-only, 전용 판정기 위임)를
 등재했다. 판정기 종료코드 1 → 0, 미커버 건수는 전 문서 baseline 그대로다.
2026-09-08 10:42:19 +09:00
HeuJung ffab451b4a fix(core,installer): dev vendor 설치본의 코어 업데이트 중단 수정 — stale 패키지 매니페스트 3계층
Laravel PackageManifest 는 bootstrap/cache/packages.php 가 있으면 stale 여부를 검사하지
않고 그대로 읽어 등재된 provider 를 new 한다. 코어 업데이트는 Step 6/8 에서 vendor 를
--no-dev 로 교체하지만 그 파일은 Step 11 까지 이전 설치본의 것이 남으므로, 옵션 없는
`composer install` 로 깔린 사이트에서는 Step 10 spawn 자식이 새 vendor 에 없는 provider 를
찾다 부팅 단계에서 죽는다. 부팅 전이라 앱 로그에 흔적이 없고 부모에게는 자식의 비정상
종료로만 보여, 운영자에게는 「Class ... not found」 와 수동 재개 안내만 남는다.
(sir.kr 커뮤니티 제보, 7.0.9 → 7.0.10)

3계층으로 막는다.

 1. 부모 — spawn 직전 PackageManifestCacheHelper::clear
 2. 자식 — bootstrap/app.php 가 G7_UPDATE_IN_PROGRESS 를 보고 스스로 정리한다.
 이미 배포된 7.0.9·7.0.10 부모는 고칠 수 없으므로 그 아래에서 도는 신버전
 자식의 유일한 방어다. App\ 클래스를 참조하지 않고 실패는 무시한다.
 3. 범위 — CoreVersionChecker 의 env APP_VERSION 우선을 CoreUpdateContext 트리 안으로
 축소한다. 업데이트 전에 뜬 artisan serve·큐 워커가 옛 값을 물고 확장을
 incompatible_core 로 끄던 경로를 닫는다(관리자 템플릿이 대상이면 복구 UI
 자체에 도달할 수 없다).

업데이트 트리 판정은 App\Support\CoreUpdateContext 가 단독 소유하고
CoreServiceProvider::isCoreUpdateInProgress 는 위임으로 남는다. bootstrap/app.php 의
복제본은 부팅 전이라 불가피한 예외이며, 두 조건의 동형성을 테스트가 단언한다.

실측 중 드러난 결함 2건을 함께 고쳤다.

 - ConfigCacheHelper::withPreservedContainer 가 파사드 애플리케이션을 되돌리지 않아
 Step 11 이 `Target class [command.tinker] does not exist` 로 실패·롤백했다.
 - updateVersionInEnv 가 프로세스 환경을 갱신하지 않아 config 캐시에 이전 버전이 구워졌다
 (Laravel env 저장소가 불변이라 재부팅으로도 덮이지 않는다).

인스톨러는 재사용 vendor 의 개발용 패키지를 installed.json 으로 감지해 설치 환경 확인
카드·설치 로그로 알리되 설치를 차단하지 않고( 결정 D1), 재사용 경로에서도 컴파일 캐시를
정리한다. 실행되는 명령만이 아니라 실패 시 안내하는 수동 명령까지 --no-dev 로 맞췄다.
코어 업데이트 완료·핸드오프·단독 재개 사후 단계에서 queue:restart 신호를 보낸다(D3).

코어 7.0.10 → 7.0.11.
2026-09-08 09:46:57 +09:00
HeuJung 5d14d01d50 fix(core): 디버그 라우트 그룹 게이트 일원화 + 확장 자산 CSS 상대 참조 치환
https://github.com/gnuboard/g7/issues/128 (제보자 glitter-gim) 과 그 부수의무
전수조사에서 확정한 결함군을 닫는다.

## 디버그 라우트 게이트 (F1~F5)

게이트가 핸들러 안에 흩어져 있어 8개 라우트 중 3개에만 붙어 있었다. 빠진 쪽에
`File::cleanDirectory(storage/debug-dump)` 를 수행하는 `DELETE clear` 가 있었고,
production·APP_DEBUG=false 에서도 미인증 200 으로 덤프 전체가 지워졌다. GET 4종은
User SPA catch-all 이 등록 순서상 앞서 가려 주고 있었을 뿐이라, 보호를 우연에 맡긴
구조였다.

게이트를 `bootstrap/app.php` 의 devtools 래퍼 한 곳(`debug.gate` 그룹 미들웨어)으로
올린다. 새 라우트는 자동으로 덮이고, 부착 지점이 하나라 훼손이 곧바로 드러난다.
`withRouting(channels:)` 인자가 함께 등록하던 게이트 없는 `/broadcasting/auth` 는
제거하고, 채널 정의 로드만 `BroadcastServiceProvider` 로 옮겨 웹소켓 킬스위치
우회로를 없앤다. catch-all 제외 패턴에는 예약 프리픽스를 전수 추가했다.

## 확장 자산 CSS 상대 참조 (범위 추가 — 결정)

`asset_url_mode=extensionless` 에서 자산 URL 이 쿼리 형태가 되면 브라우저의 기준
디렉토리가 `/api/{타입}/assets/` 로 잡혀, CSS 안의 `url('./woff2/f.woff2')` 가
존재하지 않는 주소가 된다. 사용자 템플릿 웹폰트 1건과 관리자 템플릿 국기 아이콘
약 500건이 이 상태였다. 병합 CSS 번들은 더 나빴다 — 상대 참조를 가진 확장을 번들에서
통째로 제외하고 있었는데, 번들 URL 이 내려오면 프론트는 개별 로딩을 아예 타지 않으므로
제외는 곧 그 확장 스타일이 하나도 적용되지 않음을 뜻했다.

둘 다 서빙 시점에 절대 자산 URL 로 치환한다. 최종 URL 은 런타임 모드와 캐시 버전이
정하므로 빌드 시점에는 알 수 없고, 동봉 자산은 제3자 산출물이라 원본을 손대면 상류
갱신마다 재작업이 된다. 개별 서빙 3경로와 병합 번들이 같은 해석 규칙을 공유한다 —
갈라지면 한쪽만 고쳐진 채 남는다.

이 결함군은 공통적으로 예외도 서버 로그 흔적도 남기지 않는다. 열린 엔드포인트가
정상 응답하는 것, 404 로 글꼴이 기본 서체가 되는 것이 유일한 증상이다.

## 회귀 잠금

되돌림 red 를 모집단 전원에 대해 실측했다 — 그룹 미들웨어 제거, 개별 게이트 재주입,
`channels:` 복원, catch-all 예외 제거, 컨트롤러 3원의 `fileResponse` 회귀, 번들
치환 제거가 각각 어느 테스트를 red 로 만드는지 확인하고 원상 복구했다.

게이트 부착 축은 행위 테스트로 대체할 수 없다 — 가려진 라우트는 행위상 "막힌 것" 과
구분되지 않으므로, `gatherMiddleware` 를 직접 보는 등록 계약 테스트를 둔다. 치환
배선도 같은 이유로 라우트 이름 규약에서 모집단을 도출하는 계약 검사를 둔다(손으로 적은
목록은 네 번째 경로가 생기는 순간 조용히 낡는다).
2026-09-03 07:42:28 +09:00
HeuJung 71abdeaf60 fix(security): KVE-2026-1914/1915/1919 remediation 전건
위임 관리자(부관리자)가 권한·역할·표현식·비밀 콘텐츠 경계를 우회하던
결함군을 계층 대칭성 원칙으로 전건 차단한다. 약한 경로가 정상 응답을
내보내는 것이 유일한 증상이라, 게이트를 생산 지점 한 곳(SSoT)에 두고
같은 데이터를 내보내는 소비 경로 전부가 그 게이트를 경유하도록 맞췄다.

- 등급 상한(rank ceiling): 슈퍼관리자 보호·역할/사용자 역할 배정(추가·제거
 대칭)·일괄 상태변경·순서변경을 상세 경로와 동일 강도로 재적용. 가드는
 DB 쓰기에 선행하여 거부 시 상태 불변.
- 레이아웃 표현식: new Function/with 실행을 AST 화이트리스트 평가기로 교체.
 비-문자열 computed 키 정규화(normalizeKey)·Object facade(리플렉션 static
 제거)·legacy 접근자 차단. 저장측 검증·정적 검사와 3계층 동형.
- secret 게이트: 비밀글의 댓글·첨부·문의 독립 경로 재적용, hash 파일서빙
 소유권·비밀·발행 상태 검사 통일.
- 신뢰 스크립트 호스트: 확장 선언 기반 + same-origin 브라우저 정규화를
 런타임·저장측·정적검사 3층 동형화.
- 회귀 감지: 단위·Feature·E2E·시나리오 매니페스트 전축 + audit 룰 4종 신설.
2026-08-17 01:45:09 +09:00
HeuJung 062a88ca04 feat(core): DB 최고권한 계정 차단 + OPcache 상태 안내
설치 단계와 코어 부팅의 정책이 엇갈려 있었다. 인스톨러는 root 를 자유롭게
허용하면서, 설치 후 부팅은 같은 값을 보고 확장 로딩 전체를 조용히 스킵했다.
설치는 성공하지만 이후 깨진 화면만 뜨고 원인은 로그 한 줄에만 남았다.

판정을 프레임워크 의존성 0 인 공용 SSoT 로 모아 인스톨러와 코어가 같은 답을
내게 하고, 설치 단계에서 서버 2경로로 차단하며, 설치 후 변경은 전용 에러
페이지로 드러낸다. 가드를 boot 가 아닌 미들웨어에 두어 artisan 이 죽지 않게
해 복구 수단을 남겼다. admin 은 일반 권한 운영 계정 오차단을 막기 위해 차단
목록에서 제외했다.

OPcache 는 상태 조회 경로 자체가 없어 성능 저하를 인지할 방법이 없었다.
설치 단계 경고(비차단)와 관리자 정보 탭 표시를 같은 판정 SSoT 로 추가했다.
확장 로드 여부만으로는 부족해 opcache.enable 지시자까지 확인하며, ini_get 이
차단된 환경은 "확인 불가"로 폴백해 경고도 차단도 하지 않는다.
2026-07-20 14:17:49 +09:00
HeuJung c0e0d82459 refactor(extension): 확장 미들웨어 선언적 self-gate 정규화
확장이 SP Kernel 미들웨어 그룹을 직접 조작하거나 라우트 파일에 미들웨어
FQCN 을 직접 부착하던 임시 방식을, 확장이 부착 대상(targets)을 명시 선언하고
코어가 요청 시점에 라우트명·URI 로 매칭해 실행하는 self-gate 로 정규화.
번들 7건(ecommerce/gdpr/pay 3종) 이전, audit 룰 3건·문서·버전 동기화 동반.

부수적으로 generate-skills.cjs 가 재생성 시 신의성실(부수의무) 섹션을 소실
시키던 회귀를 수정 — 스킬별 불릿을 스크립트 SSoT 로 편입해 재생성에도 보존.
2026-07-16 10:22:51 +09:00
HeuJung c4ea9a6cd1 v7.0.0 release 2026-07-01 10:30:32 +09:00
HeuJung 1db039ff34 v7.0.0-beta.4 release 2026-05-11 11:29:41 +09:00
HeuJung 05db17887d v7.0.0-beta.3 release 2026-04-23 17:36:31 +09:00
HeuJung 6595fd0eb5 v7.0.0-beta.1 release 2026-04-01 10:30:52 +09:00