미니 프로파일러가 개인 메시지 편집 시 초안 저장 시도에 403 오류를 유발합니다

v2026.8.0-latest +28(어떤 v2026.7 버전에서)로 업데이트한 이후, “개발자” 권한으로 개인 메시지(PM)를 편집할 때 다음과 같은 화면이 보입니다.

{52B46C2B-8166-4B02-92C9-936558B256BA}

브라우저 콘솔에서는 다음 내용이 표시됩니다:

20:54:29.425 includes.js?v=e0bcc9ce0ae3bb5d6b736b6f282f601f:840  POST https://dietpi.com/forum/drafts.json 403 (Forbidden)
(anonymous) @ includes.js?v=e0bcc9ce0ae3bb5d6b736b6f282f601f:840
send @ jquery.js:9940
ajax @ jquery.js:9521
i @ ajax.js:233
Z @ ajax.js:252
save @ draft.js:37
saveDraft @ composer.js:1535
_saveDraft @ composer.js:1963
_shouldSaveDraft @ composer.js:1999
sendEvent @ cache-DtDz7X5V.js:471
(anonymous) @ cache-DtDz7X5V.js:665
invoke @ index.js:262
flush @ index.js:180
flush @ index.js:334
_end @ index.js:762
(anonymous) @ index.js:499
Promise.then
(anonymous) @ index.js:18
flush @ index.js:29
_scheduleAutorun @ index.js:928
_end @ index.js:768
(anonymous) @ index.js:499
Promise.then
(anonymous) @ index.js:18
flush @ index.js:29
_scheduleAutorun @ index.js:928
_end @ index.js:768
(anonymous) @ index.js:499
Promise.then
(anonymous) @ index.js:18
flush @ index.js:29
_scheduleAutorun @ index.js:928
_end @ index.js:768
(anonymous) @ index.js:499
Promise.then
(anonymous) @ index.js:18
flush @ index.js:29
_scheduleAutorun @ index.js:928
_end @ index.js:768
(anonymous) @ index.js:499
Promise.then
(anonymous) @ index.js:18
flush @ index.js:29
_scheduleAutorun @ index.js:928
_end @ index.js:768
(anonymous) @ index.js:499
Promise.then
(anonymous) @ index.js:18
flush @ index.js:29
_scheduleAutorun @ index.js:928
_ensureInstance @ index.js:919
ensureInstance @ index.js:731
scheduleRevalidate @ index-DaCZcoCY.js:4146
dirtyTag @ index.js:229
dirtyTagFor @ index.js:848
setter @ index.js:871
set @ cache-DtDz7X5V.js:1886
_setProp @ property_set-n9lAuzYY.js:47
set @ property_set-n9lAuzYY.js:42
(anonymous) @ set_properties-D2_u0phU.js:94
changeProperties @ cache-DtDz7X5V.js:802
(anonymous) @ set_properties-D2_u0phU.js:88
setProperties @ observable.js:30
_hydrate @ store.js:446
_hydrateFindResults @ store.js:106
(anonymous) @ store.js:124
Promise.then
find @ store.js:123
(anonymous) @ composer.js:1110
mu @ rsvp-ziM3qQyS.js:412
(anonymous) @ rsvp-ziM3qQyS.js:468
invoke @ index.js:264
flush @ index.js:180
flush @ index.js:334
_end @ index.js:762
end @ index.js:565
_runExpiredTimers @ index.js:869
setTimeout
setTimeout @ index.js:39
_installTimerTimeout @ index.js:912
_reinstallTimerTimeout @ index.js:896
_later @ index.js:829
later @ index.js:652
next @ index.js:562
_triggerAction @ d-button.gts:212
click @ d-button.gts:166
20:54:31.380 includes.js?v=e0bcc9ce0ae3bb5d6b736b6f282f601f:840  POST https://dietpi.com/forum/drafts.json 403 (Forbidden)
(anonymous) @ includes.js?v=e0bcc9ce0ae3bb5d6b736b6f282f601f:840
send @ jquery.js:9940
ajax @ jquery.js:9521
i @ ajax.js:233
Z @ ajax.js:252
save @ draft.js:37
saveDraft @ composer.js:1535
_saveDraft @ composer.js:1963
invoke @ index.js:264
flush @ index.js:180
flush @ index.js:334
_end @ index.js:762
end @ index.js:565
_runExpiredTimers @ index.js:869
setTimeout
setTimeout @ index.js:39
_installTimerTimeout @ index.js:912
_scheduleExpiredTimers @ index.js:892
_runExpiredTimers @ index.js:868
setTimeout
setTimeout @ index.js:39
_installTimerTimeout @ index.js:912
_scheduleExpiredTimers @ index.js:892
_runExpiredTimers @ index.js:868
setTimeout
setTimeout @ index.js:39
_installTimerTimeout @ index.js:912
_scheduleExpiredTimers @ index.js:892
_runExpiredTimers @ index.js:868
setTimeout
setTimeout @ index.js:39
_installTimerTimeout @ index.js:912
_scheduleExpiredTimers @ index.js:892
_runExpiredTimers @ index.js:868
setTimeout
setTimeout @ index.js:39
_installTimerTimeout @ index.js:912
_reinstallTimerTimeout @ index.js:896
_later @ index.js:829
debounce @ index.js:680
debounce @ index.js:713
(anonymous) @ debounce.js:26
debouncedRecalculateHeaderOffset @ glimmer-site-header.gjs:96
_run @ index.js:804
_join @ index.js:783
join @ index.js:605
join @ index.js:152
(anonymous) @ index.js:250
20:54:34.397 7:1 The resource https://dietpi.com/matomo/matomo.js was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally.
20:54:39.457 7:1 The resource https://dietpi.com/matomo/matomo.js was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally.

이 403 오류가 미니 프로파일러(mini profiler)에서 발생하는 것으로 보입니다:

    if (!window.MiniProfiler || !window.MiniProfiler.patchesApplied) {
      var send = XMLHttpRequest.prototype.send;

      XMLHttpRequest.prototype.send = function(data) {
        ajaxStartTime = new Date();
        this.addEventListener("load", function() {
          // responseURL isn't available in IE11
          if (
            this.responseURL &&
            this.responseURL.indexOf(window.location.origin) !== 0
          ) {
            return;
          }
          if (this.__miniProfilerSkipResultsFetch) {
            return;
          }
          // getAllResponseHeaders isn't available in Edge.
          var allHeaders = this.getAllResponseHeaders
            ? this.getAllResponseHeaders()
            : null;
          if (
            allHeaders &&
            allHeaders.toLowerCase().indexOf("x-miniprofiler-ids") === -1
          ) {
            return;
          }
          // should be a string of comma-separated ids
          var stringIds = this.getResponseHeader("X-MiniProfiler-Ids");

          if (stringIds) {
            var ids = stringIds.split(",");
            fetchResults(ids);
          }
        });
        send.call(this, data);
      }; // fetch results after ASP Ajax calls

제 이해 범위 내에서는, drafts.json으로 POST를 보내려 할 때 403을 받는 부분은 send.call(this, data)입니다. 아마 data가 잘못된 값(garbage)이어서 403이 발생하는 것 같습니다. 브라우저에는 CSP나 기타 보안 규칙/헤더가 요청을 차단한다는 정보는 없습니다.

이 문제는 공개 게시글을 편집하거나 대화에 새 개인 메시지를 추가할 때 발생하지 않으며, 개인 메시지 대화에서 게시글을 편집할 때만 발생합니다.

ALT+P 키로 프로파일러를 숨겨도 이 문제가 방지되지 않습니다. 개발자가 아닌 사용자 권한으로 테스트해 보는 것이 좋을 것 같습니다.

이 오류로 인해 데이터가 유효한 JSON이 아니므로, 편집 저장도 실패하는 것으로 추정됩니다.

우리는 공식 Docker 컨테이너를 사용하지만, /forum 서브 경로 뒤에 프록시(Apache2)를 두고 실행하고 있습니다. 이것이 영향을 미치고 있을 가능성이 큽니다.

아, 만약 다른 사람이 이 문제를 겪게 된다면 참고하길 바랍니다. Cloudflare 방화벽의 오탐이었습니다. 구체적으로는 "Cloudflare Managed Ruleset"의 95a84ab1645a49c685648c17761e7a4c 규칙입니다. 저는 Discourse를 위해 이 기본 규칙 세트에서 이미 7번째로 이 규칙을 비활성화해야 했습니다. 이 규칙은 JSON 콘텐츠를, 즉 게시글의 내용을 특정 패턴으로 스캔하기 때문입니다. 저는 셸(그리고 다른) 코드를 자주 작성하거나 붙여넣기 때문에 이러한 규칙들이 작동하게 됩니다.

Discourse는 일반적으로 WAF 필터링 없이도 실행될 수 있으며, 보안 프로세스가 매우 견고하여 WAF가 보호하도록 개발된 유형의 문제를 거의 일으키지 않습니다.

그렇겠죠. 하지만 저는 오작동으로 판단되는 개별 규칙을 비활성화하는 보수적인 접근 방식을 선호합니다. 이 사례는 Discourse 업데이트 후 403 오류가 발생하거나, 일반적으로 그런 문제가 생길 경우 이를 항상 먼저 확인해야 한다는 점을 상기시킵니다.

하지만 동의하는 바입니다. 이 규칙은 Discourse 관련 문제 규칙 7개 중 하나일 뿐이며, 이미 사용하지 않는 소프트웨어에 대해 기본 활성화된 규칙을 도메인 전체에서 많이 비활성화한 상태이므로, 이 규칙을 계속 조정하는 것은 지속적인 유지보수 작업이 됩니다. 따라서 대부분의 관리자에게는 Discourse 호스트/경로의 관리형 WAF 규칙을 아예 비활성화하는 것이 유일한 실용적인 해결책일 가능성이 높습니다.

참고로, 제외해야 하는 규칙 목록을 아래에 제시합니다. 볼 수 있듯이 대부분 게시글 내용 기반의 명령 주입 오작동입니다: