미출시 예약 작업 기능에서 스케줄 권한만 위임받은 계정이 Shell/Artisan
명령으로 임의 코드를 실행할 수 있던 문제를 선제적으로 차단(KVE-2026-1567).
Shell 은 기본 차단 + 실행 파일 화이트리스트 + 셸 미경유 인자배열 실행,
Artisan 은 차단목록. 저장 시점(FormRequest)과 실행 시점(Service) 이중 검증.
아울러 공개 release 보다 앞선 미출시 버전들의 CHANGELOG 출시 날짜를
2026-07-14 로 통일.
서버가 외부 입력으로 목적지가 제어되는 outbound HTTP 요청을 보내는 지점을
전수 조사해, 내부망(사설 IP·루프백·링크로컬·메타데이터)과 host 위조를 차단한다.
KVE-2026-1546(이니시스 인증 URL 접두사 매칭 우회)과 동일 클래스의
지점(언어팩 URL 설치, 스케줄 URL 호출, 배송비 계산 API, GitHub URL 판정)을
코어 공용 유틸 App\Support\OutboundUrlValidator 로 수렴시켰다.
- 접두사 매칭을 host 완전일치로 교체 — userinfo(@) 위장과 접미사 확장 도메인
우회 벡터 차단.
- 사내 서버 호출이 정당한 관리자 지점(스케줄·외부 API)은 신규 설정
security.allow_internal_outbound_urls(기본 off)로 옵트인.
- 언어팩 URL 설치는 원격 코드 다운로드라 옵트인과 무관하게 내부 주소 항상 차단.
- 웹소켓 연결 테스트는 localhost·사설 IP 가 정상 구성이므로 구조적 위조만 차단.
각 지점에 red 재현 → 수정 → green 회귀 테스트 + 실브라우저 E2E 로 검증.
수기 API 문서가 자동 재생성으로 소실되는 사고를 막는 PreToolUse 훅을 함께 추가한다.
공개 release 빌드의 파일 본문 누출 검사에서 검출된 항목을 정리한다.
api-documentation.md 는 릴리즈에 포함되지 않는 내부 지침 파일명을 참조하고
있었다. 공개본 독자에게는 존재하지 않는 파일을 가리키므로 공개 진입점
문서(AGENTS.md)로 교체했다.
이커머스 배송국가 테스트 2종의 주석에서 내부 역할 호칭을 제거했다. 판단
근거 서술은 그대로 두고 호칭만 걷어냈으므로 어서션·실행 경로는 불변이다.
검증: check-public-release-leakage error 0 / warn 0,
PruneEmptyShippingCountryNameLocalesTest 10/10 통과.
api:docgen --seed 가 생성하는 샘플 언어팩이 scope=core + status=active 로
만들어져 getActiveCoreLocales 승격 쿼리에 걸렸다. 그 결과 시스템 전역
supported_locales/translatable_locales 가 넓어지고, 그 아래에서 시드된
다국어 데이터에 실재하지 않는 로케일 키가 박혔다. 샘플을 비활성화하면
그 키가 TranslatableField 검증을 통과하지 못해 저장이 막힌다.
문서용 샘플은 조회 예시일 뿐 시스템 동작을 바꿀 자격이 없으므로,
승격 경로를 벗어난 조합(module 스코프 + installed 상태 + 실재 번들 로케일)
으로 생성하도록 바꾸고 audit 룰로 재발을 차단한다.
README 재생성 시 사람이 덧붙인 헤딩 섹션이 통째로 소실되던 사전 결함도
함께 수정했다(경계를 임의의 ## 이 아닌 확장 목차 헤딩으로 특정).
최상위 README 의 "API 레퍼런스" 가 문서 작성 규정(api-documentation.md)을
가리켜, 개발자·AI 가 실제 엔드포인트 레퍼런스에 도달할 경로가 없었다.
코어 목차(docs/backend/api/README.md)도 어느 인덱스에서도 참조되지 않았고,
확장 API 문서 9종 역시 공개 진입점이 없었다.
코어 목차를 코어+확장 통합 진입 문서로 승격하고, 상단에 공통 규약(인증·
응답 봉투·페이지네이션·에러)을 실측 근거로 서술했다. 확장 목차는 파일 시스템
패턴 스캔으로 생성해 확장명 하드코딩을 두지 않았고, --scope 를 좁혀 실행해도
개요와 확장 표가 소실되지 않도록 보존 경로를 분리했다.
아울러 설치 없이 동작을 확인할 수 있도록 README 상단에 데모 사이트와
관리자 데모 링크를 추가했다.
INSTALLER_COMPLETED=true 가 적힌 .env 를 빈 DB 새 서버에 복사한 뒤
php artisan migrate 로 테이블을 만들기 전에 앱이 부팅되면, installer_completed
fast-path 가 테이블 존재를 잘못 전제하여 뒤따르는 쿼리가 table not found 로
부팅을 깨뜨렸다. 마이그레이션을 실행하려는 부팅에서 그 명령이 오히려 실패하는
닭-달걀 상태가 된다.
fast-path 자체(매 요청 information_schema 쿼리 제거 성능 최적화)는 유지하되,
이미 존재하던 참조 구현(LanguagePackServiceProvider::isRegistryReady)의 방어
패턴을 InstallerContext::isSchemaMutatingCommand 로 SSoT 화하여 나머지 5곳에
통일 적용했다.
- app/Support/InstallerContext.php: 마이그레이션 계열 콘솔 명령 판정 헬퍼
(migrate/migrate:fresh/migrate:refresh/migrate:rollback/migrate:reset/db:wipe)
- Caches{Module,Plugin,Template}Status: fast-path 앞에 && 가드
- {Module,Plugin}RouteServiceProvider: hasTable 검증 진입 조건에 || 가드로
무방비 pluck 도달 차단
- LanguagePackServiceProvider: 인라인 argv 검사를 헬퍼로 치환(migrate:refresh 보강)
부류 B(NotificationHookListener, IdentityPolicyRepository)는 fast-path 이후
쿼리가 이미 try/catch 로 보호되어 빈 DB 에서도 안전하므로 수정 제외.
최초 설치 흐름은 migrate 시점에 완료 플래그가 없어(false) 이번 가드의 영향권
밖이며 원래대로 hasTable 폴백을 탄다. 가드가 실제 발동하는 유일 케이스는
"플래그 true + 빈 DB" 뿐이고, 정상 운영 웹 요청은 fast-path 성능이 유지된다.
회귀 테스트: InstallerContextTest 8건 + InstallerCompletedGuardTest 5건 추가
(수정 전 5건 fail → 수정 후 green). 관련 스위트 355 passed.
7.0.3 출시 버전 bump: 코어 버전 SSoT 7곳 + CHANGELOG 동기화.
확장 API 문서가 인덱스·목차 어디에도 노출되지 않아 처음 온 개발자/AI 가
발견할 수 없던 간극을 해소했다. 코어에 확장명을 하드코딩하지 않고(동적 로딩 원칙),
파일 시스템 패턴 스캔 + 확장 소유 목차 규약으로 발견성을 확보한다.
- ApiDocScaffolder::readmeIndex — 각 대상의 docs/api/README.md 목차(도메인 파일·
엔드포인트 수)를 @generated 블록으로 생성(멱등, 사람 개요 보존). 커맨드가 대상별
README 를 방출(readmeFile 경로 헬퍼). 코어 + 9 확장 = 10개 목차 생성.
- generate-docs-index.cjs scanExtensionApiReadmes — {modules,plugins}/_bundled/*/
docs/api/README.md 를 패턴 스캔(확장명 하드코딩 0)해 ·AGENTS.md 에
"확장 API 레퍼런스" 표로 자동 편입. 확장 추가/삭제 시 재생성만으로 반영.
- api-documentation.md 에 README 목차 규약 + 발견 경로 명문화. coverage.json 에
api-doc-readme-index(manual-only) 등록. 루트 README API 레퍼런스 링크 갱신.
테스트: readmeIndex 목차 생성·멱등 2건 + 스캐너 편입·하드코딩부재 2건(node --test).
targets 에 명시된 {modules,plugins,templates,lang-packs}/_bundled 가
상위 protected(modules 등)에 걸려 3-way apply 목록에서 제외되던 결함 수정.
targets 가 protected 보다 더 구체적이면 오버라이드해 통과시켜, 코어 업데이트로
번들 확장 갱신 파일(composer.json·vendor-bundle.json 등)이 정상 반영되도록 한다.
자동 발견 폴백의 방어 및 prune 롤백 경로는 불변.
아울러 이슈 템플릿의 사용법 문의 링크를 Discussions 에서 sir.kr Q&A 로 전환.
core:update Step 7 이 targets 전체를 무조건 재복사하고 orphan 을 삭제하여
사용자 .htaccess 커스텀 블록·_bundled 커스텀 확장이 소실되던 사고(공개 )를
차단한다. 백업(base)↔_pending(theirs) 3-way md5 비교로 코어가 실제 변경/추가한
파일만 산출해 적용하고, 코어 미변경 파일은 복사·권한·mtime 을 전부 스킵해 현재
디스크 상태(사용자 수정 포함)를 보존한다. 전체 덮어쓰기 + orphan 정리가 필요하면
--prune 으로 opt-in. 백업 부재(--no-backup) 시 base 가 없어 전체 덮어쓰기로 안전
회귀 + 안내를 출력한다.
기존 stale 정리 테스트는 새 기본(증분)에서 orphan 을 삭제하지 않으므로 prune
컨텍스트로 전환했고, 증분 3-way 판정·목록 적용·fallback 단위 테스트와 --prune
커맨드 표면 테스트를 추가했다.
번들 ja 언어팩은 코어 apply_mode 안내 키(g7-core-ja)와 함께, 같은 빌드에서
동기화된 관리자↔유저 크로스링크 라벨(board/ecommerce/admin_basic/basic)의 일본어
번역을 반영하고 각 패키지 버전·CHANGELOG 를 갱신했다.
실측(g7.dev) 결과 줄바꿈 제거·gzip 은 이미 적용된 상태였고,
남은 실질 개선 3종을 구현:
- 모든 API 응답에 JSON_UNESCAPED_UNICODE 적용 — 한글이 \uXXXX(6B)로
팽창하던 것을 raw UTF-8(3B)로 직렬화 (lang pack gzip -9.1%)
- 공개 레이아웃 서빙에서 개발자용 comment/_comment 재귀 제거,
편집 모드(with_source_meta)는 보존 (layout gzip -34.9%)
- 번들 JS/CSS(BinaryFileResponse) gzip 압축 사각지대 해소 —
기존 미들웨어가 getContent==false 로 압축을 건너뛰던 문제 (-73~77%)
sudo(root) 로 core:update 를 실행하면 업그레이드 스텝 spawn 자식이 실패할 때
운영자에게 core:execute-upgrade-steps 재실행을 안내하는데, 이 명령을 root 로
그대로 재실행하면 스텝이 만드는 파일이 root 소유가 되어 이후 웹서버(www-data)
요청이 그 경로에 쓰기 실패한다.
재실행 안내를 실행 환경 4가지로 분기한다:
- non_root: 일반 SSH 사용자 / 공유 호스팅(웹서버=PHP=실행 유저) / posix 미지원 → 명령만
- root_web_known: 웹서버 계정 식별 → sudo -u {계정} + 계정명 경고
- root_web_symmetric: root 서비스 구성 → 명령만
- root_web_unknown: 계정 추정 실패 → placeholder + 확인 방법 안내
웹서버 계정은 FilePermissionHelper::inferWebServerOwnership 로 추정한다.
출력 전담 renderResumeGuidance 를 분리해 4분기 출력 계약을 회귀 테스트로 고정.
ExtensionConfigCacheListener 가 config 캐시 재생성을 큐로 디스패치해,
워커 미가동 환경·CLI 커맨드(plugin:deactivate 등)에서 재생성이 지연/누락되던
결함을 수정한다. 훅 Action 리스너의 기본 등록 정책이 큐 디스패치이므로,
getSubscribedHooks 각 훅에 'sync' => true 를 추가해 동기 실행한다.
- 회귀 테스트 test_all_hooks_are_synchronous 추가
- docs/backend/service-provider.md 에 sync 요구사항 명문화
- 2브라우저(Chrome MCP + Playwright MCP) 회귀 검증 중 발견
매 요청 균일하게 발생하던 부팅 오버헤드를 4개 축에서 제거한다.
- 설치 완료 상태에서 매 요청 반복되던 information_schema 조회(알림·본인인증
테이블 hasTable)를 installer_completed 가드로 스킵. 미설치 환경은 기존
hasTable 폴백 유지.
- 코어+모듈+플러그인 정적 훅 매핑을 bootstrap/cache/hooks.php 에 사전 계산해
매 요청 디렉토리 스캔·리플렉션·클래스 로딩을 제거(route:cache 동형).
등록↔발화 계약·매핑 바이트 동일. 확장/코어 변경 시 자동 재생성, 캐시
부재·손상은 스캔 폴백.
- 확장 소스(Modules\*/Plugins\*)를 autoload-extensions.php 의 classmap 에
편입해 findFile 파일시스템 스캔을 제거(느린 FS·cold OPcache 환경 직격).
클래스 로딩은 여전히 lazy, PSR-4 폴백 유지.
- config 캐시를 변경 지점(설정 저장/코어·확장 업데이트/APP_KEY 재생성/설치
완료)에서 clear 후 즉시 재생성하도록 ConfigCacheHelper 로 일원화. clear 만
하고 방치돼 캐시가 영구 비활성으로 남던 성능 손실 제거. HookCacheManager::read
요청당 1회 로드(memo)로 중복 파싱 제거.
부팅 매핑 fingerprint 스캔↔캐시 완전 동일(229 action + 35 filter). 신규/수정
8개 스위트 54 pass. 순수 내부 부팅 인프라 — 확장 공개 표면·훅 계약 불변.