prepare-release.sh 의 파일 본문 누출 검사가 error 10건으로 릴리즈를 차단하고 있었다. 공개 저장소는 를 제외하므로, 배포되는 파일이 그 경로나 를 참조하면 공개본에서 깨진 참조가 된다. - 시나리오 매니페스트 4건: test_files 의 내부 하네스 테스트 경로 제거 - Playwright spec 3건: 룰 파일 경로/@see 포인터를 자기완결 서술로 교체 (@see 2건은 이미 stale — 가리키던 사례 번호가 실제와 달랐다) - 소스/테스트 주석 3건: 파일명 노출 제거 내부 역할 호칭이 공개본에 영구 노출되는 warn 23건 중 21건도 함께 중립 표현으로 정정했다. API 표면 파일 2건은 주석만 고쳐도 문서 커버리지 게이트에 영구 면제가 필요해져 보류한다 — warn 은 릴리즈를 차단하지 않는다. 부수로 드러난 기존 결함 둘을 같은 세션에서 처리: - gdpr Playwright config 에 locale 고정 누락 — 첫 로드가 en-US 로 나가 화면이 영어로 굳으면 한국어 문구를 단언하는 spec 이 전부 실패한다 - 언어팩 시나리오의 정적 검사 계층 전용 효과 2건을 effects 에서 rules_layer_coverage 로 이관 — 런타임 테스트로 성립하지 않는 항목이라 그 칸이 정합 위치다 검증: 누출 검사 error 0, audit --working 위반 0(룰 192), 시나리오 매니페스트 8건 errors 0, gdpr Vitest 148 green, PHP lint/JSON/YAML 정상. 버전 정합은 코어 working 7.0.6 / 공개 latest 7.0.5 = new-bump 로 릴리즈 가능 상태.
96 lines
7.0 KiB
YAML
96 lines
7.0 KiB
YAML
feature: 언어팩 PHP 구문 화이트리스트 및 활성화 권한 경계
|
||
|
||
description: |
|
||
언어팩의 PHP 파일은 활성화되면 fallback 경로에 배선되어 `require` 된다. 즉 파일 내용이
|
||
그대로 실행되며, `Accept-Language: {locale}` 요청 한 번이면 발화한다.
|
||
|
||
기존 방어는 두 지점 모두 뚫려 있었다.
|
||
- 내용 검사: 위험 함수 11개를 정규식으로 나열한 블록리스트 —
|
||
`file_put_contents` `fopen` `call_user_func` `assert` 백틱 등이 목록에 없어 통과했고,
|
||
`('system')('id')` 처럼 함수 이름을 아예 쓰지 않는 형태는 원리적으로 잡을 수 없었다.
|
||
- 권한 경계: 설치(install)와 활성화(manage)는 별도 권한인데, 설치 요청의
|
||
`auto_activate` 플래그가 `activate()` 를 우회하고 `status=Active` 를 직접 기록해
|
||
install 권한만으로 활성화가 성립했다. 활성 승격이 `require` 배선의 필수 조건이므로
|
||
이 경계가 체인의 마지막 관문이다.
|
||
|
||
본 기능은 판정을 뒤집는다.
|
||
- 내용: "번역 배열만" 통과시키는 토큰 상태기계 (함수 호출은 이름과 무관하게 전부 거부)
|
||
- 형식·위치: 확장자 화이트리스트(json/php/md, 대소문자 무관) + PHP 위치 강제 + 심볼릭 링크 거부
|
||
- 권한: 외부 소스(파일·URL·GitHub)의 `auto_activate` 에 활성화 권한을 필드 단위로 요구
|
||
- 활성화 경로 단일화: 설치 트랜잭션은 항상 installed 로 기록하고 activate() 를 경유
|
||
|
||
axes:
|
||
vector: [php_syntax_validator, assert_security_rules, install_from_untrusted_source, install_from_bundled, finalize_install_activation]
|
||
actor_permission: [cli_system, install_only, manage_only, install_and_manage]
|
||
|
||
cross_product: |
|
||
vector × actor_permission 이 핵심이다. 같은 판정이라도 어느 계층에 걸리느냐에 따라
|
||
깨뜨리는 것이 다르다 — 권한 검사를 서비스 계층에 두면 CLI(`language-pack:install`,
|
||
`language-pack:provision`)와 확장 설치 cascade 가 깨지고, FormRequest 의 `authorize()`
|
||
에 두면 `auto_activate` 를 보내지 않은 정상 설치까지 403 이 된다.
|
||
|
||
페이로드 형태(문자열 리터럴 호출 / 가변 변수 / heredoc / 닫는 태그 뒤 코드 / 대문자 확장자
|
||
/ phar / 잘못된 locale 디렉토리 / 심볼릭 링크)는 별도 axis 로 두지 않는다. 전부 같은
|
||
문법 규칙 하나 또는 같은 파일 정책 하나에서 갈리므로 axis 로 두면 조합만 늘고 판정
|
||
지점은 하나다. 대신 DataProvider 로 전수 전개하고 effects 로 대표한다.
|
||
|
||
exclusions:
|
||
- { vector: php_syntax_validator, actor_permission: install_only, reason: "프레임워크 의존이 없는 순수 판정 유틸 — 권한 컨텍스트가 존재하지 않는다" }
|
||
- { vector: php_syntax_validator, actor_permission: manage_only, reason: "동일 — 권한 개념 없음" }
|
||
- { vector: php_syntax_validator, actor_permission: install_and_manage, reason: "동일 — 권한 개념 없음" }
|
||
- { vector: assert_security_rules, actor_permission: install_only, reason: "서비스 계층의 내용·형식 검사로 권한과 무관하게 모든 설치 경로에 동일 적용" }
|
||
- { vector: assert_security_rules, actor_permission: manage_only, reason: "동일 — 권한별 분기 없음" }
|
||
- { vector: assert_security_rules, actor_permission: install_and_manage, reason: "동일 — 권한별 분기 없음" }
|
||
- { vector: install_from_untrusted_source, actor_permission: cli_system, reason: "CLI 는 FormRequest 를 거치지 않는다 — 권한 게이트를 서비스에 두지 않기로 한 설계의 직접 귀결이다. CLI 비파괴 자체는 finalize_install_activation × cli_system 셀이 실 커맨드 실행으로 단언한다(cli_installation_is_unaffected_by_the_http_activation_gate)" }
|
||
- { vector: install_from_bundled, actor_permission: manage_only, reason: "설치 권한이 없어 라우트 미들웨어에서 끊긴다 — 외부 소스 쪽에서 같은 경계를 이미 단언하므로 번들에서 반복하지 않는다" }
|
||
- { vector: install_from_bundled, actor_permission: cli_system, reason: "동일 — FormRequest 미경유" }
|
||
- { vector: finalize_install_activation, actor_permission: install_only, reason: "서비스 계층 활성화 경로로 HTTP 권한 컨텍스트가 없다 — 권한 판정은 FormRequest 계층이 소유" }
|
||
- { vector: finalize_install_activation, actor_permission: manage_only, reason: "동일 — 권한 컨텍스트 없음" }
|
||
- { vector: finalize_install_activation, actor_permission: install_and_manage, reason: "동일 — 권한 컨텍스트 없음" }
|
||
|
||
effects:
|
||
# 구문 화이트리스트
|
||
- translation_array_files_are_accepted
|
||
- function_calls_are_rejected_regardless_of_name
|
||
- name_free_bypasses_are_rejected
|
||
- broken_and_oversized_sources_are_rejected_early
|
||
- violation_line_is_reported
|
||
# 파일 정책
|
||
- install_rejects_php_that_is_not_a_translation_array
|
||
- install_rejects_files_outside_the_extension_whitelist
|
||
- install_rejects_php_outside_the_declared_locale_directory
|
||
- install_rejects_packages_containing_symlinks
|
||
- install_accepts_real_world_translation_files
|
||
- every_bundled_pack_passes_the_new_validator
|
||
# 권한 경계
|
||
- install_only_user_cannot_auto_activate
|
||
- install_without_auto_activate_is_not_blocked
|
||
- manage_user_can_auto_activate
|
||
- manage_only_user_cannot_install_at_all
|
||
- overwriting_an_active_pack_does_not_inherit_active_status
|
||
# 활성화 경로 단일화
|
||
- activated_hook_fires_exactly_once_on_auto_activate
|
||
- auto_activate_skipped_when_slot_occupied
|
||
- auto_activate_goes_through_activate_and_enforces_dependencies
|
||
- reinstalling_an_active_pack_does_not_demote_it
|
||
- cli_installation_is_unaffected_by_the_http_activation_gate
|
||
- update_of_an_active_pack_keeps_it_active
|
||
- update_of_an_inactive_pack_does_not_promote_it
|
||
|
||
# E2E axis 없음: 판정은 전부 서버측(FormRequest / Service)이다. 관리자 화면에서
|
||
# auto_activate 를 보내는 곳은 번들 재설치 모달 하나뿐이고, 게이트를 네 경로 전부에
|
||
# 걸기로 하면서(프로젝트 결정 2026-08-04) 이 모달이 보내는 값을 운영자의 활성화 권한
|
||
# (`abilities.can_activate`)으로 분기하도록 바꿨다 — 권한이 없으면 auto_activate 를
|
||
# 보내지 않아 재설치는 그대로 되고 팩만 installed 로 내려간다.
|
||
#
|
||
# 이 분기는 표현식 한 줄이고 관측 지점이 "요청 본문에 무엇이 실렸는가" 하나뿐이라,
|
||
# 브라우저를 띄우는 것보다 서버측 권한 조합 테스트가 같은 것을 더 촘촘히 고정한다
|
||
# (install_only / install_and_manage 두 조합 모두 단언). 렌더 결과·조작 흐름이
|
||
# 달라지지 않으므로 E2E axis 를 두지 않는다.
|
||
|
||
test_files:
|
||
- tests/Unit/Services/LanguagePack/LanguagePackPhpArrayValidatorTest.php
|
||
- tests/Unit/Services/LanguagePack/LanguagePackSecurityTest.php
|
||
- tests/Unit/Services/LanguagePack/LanguagePackServiceTest.php
|
||
- tests/Feature/Api/Admin/LanguagePackControllerTest.php
|