KISA 신고포상제 접수 3건(KVE-2026-1677/1678/1679)의 공통 원인은 하나다 — "위험한 것을 골라 막았고, 목록 밖으로 우회됐다". 세 지점 모두 판정을 뒤집는다. 예약 작업 Artisan (KVE-2026-1679) 차단 9개 / 허용 229개였던 구조를 허용목록으로 바꾸고 명령마다 허용 옵션을 선언한다. 검증 과정에서 보고서보다 짧은 경로를 추가로 발견했다 — 검증기는 preg_split 의 첫 토큰을 명령명으로 봤지만 실행부는 문자열째 넘겼고, parameters 가 비면 Laravel 이 StringInput 으로 재파싱한다. 따옴표 한 쌍이면 차단목록의 tinker 가 그대로 실행됐다 (마커 파일로 확인). 파서를 엄격 형태 하나로 고정하고 실행은 (명령명, 인자배열)로 넘겨 재해석 지점 자체를 없앴다. 언어팩 (KVE-2026-1678) 위험 함수 11개를 정규식으로 나열하던 검사를 토큰 상태기계로 교체해 "번역 배열만" 통과시킨다. 목록에 없는 함수도, 함수 이름을 아예 쓰지 않는 형태도 원리적으로 막힌다. 파일 형식·PHP 위치·심볼릭 링크도 화이트리스트로 뒤집었다. 활성 승격이 require 배선의 필수 조건이므로 설치와 활성화의 권한 경계를 네 설치 경로 전부에 같은 강도로 건다 — 경로별 면제는 곧 우회로가 되기 때문이다. 화면은 운영자의 활성화 권한을 보고 auto_activate 를 실을지 결정하므로 재설치 흐름은 그대로 동작한다. 인스톨러 (KVE-2026-1677) 실행 바이너리 자리에 인자가 들어가는 것을 형태 규칙으로 차단한다. escapeshellarg 는 "하나의 인자" 임만 보장할 뿐 그 인자가 실행 파일인지 스크립트인지는 보장하지 않는다. 판정을 의존성 0 공용 정책으로 모아 진단 API·설치 워커·저장 시점이 같은 규칙을 쓴다. stat 은 호출하지 않는다 — 접근이 제한된 서버에서 정상 경로가 거부되던 회귀를 재발시키지 않기 위함이다. 검증: 백엔드 392건 + 프론트 7건 green(skip 1, symlink 미지원 환경). PoC 마커로 red 를 먼저 확보하고, 수정 후 stash 역회귀로 다시 red 가 되는 것까지 확인했다.
API 레퍼런스 문서 목차
소유: 코어 · 생성:
php artisan api:docgen(실측 기반). 아래 표는 자동 생성됩니다. 각 문서를 열면 엔드포인트별 파라미터·응답·예시를 볼 수 있습니다.
G7 의 REST API 레퍼런스입니다. 도메인별 문서에는 엔드포인트마다 메서드·URI·인증/권한, 요청 파라미터 표, 응답 필드 표, 실제 호출로 관측한 요청·응답 예시가 실려 있습니다. 아래 공통 규약은 모든 엔드포인트에 동일하게 적용되므로 개별 문서에서 반복하지 않습니다.
문서 작성·갱신 규정은 api-documentation.md 를 참고하세요.
공통 규약
인증
Laravel Sanctum 의 Bearer 토큰 전용입니다(세션 쿠키 인증 미사용).
POST /api/auth/login 또는 POST /api/auth/admin/login 으로 토큰을 발급받아 모든 후속 요청에 실어 보냅니다.
GET /api/me HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}
각 문서의 인증/권한 줄은 다음 네 가지 중 하나입니다.
| 표기 | 의미 |
|---|---|
공개 (인증 불필요) |
토큰 없이 호출 가능 |
optional.sanctum |
토큰이 있으면 회원, 없으면 비회원으로 처리 (둘 다 접근 가능) |
auth:sanctum |
유효한 토큰 필수 (없거나 만료 시 401) |
auth:sanctum + admin + permission:{키} |
토큰 + 관리자 + 해당 권한 필요 (권한 부족 시 403) |
로케일
응답 메시지의 언어는 ① 로그인 사용자의 users.language → ② Accept-Language 헤더 →
③ 시스템 기본값(config('app.locale')) 순으로 결정됩니다. 지원 언어는 config('app.supported_locales') 를 따릅니다.
응답 봉투
성공·실패 모두 동일한 최상위 구조를 씁니다. 실제 페이로드는 항상 data 안에 들어갑니다.
{
"success": true,
"message": "요청이 성공했습니다.",
"data": { }
}
실패 시 success 는 false 이고, 검증 오류는 errors 에 필드별 메시지 배열로 담깁니다.
{
"success": false,
"message": "입력값이 올바르지 않습니다.",
"errors": {
"email": ["이메일 형식이 올바르지 않습니다."]
}
}
페이지네이션
목록 엔드포인트는 data.data[] 에 항목 배열을, data.pagination 에 페이지 정보를 담습니다.
요청은 page 와 per_page 쿼리 파라미터로 제어합니다.
{
"pagination": {
"current_page": 1,
"last_page": 4,
"per_page": 25,
"total": 87,
"from": 1,
"to": 25,
"has_more_pages": true
}
}
일부 목록은 data.abilities 에 컬렉션 레벨 권한(can_create, can_delete 등)을 함께 반환합니다.
화면의 버튼 노출 여부를 이 값으로 판정하세요.
공통 에러 상태코드
| 상태코드 | 의미 | 발생 조건 |
|---|---|---|
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
| 403 | Forbidden | 엔드포인트가 요구하는 권한이 없는 경우 |
| 404 | Not Found | 대상 리소스가 없거나 접근 범위를 벗어난 경우 |
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (errors 에 필드별 메시지) |
| 428 | Precondition Required | 본인인증(IDV)이 선행되어야 하는 경우 |
428 응답은 error_code: "identity_verification_required" 와 함께 verification 객체를 반환합니다.
클라이언트는 이 값으로 본인인증 화면을 띄운 뒤 원래 요청을 재시도합니다.
{
"success": false,
"error_code": "identity_verification_required",
"message": "본인인증이 필요합니다.",
"verification": { }
}
자산 URL 이중 모드
정적 파일 확장자(.js / .css / .json)로 끝나는 동적 엔드포인트는 확장자 없는 형태를 함께 제공합니다.
아래 문서에 실린 URI 는 확장자 형태를 기준으로 표기하지만, 각 엔드포인트는 대응하는 확장자 없는 형태로도
동일한 응답·동일한 권한 가드로 호출할 수 있습니다.
이유는 서버 설정입니다. nginx/Apache 의 표준적 정적 최적화 블록은 URL 마지막 확장자로 분기하며,
nginx 에서 정규식 location 은 프리픽스 location 보다 먼저 매칭되므로 try_files ... /index.php 폴백이
실행될 기회가 없습니다. 그런 환경에서는 확장자 붙은 동적 응답이 PHP 에 도달하지 못하고 404 가 됩니다.
location ~* \.(js|css|json)$ { expires max; access_log off; }
| 확장자 형태 | 확장자 없는 형태 | 변환 규칙 |
|---|---|---|
/api/templates/{id}/routes.json |
/api/templates/{id}/routes |
접미사 제거 |
/api/layouts/{tpl}/{layout}.json |
/api/layouts/{tpl}/{layout} |
접미사 제거 |
/api/modules/bundle.js |
/api/modules/bundle/js |
접미사를 경로 세그먼트로 (js/css 구분이 필요) |
/api/templates/assets/{id}/js/a.js |
/api/templates/assets/{id}?file=js/a.js |
파일 경로를 file 쿼리로 (경로가 곧 파일명이라 제거 불가) |
file 쿼리 형태가 안전한 이유는 nginx 의 location 정규식이 쿼리스트링을 제외한 경로에만 매칭되기 때문입니다.
확장자 없는 형태에도 경로 탈출 방어와 확장자 화이트리스트가 동일하게 적용됩니다.
두 형태는 모두 영구 유지됩니다. 확장자 형태를 제거하면 URL 을 하드코딩한 서드파티 확장이 깨집니다.
어느 형태를 쓸지는 서버 환경에 따라 결정되며, 다음 프로브 엔드포인트로 판정합니다.
| 메서드 | URI | 인증/권한 | 설명 |
|---|---|---|---|
| GET | /api/system/asset-probe.js |
공개 (인증 불필요) | 확장자 형태 프로브 |
| GET | /api/system/asset-probe |
공개 (인증 불필요) | 대조군 |
두 URL 을 브라우저에서 쌍으로 요청합니다(서버측 loopback curl 은 vhost·프록시 체인을 우회해 오판합니다).
응답은 application/javascript 이며 본문에 매직 토큰 G7_ASSET_PROBE_OK 를 담습니다. DB 에 접근하지 않고
Cache-Control: no-store 로 캐시되지 않습니다.
asset-probe.js |
asset-probe |
판정 |
|---|---|---|
| 성공 | 성공 | 확장자 형태 사용 가능 |
| 실패 | 성공 | 정적 블록 가로채기 확정 — 확장자 없는 형태 사용 |
| 실패 | 실패 | 모드 문제가 아님 (PHP/라우팅 장애) |
성공 판정은 상태코드가 아니라 본문의 매직 토큰과 Content-Type 으로 합니다. 상태코드만 보면 "404 대신 200 + 에러 HTML" 이나 catch-all 200 페이지를 반환하는 설정에서 영원히 오판합니다.
코어 API 레퍼런스
- 문서 수: 35 · 엔드포인트 수: 291
| 문서 | 도메인 | 엔드포인트 |
|---|---|---|
| activity-logs.md | activity-logs |
3 |
| attachment.md | attachment |
1 |
| attachments.md | attachments |
4 |
| auth.md | auth |
15 |
| avatar.md | avatar |
2 |
| broadcasting.md | broadcasting |
1 |
| changelog.md | changelog |
1 |
| core-update.md | core-update |
2 |
| dashboard.md | dashboard |
5 |
| extensions.md | extensions |
3 |
| identity.md | identity |
27 |
| language-packs.md | language-packs |
15 |
| layouts.md | layouts |
2 |
| license.md | license |
1 |
| locales.md | locales |
1 |
| me.md | me |
3 |
| menus.md | menus |
10 |
| modules.md | modules |
25 |
| notification-channels.md | notification-channels |
1 |
| notification-definitions.md | notification-definitions |
5 |
| notification-logs.md | notification-logs |
3 |
| notification-templates.md | notification-templates |
4 |
| notifications.md | notifications |
14 |
| password.md | password |
1 |
| permissions.md | permissions |
1 |
| plugins.md | plugins |
27 |
| profile.md | profile |
4 |
| roles.md | roles |
7 |
| schedules.md | schedules |
12 |
| search.md | search |
1 |
| seo.md | seo |
5 |
| settings.md | settings |
15 |
| templates.md | templates |
57 |
| users.md | users |
12 |
| verify-password.md | verify-password |
1 |
확장 API 레퍼런스
각 확장이 자신의 API 문서를 소유합니다. 아래 표는 자동 생성됩니다.
- 확장 수: 13 · 엔드포인트 수: 390
| 확장 | 유형 | API 문서 목차 | 문서/엔드포인트 |
|---|---|---|---|
gnuboard7-hello_module |
모듈 | docs/api/ | 1 / 7 |
sirsoft-board |
모듈 | docs/api/ | 10 / 80 |
sirsoft-ecommerce |
모듈 | docs/api/ | 33 / 231 |
sirsoft-page |
모듈 | docs/api/ | 2 / 17 |
sirsoft-ckeditor5 |
플러그인 | docs/api/ | 2 / 2 |
sirsoft-gdpr |
플러그인 | docs/api/ | 4 / 15 |
sirsoft-marketing |
플러그인 | docs/api/ | 2 / 2 |
sirsoft-message_bizppurio |
플러그인 | docs/api/ | 6 / 12 |
sirsoft-pay_kginicis |
플러그인 | docs/api/ | 5 / 22 |
sirsoft-pay_nhnkcp |
플러그인 | docs/api/ | 0 / 0 |
sirsoft-pay_nicepayments |
플러그인 | docs/api/ | 0 / 0 |
sirsoft-verification_kginicis |
플러그인 | docs/api/ | 1 / 1 |
sirsoft-verification_nhnkcp |
플러그인 | docs/api/ | 1 / 1 |