WordPress.org 플러그인 제출 스킬
어떤 플러그인이든 wp.org 자동 스캔과 수동 검토를 다섯 번 재업로드하지 않고 통과시킬 수 있습니다. 실제 리뷰 사이클에서 얻은 경험(예: 단순한 .gitkeep 하나가 전체 스캔을 실패하게 만드는 경우)을 바탕으로 정제되었습니다.
유지 관리자: moksa · moksaweb.com · 라이선스: MIT
사용 시기
사용자가 다음을 요청할 때 이 스킬을 실행하세요:
- wordpress.org용 깨끗한 설치 zip 패키징/빌드
- wp.org 디렉토리에 플러그인 제출 또는 재제출
- wp.org 리뷰 팀 피드백 수정 (4가지 고전적인 카테고리)
- 자동 스캔 거부 진단 (숨김 파일, 접두사, DB 호출 등)
- 플러그인 버전 일괄 업데이트 (헤더 + 리드미 + 상수)
- 업로드 전 플러그인 사전 점검
다음의 경우에는 사용하지 마세요:
- WordPress가 아닌 프로젝트
- wp.org에 제출되지 않을 비공개/내부 플러그인 (검토 규칙이 적용되지 않으나 보안 관행은 여전히 유용함)
두 가지 관문
- 자동 스캔 (Plugin Check, 업로드 시 실행) — 하드 블록. 사람 검토에 도달하려면 반드시 통과해야 합니다.
references/automated-scan.md참조. - 수동 검토 (사람 검토자, 스캔 통과 후) — 이슈를 이메일로 보내면 수정 후 답변합니다. 거의 항상 4가지 코드 카테고리 중 하나(
references/review-categories.md) 와 린터가 잡지 못하는 정책 가이드라인(라이선싱, 원격 코드, 추적, 관리자 알림, 상표, 외부 서비스 공개)이 포함됩니다(references/guidelines.md).
참고 자료 맵
| 주제 | 파일 |
|---|---|
| Plugin Check (PCP) 실행 방법 — UI/CLI, 전체 31개 검사 → 수정 맵, 오류 vs 경고 | references/plugin-check.md |
| 자동 스캔 하드 블록 (숨김 파일, DB, 허용되지 않은 호출) | references/automated-scan.md |
| 4가지 수동 검토 카테고리 + 답변 표현 | references/review-categories.md |
| 실제 리뷰 교훈 (샘플→전체 검색, 3가지 논스 케이스, phpcs:ignore≠이스케이프, 캐스트≠살균, 경로, 포크 접두사) | references/real-review-lessons.md |
| 접두사 모든 전역 (함수/옵션/훅/핸들/커스텀 포스트 타입/메타 등) | references/prefixing.md |
살균 · 유효성 검사 · 이스케이프 + $wpdb->prepare + 웹훅 서명 | references/sanitization-escaping.md |
| 인큐 JS/CSS (의존성/버전, 인라인, 게이팅, 블록, 원시 태그 금지) | references/enqueue.md |
| i18n (textdomain==슬러그, 리터럴 문자열, 번역자 주석, JS) | references/i18n.md |
| 역량 및 권한 콜백 (권한 부여, REST, priv/nopriv) | references/capabilities.md |
| 정책 가이드라인 (라이선스, 원격 코드, 추적, 알림, 상표, 공개) | references/guidelines.md |
| readme.txt 형식 사양 (헤더, 태그≤5, 짧은 설명≤150, 스크린샷, 외부 서비스) | references/readme-txt.md |
| Abilities API · AI Client · MCP (능력별 권한, 데이터 경계, 파괴적 작업 확인, 제공자 공개) | references/abilities-ai-mcp.md |
| 보안 추가 사항 (파일/WP_Filesystem, 업로드, SSRF, 허용되지 않는/난독화 호출) | references/security-extras.md |
| 제거 및 생명주기 (데이터 정리, 사용자 콘텐츠 삭제 금지, 멀티사이트) | references/uninstall.md |
| zip 패키징 + 버전/헤더/리드미 일관성 + .mo | references/packaging.md |
| 스캔/PHPCS 코드 → 수정 조회 | data/plugin-check-errors.csv |
빠른 업로드 전 체크리스트 (복사-붙여넣기 확인)
자동 스캔 (하드 블록)
[ ] zip에 숨김 파일 없음: `find . -name '.*' -not -name '.' -not -name '..'` 결과가 비어 있어야 함
(.gitkeep / .gitignore / .DS_Store / .editorconfig 모두 "hidden_files" 실패)
[ ] zip에 node_modules/, .git/, tests/, scripts/, phpcs.xml, webpack.config.js 없음
[ ] 모든 플러그인 소유 전역 변수가 고유한 >=4자 접두사 사용 (wp_ / __ / 단일 _ 금지)
[ ] $wpdb에 prepare() 없이 직접 쿼리 금지; 원격 코드 실행 금지; 전화 집(phone-home) 금지
수동 검토 (4가지 카테고리)
[ ] 인큐: 원시 echo '<script>'/'<style>' 금지; wp_enqueue_* + wp_add_inline_* 사용
[ ] 모든 상태 변경 AJAX/admin_post에 논스 + current_user_can 사용; 웹훅은 hash_equals()로
제공자 서명 확인 (WP 논스 불가능)
[ ] 모든 $_POST/$_GET 살균 (wp_unslash + sanitize_*); json_decode -> map_deep 사용;
출력은 늦게 이스케이프 (esc_html/esc_attr/esc_url/wp_kses)
[ ] textdomain == 슬러그; __() 인자는 리터럴 문자열; /* translators: */ 주석을 %s 앞에 추가
정책 (수동 검토, 린터로 잡을 수 없음 — references/guidelines.md)
[ ] GPL 호환 라이선스 명시 + LICENSE 파일 포함; 모든 번들 라이브러리/에셋 GPL 호환
[ ] 원격 코드 실행 금지 / CDN에서 실행 가능한 JS 로딩 금지; 에셋은 로컬 번들
[ ] readme "== External services =="에 타사 API 호출 공개 (어떤 데이터, 언제, URL)
[ ] 명시적 동의 없이 데이터 수집/추적 금지 (기본 OFF); 프런트엔드 크레딧 링크는
옵트인 전용; 관리자 알림은 닫을 수 있고 화면 범위로 제한 (알림 스팸 금지)
[ ] 슬러그/이름이 소유하지 않은 상표로 시작하지 않음 (woocommerce-… / google-…)
README.TXT (references/readme-txt.md — wp.org 리드미 검증기로 확인)
[ ] 헤더 구문 분석: Contributors=wp.org 사용자명, Tags<=5, Stable tag = 실제 배포 버전
[ ] 짧은 설명 <=150자, 일반 텍스트; Changelog에 Stable tag 항목 있음
[ ] 스크린샷/배너/아이콘은 SVN /assets/에 위치, zip 내부에 포함 금지
AI / ABILITIES / MCP (플러그인이 능력 등록, LLM 호출 또는 MCP 노출 시)
[ ] 모든 wp_register_ability는 __return_true가 아닌 실제 permission_callback (객체 수준 cap) 사용
[ ] 데이터 경계: ability/MCP 도구는 현재 사용자의 cap이 허용하는 데이터만 반환
[ ] 모델/에이전트 입력은 신뢰할 수 없는 것으로 처리; 파괴적 능력은 확인 후 게이트
[ ] LLM/AI SDK 번들 (GPL); == External services ==에 제공자 공개; 키는 절대 로깅 금지
[ ] MCP 엔드포인트 인증 (앱 비밀번호/OAuth) 및 각 ability의 permission_callback 실행
추가 보안 + 생명주기
[ ] eval/shell/난독화/extract 없음; var_dump/print_r/error_log 잔류 금지
[ ] 파일은 WP_Filesystem을 통해 업로드 전용; 업로드는 wp_handle_upload + MIME 화이트리스트
[ ] 원격 호출은 wp_safe_remote_* (SSRF: 내부 범위 차단) + 타임아웃 + 응답 확인
[ ] uninstall.php는 WP_UNINSTALL_PLUGIN으로 보호; 접두사가 붙은 데이터만 제거; 사용자 콘텐츠 절대 삭제 금지
패키지 + 메타데이터
[ ] 버전 동기화: 메인 파일 헤더 Version, 버전 상수, 리드미 Stable tag, Changelog 모두 일치
[ ] Requires 헤더가 메인 파일과 리드미 모두에 일치 (WP / PHP / WC) — 또한 활성화를 게이트하는
런타임 MIN_* 상수와 일치
[ ] vendor/는 `composer install --no-dev` 결과 (squizlabs/phpunit/phpcompatibility 없음)
[ ] zip 루트 폴더명 == 플러그인 슬러그
워크플로우
- 작업에 대한 규칙 읽기 (위의 참고 자료 맵 참조) —
references/automated-scan.md+references/review-categories.md로 시작한 후, 발견 사항과 일치하는 심층 자료 (prefixing.md/sanitization-escaping.md/enqueue.md/i18n.md)와references/packaging.md(빌드용)를 읽습니다. - 깨끗한 zip 빌드 —
scripts/build-zip.sh실행 (git 아카이브 처리, 개발 파일 + 모든 점 파일 제거,vendor/를--no-dev로 설치, 슬러그를 루트로 zip). 절대 작업 트리를 수동으로 zip하지 마세요. 그렇게 하면 개발 쓰레기와 점 파일이 유출됩니다. - 사전 점검 —
scripts/preflight.sh <zip-또는-디렉토리-경로>실행: 숨김 파일 스캔, 버전 동기화 + 헤더/리드미 일관성 확인, PHPCS가 설정된 경우 실행. 깨끗해질 때까지 수정. - Plugin Check (PCP) 실행 — 디렉토리 자체 자동 검사를 로컬에서 실행하는 공식 도구.
wp plugin check <슬러그> --require=./wp-content/plugins/plugin-check/cli.php(또는 도구 → Plugin Check). 모든 ERROR 수정, 모든 WARNING 검토. 각 31개 검사, 플래그 및 각각을 수정하는 스킬 챕터는references/plugin-check.md참조. - 업로드. 자동 스캔이 거부하면 → 정확한 코드를 읽고
data/plugin-check-errors.csv를 통해 매핑하여 수정, 재빌드, 재업로드. - 수동 검토 답변 — 검토자가 이슈를 이메일로 보내면 각각을 모든 곳에서 수정 (예제는 샘플일 뿐 — 전체 트리를 grep하고, 전체 플러그인에 대해 Plugin Check + PHPCS 실행; 동일한 카테고리 반복 = 즉시 재거부). 그런 다음 변경 사항을 참조하여 항목별로 답변합니다. 표준 수정 + 답변 표현은
references/review-categories.md를, 카테고리별 전체 검색 명령과 첫 번째 수정 후 살아남는 함정은references/real-review-lessons.md를 참조하세요.
어렵게 얻은 주의사항 (값비싼 경험)
.gitkeep는 자동 스캔을 실패시킵니다. 숨김 파일입니다. 패키지에서 모든 점 파일을 제거하세요. git에서.gitkeep을 제거하여git archive가 다시 추가하지 않도록 하세요.- 요구 버전 불일치. wp.org는 메인 파일 헤더와 readme.txt를 모두 읽습니다. 런타임
MIN_WC/MIN_PHP상수는 활성화를 별도로 게이트할 수 있습니다. 세 가지가 모두 일치해야 하며, 그렇지 않으면 낮은 버전 사용자가 성공적으로 설치한 후 깨진 기능을 경험하게 됩니다. - 웹훅은 WP 논스를 사용할 수 없습니다. 검토자가 IPN 핸들러에서 "no nonce"를 지적합니다 — 이는 오탐(false positive)입니다. 올바른 해결책은
hash_equals()를 사용한 제공자 서명 확인입니다. 각 줄에phpcs:ignore+ 이유를 주석으로 문서화하세요. json_decode는 살균이 아닙니다. 결과에 항상map_deep(sanitize_text_field)를 적용하세요.composer install(--no-dev없이) 는 phpcs/phpunit을 vendor/로 유출시켜 패키지가 비대해지고 지적됩니다. 배포용 zip에는 항상--no-dev를 사용하세요.