🔐로그인하면 문서 작성, 프로젝트 게시, ZIP 기반 버전 업로드, 브랜치 생성 기능을 사용할 수 있습니다. 로그인하러 가기

서버측 본문 정제와 게시 화면 스타일 (v10에 없는 것)

v10.5.1에 config/t2_sanitize.php와 T2Esanhtml이 없는 실측 근거, 그누보드5·라이믹스 CMS 필터의 위치, 스탠데얼론에서 정제를 붙일 방법, v10의 content.css 자동 삽입 방식과 수동 링크가 정답인 이유, v11과의 차이표

Answer Summary

v10.5.1에 config/t2_sanitize.php와 T2Esanhtml이 없는 실측 근거, 그누보드5·라이믹스 CMS 필터의 위치, 스탠데얼론에서 정제를 붙일 방법, v10의 content.css 자동 삽입 방식과 수동 링크가 정답인 이유, v11과의 차이표

소유자 👤 T2WIKI 편집
브랜치 ⎇ main
리비전 r2
작성자 T2WIKI 편집
수정 2026.09.28
요약 정정 (2026-09-28, T2WIKI 편집): 존재하지 않는 문서로 향하던 내부 링크를 실제 문서로 재지정했습니다(N-08-admin-console-guide→N-02-admin-console-guide.md, N-06-rhymix-integration-guide→N-05-cms-adapter-and-data-paths.md). 원문은 리비전 이력에 남아 있습니다.
v10CMSt2본문config입니다v11js

서버측 본문 정제와 게시 화면 스타일 (v10에 없는 것)

이 문서는 2026-09-28에 새로 썼습니다.
이 문서의 존재 이유가 "없는 것을 알려 주는 것"입니다.
v10을 쓰는 분이 v11 문서를 읽으면 존재하지 않는 기능을 안내받습니다. 그 구분을 실측으로 확정하기 위해 썼습니다. 여기 적힌 "없다"는 모르는 게 아니라 확인한 결과입니다.

0. 먼저 결론 3줄

  1. v10.5.1에는 서버측 본문 정제 진입점이 없습니다. config/t2_sanitize.php 는 404, T2Esanhtml 같은 심볼은 173개 php/js/inc 파일에 0건입니다.
  2. 그누보드5·라이믹스 위에서는 CMS 자체 필터가 방어선입니다. 단 T2Editor가 그 필터를 부르는 코드는 v10에 없습니다 — CMS가 자기 경로에서 도는 것이지, T2Editor 본문 저장 지점에서 거르는 것이 아닙니다.
  3. v10의 게시 화면 스타일 해법은 수동 <link> 입니다. 본문 끝에 자동 삽입되는 css/content.css 링크가 CMS 필터에게 지워질 수 있기 때문입니다. v11의 ?bundle=content·format=js 로더는 v10에 없습니다.

1. 서버측 본문 정제 — v10에 없다

1-1 · 실측 (추측 아님)

releases/v10.5.1 recursive tree = blob 290 / tree 59, truncated=false. 그중 php 86개 + js 86개 + inc 1개 = 173개 파일을 100% 로컬로 받아 심볼을 전수 검색했다.

검색 범위 명시: 290개 중 나머지 117개는 json 76 · css 19 · txt 5 · htaccess 3 · html 3 · woff2 2 · wasm 3 · bin 2 · xml 1 · 확장자 없음 3 이며 심볼 검색에서 제외했습니다. .inc 1건(extend/php/t2_first_run_setup_full.inc)은 포함했고 10개 심볼 전부 0건입니다. 제외한 파일 종류에 PHP 심볼이 있을 수 없다는 것은 근거가 아니라 가정입니다 — §10-3 참조.
확인 대상결과
config/t2_sanitize.phpHTTP 404 (파일이 없음)
T2Esanhtml0 파일
T2Esanclean0 파일
T2Esanregisterprovider0 파일
t2_sanitize (문자열 전체)0 파일
config/t2_hard_config.phpHTTP 404
config/t2_cms_security.phpHTTP 404
docs/ 디렉터리없음 (S2 guide.txt §10이 안내하는 docs/content-sanitizer.md 등 전부 v10에 없음)

1-2 · getSanitizedContent() 이름은 오해를 부릅니다

이 이름 때문에 "브라우저에서라도 거르겠구나"로 읽기 쉽습니다. 37줄을 전부 읽었습니다 (js/core.js:2783-2819). 하는 일은 이 4가지뿐입니다.

js
// js/core.js:2789-2795 tmp.querySelectorAll('p[data-t2-boundary], p[data-t2-generated]').forEach((p) => { p.removeAttribute('data-t2-boundary'); p.removeAttribute('data-t2-generated'); p.removeAttribute('contenteditable'); }); tmp.querySelectorAll(':scope > p[contenteditable]').forEach((p) => p.removeAttribute('contenteditable')); tmp.querySelectorAll('br[data-t2-placeholder]').forEach((br) => br.removeAttribute('data-t2-placeholder')); // js/core.js:2804-2811 에디터 placeholder로 쓰는 ZWSP(U+200B)/BOM(U+FEFF)만 제거 // js/core.js:2814-2818 속성 없는 빈 <p></p>를 <p><br></p>로 치환
하는 일하지 않는 일
에디터 내부 전용 경계 표식 제거<script> 제거
contenteditable 속성 제거onerror·onclick 같은 이벤트 속성 제거
placeholder용 ZWSP/BOM 제거javascript: URL 차단
빈 문단 정규화<iframe>·<object> 제거

주석에도 이유가 적혀 있습니다: "빈 줄 자체는 의미가 있으므로 <p><br></p> 형태로 보존한다", "ZWNJ(U+200C)·ZWJ(U+200D)는 페르샤어 형태/복합 이모지 등에 실제 의미가 있으므로 사용자 본문에서 제거하면 안 된다." ⇒ 본문 내용을 정리하는 함수이지, 보안을 하는 함수가 아닙니다.

1-3 · 제출(저장) 경로에서 제거하는 것은 클래스 1개뿐

editor.core.php:1768-1838 이 폼 제출 직전 본문을 정리하는 전부입니다. remove() 는 3곳 나오는데 2곳은 표 래퍼 DOM 이동이고, 본문에서 제거하는 것은 하나:

php
// editor.core.php:1798 tempDiv.querySelectorAll('.t2-move-controls').forEach(function (ctrl) { ctrl.remove(); }); // :1837 document.getElementById({$js_id}).value = finalContent;

.t2-move-controls 는 에디터 UI 손잡이입니다. 사용자 본문에서 하나도 제거되지 않습니다.

1-4 · 그럼 방어는 전무합니까 — 아닙니다

비대칭이 정확합니다. v10에도 확실히 있는 방어들입니다.

방어근거 (S3 실측)
iframe 허용 호스트 목록config/t2_config.php:304-305 T2EDITOR_ALLOWED_IFRAME_DOMAINS + :328-338 t2editor_get_allowed_iframe_domains() · 브라우저 검증 js/core.js:4659-4660 · 관리자 저장값 승격 extend/php/t2admin_settings_loader.php:52-57
같은 출처(Origin) 검사config/upload_config.php:229-255 check_request_origin() — 변경 요청에 Origin 이 없으면 거부, 역방향 프록시 뒤의 X-Forwarded-Proto 를 반영. 호출처 10곳 (업로드·프록시·iframe·최초설치 API 등)
업로드 파일 시그니처 검사config/upload_config.php:28-33 주석: "server-side identification uses signatures so Fileinfo is not required"
업로드 확장자 허용목록 (SVG·ICO 제외)config/upload_config.php:54-75
파일명 정제config/upload_config.php:258 sanitize_original_name() — NUL·경로 구분자·제어 문자 제거, 길이 제한
NSFW 이미지 필터vendor/nsfwjs/ 번들 + config/nsfw_api_browser.{js,php} (S1 10.4.0: 외부 서버 의존 제거)

⇒ 정확한 서술은 "v10에 보안 기능이 없다"가 아니라 「URL·업로드·Origin 축은 방어가 있고, 본문 HTML 전체를 걷어내는 축만 비어 있다」 입니다.


2. 그누보드5·라이믹스 위에서는 누가 거릅니다

2-1 · ⚠ 여기서 과장하지 않기

S2 guide.txt §3.4 와 §4.4 는 v11이 그 CMS 필터에 연결했다는 기록입니다 — 각 행 끝에 ← 11.0 에서 연결 이라고 붙어 있습니다. 그래서 v10에 대해 정확한 서술은 이렇습니다.

서술판정
"그누보드5는 clean_xss_tags() 를 제공한다"✅ — CMS가 제공하는 함수입니다 (T2Editor와 무관하게 그누보드5에 존재)
"라이믹스는 HTMLFilter::clean() (HTMLPurifier) 을 제공한다"✅ — CMS가 제공하는 클래스입니다
"v10 T2Editor가 그 필터를 본문 저장 지점에서 부른다"❌ 틀렸습니다. clean_xss_tags 0건, HTMLFilter 0건, HTMLPurifier 는 plugin/collab/collab_number.core.php:118 주석 1건뿐입니다

⇒ 정확한 문장: "그누보드5와 라이믹스는 CMS 자신의 저장·출력 경로에서 HTML 필터를 돌립니다. T2Editor 본문은 그 필터를 통과합니다. 그것은 CMS가 하는 일이지, v10 T2Editor가 하는 일이 아닙니다. v10 배포판에는 이 필터를 T2Editor 저장 지점에서 부르는 코드가 없습니다."

get_safe_filename · check_request_origin(CMS판) · get_token · spamfilter_denied_word · Session::createToken · FilenameFilter · FileContentFilter 도 v10 173파일 기준 0건입니다. MediaFilter 만 integration/cms/rhymix/iframe_autoregister.php:17,58 2곳 — 그것도 본문 정화가 아니라 동영상 iframe 등록용입니다.

2-2 · 배치별 실질 방어선

배치본문 정제실질 방어선사용자가 할 수 있는 것
그누보드5T2Editor에 없음CMS의 clean_xss_tags() (CMS 경로) + T2Editor의 iframe allowlistCMS 필터를 신뢰하되, CMS 필터가 걷지 않는 속성을 알 수 없음을 인정
라이믹스T2Editor에 없음CMS의 HTMLFilter::clean() (HTMLPurifier)위와 같음
스탠드얼론T2Editor에 없음CMS도 없음 ⇒ 이 축이 완전히 비어 있음직접 정제를 붙여야 함 (§2-3)
워드프레스T2Editor에 없음CMS 필터 사용 가능하나 어댑터 미사용CMS 필터 + 별도 확인

2-3 · 스탠드얼론에서 정제를 붙일 수 있는 지점

"붙일 지점이 배포판에 없다"는 게 정확합니다. v10 배포판에 정제 모듈이 없으니까요. 그러나 저장 지점은 있습니다 — CMS가 없으면 그 지점은 사용자 자신의 저장 코드입니다.

php
// ① 라이브러리를 먼저 넣습니다 (v10 배포판에 없으므로 직접 추가) require_once __DIR__ . '/sanitize.php'; // 예: HTML Purifier // ② 저장 직전에 한 번 거릅니다 $body = $_POST['content'] ?? ''; $purifier = new HTMLPurifier(array( 'HTML.Allowed' => 'p,br,strong,em,ul,ol,li,a[href|title],img[src|alt],table,thead,tbody,tr,td,th,pre,code,blockquote', 'URI.AllowedSchemes' => array('http' => true, 'https' => true, 'mailto' => true, 'data' => true), )); $safe = $purifier->purify($body); save_article($safe);

필수 요건 3가지:

  1. 허용목록 방식으로 하십시오. 차단목록(<script> 지우기) 방식은 새 벡터를 못 막습니다.
  2. URL 스킴을 막으십시오. javascript:, vbscript:, data:text/html, blob:, file: 은 href·src 어디에서도 통과시키지 않습니다.
  3. <iframe> 을 쓴다면 호스트를 허용목록으로 제한하십시오. v10 T2Editor의 iframe allowlist와 같은 목록을 쓰는 것이 가장 일관됩니다(config/t2_config.php:304-340).
⚠ 경고 하나: 배포판을 갱신할 때 이 코드는 덮어써지지 않습니다 — sanitize.php 는 배포판에 없기 때문입니다. 다만 config/ 아래에 두면 유지보수 대상이 헷갈리니, CMS 바깥(예: includes/)에 두십시오.

2-4 · 라이선스가 이 공백을 사용자에게 넘긴다는 점

readme.txt:42 — T2 서비스는 "있는 그대로(AS-IS)" 제공되며 어떠한 책임도 지지 않습니다. readme.txt:51 — 코드와 호환 구조는 사전 공지 없이 변경될 수 있습니다.

⇒ "다음 판본에 필터가 들어오겠다"는 기대가 계약으로 보장되지 않습니다. 위 §2-3 처럼 직접 붙이시는 편이 안전합니다.


3. ⭐ 게시 화면 스타일 — v10의 정답은 수동 링크입니다

3-1 · v10은 본문 끝에 링크를 자동으로 붙입니다

php
// editor.core.php:1830-1836 var contentStyle = '<link href="' + {$js_editor_url} + '/css/content.css" rel="stylesheet">'; var finalContent = tempDiv.innerHTML; // Append content.css only when absent so repeated edit/publish cycles do not accumulate duplicate links. if ((finalContent.indexOf('t2-media-block') !== -1 || finalContent.indexOf('t2-table') !== -1 || finalContent.indexOf('t2-code-block') !== -1 || finalContent.indexOf('t2-drawing-block') !== -1) && finalContent.indexOf('/css/content.css') === -1) { finalContent += contentStyle; }

읽으면 알 수 있는 세 가지:

  1. 본문 끝(+=)에 붙습니다. <head> 가 아닙니다.
  2. 해당 블록이 있을 때만 붙습니다(미디어·표·코드·그림 블록). 순수 텍스트 글에는 안 붙습니다.
  3. 이미 있으면 안 붙입니다 (반복 저장 시 누적 방지). ⇒ CMS가 링크를 지워버리면 다음 저장부터 매번 다시 붙습니다.

3-2 · v10의 CMS 어댑터는 head 에 아무것도 넣지 않습니다

head_script · head_tag 같은 주입 지점을 173개 파일(php 86 + js 86 + inc 1) 전수 검색했습니다.

검색어결과
head_script0건
</head>6건 — 전부 독립 문서(관리자 페이지·동영상 플레이어·PDF 뷰어·오디오 플레이어·내보내기)
content.css4건 — 3건이 위 editor.core.php:1830-1836, 1건이 plugin/image/hooks.js:10 주석

⇒ 그누보드5 어댑터도 라이믹스 어댑터도 게시 페이지 <head> 에 링크를 넣지 않습니다. 10.5.1 코드까지 확인했습니다.

3-3 · 그래서 수동 링크가 v10의 정답입니다

CMS가 t2editor 저장 지점을 지나는 경로에는 보통 HTML 필터가 붙습니다. 그 필터는 남의 글에 외부 스타일시트를 심지 못하게 막습니다 — 그것이 정상 동작입니다. 그래서:

  • v10: 본문 끝의 <link> 가 CMS 필터에게 지워질 수 있음 → 스킨/레이아웃에 직접 <link> 를 넣는 것이 v10의 정답입니다. (N-03-migration-v9-to-v10.md 및 기존 위키의 content.css 수동 링크 안내가 유효한 이유입니다. "낡은 지시"가 아닙니다.)
  • v11: scope·format=js 로더·T2Econtentstyleattach() 로 이 문제를 구조적으로 해결. v10에는 없습니다.
⚠ 정확히 무슨 일이 벌어지느냐는 CMS·스킨·필터 설정에 따라 다르고, v10 배포판 안에서는 그 필터를 직접 볼 수 없습니다. 그래서 "무조건 지워진다"고 단정하지 않습니다. 확인 없는 말로 바꾸면: "지워질 수 있습니다. 그래서 v10에서는 수동 링크를 안전 장치로 두십시오."

3-4 · v10의 게시 화면이 밋밋할 때 확인할 것

증상먼저 볼 것
표 셀이 붙어 보인다css/content.css 링크가 살아 있는지
이미지·영상 블록 레이아웃이 깨진다동일
아무 변화가 없다링크가 <head> 가 아니라 본문 끝에 있다 — 브라우저는 이를 허용하지만 CMS 필터가 지울 수 있음
링크가 매번 새로 붙는다3-1 의 3번 — 지워졌다는 뜻입니다

4. ⭐ v10 과 v11 차이표 — v10 사용자에게 v11 안내를 하지 않기 위한 표

4-1 · v10에 없는 것 (전부 실측)

v11 계약 (S2 guide.txt)v10.5.1 실측v10 사용자가 겪는 일
§6 서버측 XSS 필터<br>T2Esanhtml / T2Esanclean / T2Esanregisterprovider / config/t2_sanitize.php없음 — config/t2_sanitize.php 404, 심볼 0/173파일본문 저장 지점에서 거를 코드가 없음. §2 참고
§7 발행 본문 스타일 번들<br>T2Econtentstyletag / T2Econtentstyleattach / ?bundle=content&scope=&format=&v=지문없음 — 심볼 0/173파일, bundle·scope 인자 0건§3 참고 (수동 링크가 정답)
§2.5 설치 진단 화면<br>endpoints/install-check.php404 — endpoints/ 디렉터리 자체가 없음URL 자동 감지가 빗나갔을 때 볼 공식 진단 화면이 배포판에 없음
§2.6·§5 하드 설정 계약<br>config/t2_hard_config.php / admin/sections/404 / admin/sections/ 없음설정은 관리자 화면 + DB 행. 새 설정 키를 알려 주는 장치도 없음
§8 호스트 보안 브리지<br>T2Ehostsecuritysnapshot() / config/t2_cms_security.php404 / 심볼 0건기능 손실이 아니라 관측 가능성 손실 — CMS가 무엇을 방어하는지 조회할 수 없음
§6.5 sanitize 설정 블록t2_sanitize 0건켜고 끌 설정 자체가 없음

4-2 · v10의 t2_css_min.php 는 다른 프로그램입니다

S2 guide.txt §7 은 endpoints/t2_css_min.php?bundle=content&format=js 로 안내합니다. v10에는 endpoints/ 가 없습니다. v10의 t2_css_min.php 는 단일 CSS 파일 축소기입니다:

php
// t2_css_min.php:16 $file = isset($_GET['f']) ? $_GET['f'] : '';

?f= 인자 하나만 받습니다. bundle·scope·format 인자가 없고 지문(v=)도 없습니다. ⇒ v10 사용자에게 ?bundle=content 를 안내하면 400이 나옵니다(t2_css_min.php:20-23).

4-3 · v11에 있고 v10에도 있는 것 (경계 표시)

v10근거
iframe 허용 호스트 allowlist✅ 있음config/t2_config.php:304-340
다크 모드✅ 있음css/dark.css (6,476 B)
4종 CMS 어댑터✅ 있음integration/cms/adapters/ 4종
4언어 i18n✅ 있음locales/ + plugin/*/locales/ + js/i18n.js
접근성 속성✅ 부분aria-label 다수, editor.core.php:196 으로 다국어화

5. 실무 체크리스트

관련 문서: N-02-admin-console-guide.md · N-05-cms-adapter-and-data-paths.md · 74-security-guide.md · 75-upload-and-media-pipeline-guide.md

스탠데얼론이라면

  1. 본문 저장 지점에 HTML 정제를 붙이십시오(§2-3). 지금 안 붙이면 그 축은 비어 있습니다.
  2. 게시 페이지 스킨 <head> 에 css/content.css 를 수동으로 링크하십시오(§3-3).
  3. 배포판 루트에 readme.txt 가 있고 유효일이 2026년 6월 29일인지 확인하십시오(07-license-guide.md). N-07 은 채워지지 않은 번호입니다 — 이 항목에 쓸 별도 문서는 없습니다.

그누보드5·라이믹스라면

  1. CMS 필터가 켜져 있는지 CMS 설정에서 확인하십시오. T2Editor 화면에는 그 정보가 없습니다.
  2. 게시 스킨 <head> 에 css/content.css 를 수동 링크하십시오.
  3. iframe이 안 보이면 관리자 도메인 설정에서 해당 플랫폼을 체크하십시오(N-02-admin-console-guide.md).
  4. v10.5.1로 올리십시오 — 10.5.0의 CMS 인증 결함이 있습니다.

어디서든 하지 말아야 할 것

  • ❌ v11 문서의 T2Econtentstyletag()·T2Econtentstyleattach() 안내를 v10 서버에 쓰지 말 것 — 0건입니다.
  • ❌ endpoints/t2_css_min.php?bundle=content 을 쓰지 말 것 — 404/400입니다.
  • ❌ T2Esanhtml() 를 부르지 말 것 — 정의가 없습니다(fatal).
  • ❌ "v10은 서버에서 XSS를 막아 준다"고 쓰지 말 것 — 그 문장은 v11의 설명입니다.

N-xx 번호 체계 메모 (2026-09-28 확정)

이 위키의 N- 번호는 2026-09-28에 등록부를 확정하면서 붙였습니다. 확정된 신규 문서는 4건입니다.

번호슬러그내용
N-02N-02-admin-console-guide.md통합 관리 페이지 가이드
N-03N-03-migration-v9-to-v10.mdv9 → v10 이관 안내서
N-05N-05-cms-adapter-and-data-paths.mdCMS 어댑터와 데이터 저장 위치
N-06N-06-server-sanitize-and-content-style.md서버측 본문 정제와 게시 화면 스타일

N-01 · N-04 · N-07은 비어 있습니다. 번호를 미리 예약하면 그 번호가 "예정된 문서가 있다"는 뜻으로 읽히고, 실제로 그런 문서가 생기지 않으면 독자가 찾다 포기합니다. 필요해지는 시점에 그때 번호를 쓰십시오. 이전 문안에서 [[N-02-admin-console-guide.md]]·[[N-05-cms-adapter-and-data-paths.md]]처럼 가리키던 링크는 이 등록부 기준으로 교정되었습니다(N-08 → N-02, 라이믹스 경로는 N-05에 포함).

6. 내가 틀릴 수 있는 것 3가지

  1. "CMS 필터가 v10 본문을 거른다"는 서술을 §2-1 에서 좁혔지만, CMS 내부 동작은 v10 배포판 밖입니다. 그누보드5·라이믹스 소스를 이 세션에서 읽지 않았습니다. "CMS가 자기 경로에서 필터를 돈다"는 CMS의 알려진 성질이지, 제가 v10과 결합해 재현한 결과가 아닙니다. CMS 버전에 따라 필터가 꺼져 있거나 정책이 다를 수 있습니다.
  1. §3-3 의 "수동 링크가 v10의 정답"은 코드에서 확인된 것(어댑터가 head 에 주입하지 않는다)과 운영 판단이 합쳐진 결론입니다. 본문 끝 링크가 실제 CMS에서 살아남는지는 CMS·스킨·필터 설정에 달려 있고, 저는 그 CMS 위에서 재현하지 못했습니다. 그래서 "무조건 지워진다"가 아니라 "지워질 수 있으니 안전 장치로 두라"고 적었습니다.
  1. §1 의 심볼 0건은 releases/v10.5.1 290 blob 중 php 86 + js 86 + inc 1 = 173개 파일 기준입니다. 나머지 117개(json 76 · css 19 · txt 5 · htaccess 3 · html 3 · woff2 2 · wasm 3 · bin 2 · xml 1 · 확장자 없음 3)는 심볼 검색에서 제외했습니다. 제외 근거는 가정입니다(이미지에 심볼이 있을 수는 없지만, 텍스트 파일에 주석으로 새어 있을 가능성은 배제하지 않습니다) — [확인 필요]. 그리고 배포 ZIP이 git tree와 같은 파일 집합이라는 보장은 없습니다. ZIP에 v11 스타일의 추가 파일이 들어 있을 가능성은 배제하지 않았습니다. download_url 오염 금지 규칙 때문에 열지 않았습니다.
이 문서가 링크하는 문서 · 6
백링크 · 7
관련 문서
문서 연결 지도

이 문서를 중심으로 들어오는 링크와 나가는 링크를 한 번에 볼 수 있습니다.

전체 보기
현재 문서 백링크 나가는 링크 양방향 연결
이 브랜치의 리비전
T2WIKI · 기술 통합 위키 & 프로젝트 허브 · 나무위키 + Markdown 완벽 지원 · SQLite · PHP 8.2 · 소개 · 문법 안내