From fd0e2a1cc39041e5a376ff40355ebac882e48382 Mon Sep 17 00:00:00 2001
From: Kijin Sung
Date: Wed, 22 Oct 2025 15:18:15 +0900
Subject: [PATCH 01/16] Fix missing temp directory when admin resizes image
using magick
---
modules/file/file.admin.controller.php | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/modules/file/file.admin.controller.php b/modules/file/file.admin.controller.php
index 759658df2..6235750e2 100644
--- a/modules/file/file.admin.controller.php
+++ b/modules/file/file.admin.controller.php
@@ -389,6 +389,11 @@ class FileAdminController extends File
$result = FileHandler::createImageFile(FileHandler::getRealPath($file->uploaded_filename), $temp_filename, $width, $height, $format, 'fill', $quality);
if (!$result && !empty($config->magick_command))
{
+ $temp_dir = dirname($temp_filename);
+ if (!Rhymix\Framework\Storage::isDirectory($temp_dir))
+ {
+ Rhymix\Framework\Storage::createDirectory($temp_dir);
+ }
$command = vsprintf('%s %s -resize %dx%d -quality %d %s %s %s', [
\RX_WINDOWS ? escapeshellarg($config->magick_command) : $config->magick_command,
escapeshellarg(FileHandler::getRealPath($file->uploaded_filename)),
From f3494e8a11e23ee21bd29606cdb6d1f1e6de0fca Mon Sep 17 00:00:00 2001
From: Kijin Sung
Date: Wed, 22 Oct 2025 15:21:36 +0900
Subject: [PATCH 02/16] Fix failure to convert palette-based PNG to WebP #2608
---
classes/file/FileHandler.class.php | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/classes/file/FileHandler.class.php b/classes/file/FileHandler.class.php
index aa5f6aaba..f80ed9dfd 100644
--- a/classes/file/FileHandler.class.php
+++ b/classes/file/FileHandler.class.php
@@ -728,6 +728,10 @@ class FileHandler
}
elseif ($target_type === 'webp' && function_exists('imagewebp'))
{
+ if (!imageistruecolor($thumb))
+ {
+ imagepalettetotruecolor($thumb);
+ }
$output = imagewebp($thumb, $target_file);
}
else
From 82de42dfc863acfd2c0f0015d03e4057af7abdc6 Mon Sep 17 00:00:00 2001
From: Kijin Sung
Date: Wed, 22 Oct 2025 15:30:14 +0900
Subject: [PATCH 03/16] Fix invalid widget info shown in server environment
page
---
modules/widget/widget.model.php | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/modules/widget/widget.model.php b/modules/widget/widget.model.php
index f9cd09d45..905c18e3e 100644
--- a/modules/widget/widget.model.php
+++ b/modules/widget/widget.model.php
@@ -58,10 +58,9 @@ class WidgetModel extends Widget
$widget = $searched_list[$i];
// Wanted information on the Widget
$widget_info = self::getWidgetInfo($widget);
-
- if(!$widget_info)
+ if (!$widget_info)
{
- $widget_info = new stdClass();
+ continue;
}
// get easyinstall remove url
@@ -101,8 +100,10 @@ class WidgetModel extends Widget
$widgetStyle = $searched_list[$i];
// Wanted information on the Widget
$widgetStyle_info = self::getWidgetStyleInfo($widgetStyle);
-
- $list[] = $widgetStyle_info;
+ if ($widgetStyle_info)
+ {
+ $list[] = $widgetStyle_info;
+ }
}
return $list;
}
From ee48d4efea099817a1d1ef2e44722ed6db3a7531 Mon Sep 17 00:00:00 2001
From: Kijin Sung
Date: Wed, 22 Oct 2025 17:52:30 +0900
Subject: [PATCH 04/16] Fix inconsistent length of ipaddress column #2605
---
modules/member/member.class.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/modules/member/member.class.php b/modules/member/member.class.php
index 32cf596b2..4f932bb53 100644
--- a/modules/member/member.class.php
+++ b/modules/member/member.class.php
@@ -296,11 +296,11 @@ class Member extends ModuleObject
// Add columns for IP address
if(!$oDB->isColumnExists("member", "ipaddress"))
{
- $oDB->addColumn("member", "ipaddress", "varchar", 120, null, false, 'regdate');
+ $oDB->addColumn("member", "ipaddress", "varchar", 60, null, false, 'regdate');
}
if(!$oDB->isColumnExists("member", "last_login_ipaddress"))
{
- $oDB->addColumn("member", "last_login_ipaddress", "varchar", 120, null, false, 'last_login');
+ $oDB->addColumn("member", "last_login_ipaddress", "varchar", 60, null, false, 'last_login');
}
if(!$oDB->isIndexExists("member","idx_ipaddress"))
{
From f536f0f382ca063b90d6e401948d845d25db8f57 Mon Sep 17 00:00:00 2001
From: Kijin Sung
Date: Wed, 22 Oct 2025 17:56:47 +0900
Subject: [PATCH 05/16] Delete message about point reversion, because it is now
customizable per module
---
modules/point/lang/en.php | 4 ++--
modules/point/lang/es.php | 2 +-
modules/point/lang/fr.php | 2 +-
modules/point/lang/ja.php | 2 +-
modules/point/lang/ko.php | 2 +-
modules/point/lang/ru.php | 2 +-
modules/point/lang/tr.php | 2 +-
modules/point/lang/vi.php | 2 +-
modules/point/lang/zh-CN.php | 2 +-
modules/point/lang/zh-TW.php | 2 +-
10 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/modules/point/lang/en.php b/modules/point/lang/en.php
index efc87076e..92d8a959e 100644
--- a/modules/point/lang/en.php
+++ b/modules/point/lang/en.php
@@ -36,7 +36,7 @@ $lang->point_group_ratchet = 'Change on Point Reduction';
$lang->point_group_ratchet_yes = 'Maintain current group if point is reduced';
$lang->point_group_ratchet_no = 'Move to lower group if point is reduced';
$lang->about_point_link_group = 'If you specify level for a specific group, users are assigned into the group when they advance to the level by getting points.';
-$lang->about_module_point = 'You can set point for each module, and modules which don\'t have any value will use the default point. All points will be restored on acting reverse.';
+$lang->about_module_point = 'You can set points for each module, and modules which don\'t have any value will use the default point settings.';
$lang->point_signup = 'Sign Up';
$lang->point_insert_document = 'Writing a post';
$lang->point_delete_document = 'Deleting a post';
@@ -50,7 +50,7 @@ $lang->point_voter = 'Upvoting another person\'s post';
$lang->point_blamer = 'Downvoting another person\'s post';
$lang->point_voter_comment = 'Upvoting another person\'s comment';
$lang->point_blamer_comment = 'Downvoting another person\'s comment';
-$lang->point_download_file_author = 'Downloaded by others (without images)';
+$lang->point_download_file_author = 'Downloaded by others (without images)';
$lang->point_read_document_author = 'Read by others';
$lang->point_voted = 'One\'s post is upvoted';
$lang->point_blamed = 'One\'s post is downvoted';
diff --git a/modules/point/lang/es.php b/modules/point/lang/es.php
index 14300bab0..10226dadd 100644
--- a/modules/point/lang/es.php
+++ b/modules/point/lang/es.php
@@ -21,7 +21,7 @@ $lang->cmd_exp_calc = 'Calcular';
$lang->cmd_exp_reset = 'Restablecer';
$lang->point_link_group = 'Grupo de cambio de nivel';
$lang->about_point_link_group = 'Si especifica nivel para un grupo específico, a los usuarios se les asigna en el grupo cuando se adavnce al nivel por conseguir puntos.';
-$lang->about_module_point = 'Usted puede definir los puntos para cada módulo y los módulos que no tengan ningun valor usarán punto predefinido. Todos los puntos serán restituidos al actuar en forma contraria.';
+$lang->about_module_point = 'Usted puede definir los puntos para cada módulo y los módulos que no tengan ningun valor usarán punto predefinido.';
$lang->point_signup = 'Fecha del';
$lang->point_insert_document = 'Al escribir documento';
$lang->point_delete_document = 'Al borrar documento';
diff --git a/modules/point/lang/fr.php b/modules/point/lang/fr.php
index a16315b3b..0f8833b4c 100644
--- a/modules/point/lang/fr.php
+++ b/modules/point/lang/fr.php
@@ -20,7 +20,7 @@ $lang->cmd_point_recal = 'Restaurer le Point';
$lang->about_cmd_point_recal = 'Tous les points seront recalculés basé seulement sur les points des articles/commentaires/annexes/inscription. Après la restauration, Les membres gagneront le point d\'inscription seulement quand il fait de l\'activité dans le site Web. Utilisez cette fonction seulement quand l\'initialisation complète est necessaire comme le cas de transfert des données etc.';
$lang->point_link_group = 'Changement du Groupe lié à celui du Niveau';
$lang->about_point_link_group = 'Si vous designez un niveau à un groupe particulier, les utilisateur s sont assignés dans le groupe quand ils s\'avancent au groupe en gagnant des points.';
-$lang->about_module_point = 'Vous pouvez configurer les points pour chaque module. Le module qui n\'a pas de valeurs utilisera les points par défaut. Tous les points seront restaurés quand on fait de l\'action inverse.';
+$lang->about_module_point = 'Vous pouvez configurer les points pour chaque module. Le module qui n\'a pas de valeurs utilisera les points par défaut.';
$lang->point_signup = 'Inscription';
$lang->point_insert_document = 'Écrire';
$lang->point_delete_document = 'Supprimer';
diff --git a/modules/point/lang/ja.php b/modules/point/lang/ja.php
index 30d247080..6145f749b 100644
--- a/modules/point/lang/ja.php
+++ b/modules/point/lang/ja.php
@@ -26,7 +26,7 @@ $lang->point_link_group = 'グループ連動';
$lang->point_group_reset_and_add = '設定されたグループを初期化後に新規グループに付与';
$lang->point_group_add_only = '新規グループのみ付与';
$lang->about_point_link_group = 'グループにレベルを指定すると、該当レベルになったときにグループが変更されます。';
-$lang->about_module_point = 'モジュール別にポイントを指定することができますが、指定されていないモジュールには、デフォルトポイントが使用されます。すべてのポイントは、反対の行動の行った場合に元に戻ります。';
+$lang->about_module_point = 'モジュール別にポイントを指定することができますが、指定されていないモジュールには、デフォルトポイントが使用されます。';
$lang->point_signup = '加入';
$lang->point_insert_document = '書き込み作成';
$lang->point_delete_document = '書き込み削除';
diff --git a/modules/point/lang/ko.php b/modules/point/lang/ko.php
index 05344b676..929381c3e 100644
--- a/modules/point/lang/ko.php
+++ b/modules/point/lang/ko.php
@@ -36,7 +36,7 @@ $lang->point_group_ratchet = '포인트 감소 처리 방식';
$lang->point_group_ratchet_yes = '포인트가 감소하더라도 기존 그룹을 유지';
$lang->point_group_ratchet_no = '포인트가 감소하면 하위 그룹으로 이동';
$lang->about_point_link_group = '그룹에 원하는 레벨을 지정하면, 회원의 포인트가 해당 레벨의 포인트에 도달할 때 그룹이 변경됩니다.';
-$lang->about_module_point = '모듈별 포인트를 지정할 수 있으며 지정되지 않은 모듈은 기본 포인트를 이용합니다. 모든 점수는 반대 행동을 하였을 경우 원상복구 됩니다.';
+$lang->about_module_point = '모듈별 포인트를 설정할 수 있습니다. 설정하지 않은 모듈은 기본 포인트 설정을 적용합니다.';
$lang->point_signup = '가입';
$lang->point_insert_document = '글 작성';
$lang->point_delete_document = '글 삭제';
diff --git a/modules/point/lang/ru.php b/modules/point/lang/ru.php
index 83b2aa2ae..d16148cdd 100644
--- a/modules/point/lang/ru.php
+++ b/modules/point/lang/ru.php
@@ -15,7 +15,7 @@ $lang->level_point = 'Уровень поинтов';
$lang->about_level_point = 'Уровень будет изменен, когда поинты достигают каждого уровня поинтов или падают ниже его';
$lang->disable_download = 'Запретить скачивание';
$lang->about_disable_download = 'Это запретит скачивание файлов, когда не хватает достаточного кол-ва поинтов. (За исключением файлов изображений)';
-$lang->about_module_point = 'Вы можете установть поинты для каждого модуля, а модули, не имеющие значения будут использовать значение по умолчанию для поинтов. Все поинты будут восстановлены при обратном действии.';
+$lang->about_module_point = 'Вы можете установть поинты для каждого модуля, а модули, не имеющие значения будут использовать значение по умолчанию для поинтов.';
$lang->point_signup = 'Присвоить';
$lang->point_insert_document = 'При написании';
$lang->point_delete_document = 'При удалении';
diff --git a/modules/point/lang/tr.php b/modules/point/lang/tr.php
index edcc6aedd..0f617f677 100644
--- a/modules/point/lang/tr.php
+++ b/modules/point/lang/tr.php
@@ -27,7 +27,7 @@ $lang->point_link_group = 'Seviyeye Göre Grup Değiştirme';
$lang->point_group_reset_and_add = 'Düzenlenmiş grupları sıfırla ve yeni gruplar ekle';
$lang->point_group_add_only = 'Sadece yeni gruplara';
$lang->about_point_link_group = 'Belirli bir grup için seviye belirliyorsanız, kullanıcılar gruba o seviyenin puanına eriştiklerinde atanacaklardır.';
-$lang->about_module_point = 'Her modül için puan ayarlayabilirsiniz. Hiçbir değer atanmayan modüller varsayılan puan sistemini kullanacaktır. Tersi hareket durumunda tüm puanlar iade edilecektir.';
+$lang->about_module_point = 'Her modül için puan ayarlayabilirsiniz. Hiçbir değer atanmayan modüller varsayılan puan sistemini kullanacaktır.';
$lang->point_signup = 'Kayıt Olmaya';
$lang->point_insert_document = 'Yazıya';
$lang->point_delete_document = 'Silmeye';
diff --git a/modules/point/lang/vi.php b/modules/point/lang/vi.php
index 5ecba3008..4e0536c5a 100644
--- a/modules/point/lang/vi.php
+++ b/modules/point/lang/vi.php
@@ -27,7 +27,7 @@ $lang->point_link_group = 'Chuyển nhóm với cấp độ';
$lang->point_group_reset_and_add = 'Điểm số để thăng cấp cho nhóm mới.';
$lang->point_group_add_only = 'Chỉ cấp cho nhóm mới';
$lang->about_point_link_group = 'Nếu bạn đặt cấp độ cho một nhóm đặc biệt nào đó, người sử dụng trong nhóm đó khi đạt đến số điểm giới hạn sẽ tự động được chuyển sang nhóm mới.';
-$lang->about_module_point = 'Bạn có thể đặt thang điểm riêng cho mỗi Module, Module nào không được đặt sẽ sử dụng sự thiết lập mặc định. Tất cả điểm sẽ khác khi sử dụng chức năng này.';
+$lang->about_module_point = 'Bạn có thể đặt thang điểm riêng cho mỗi Module, Module nào không được đặt sẽ sử dụng sự thiết lập mặc định.';
$lang->point_signup = 'Khi đăng kí';
$lang->point_insert_document = 'Khi gửi bài';
$lang->point_delete_document = 'Khi xóa bài';
diff --git a/modules/point/lang/zh-CN.php b/modules/point/lang/zh-CN.php
index ada98e17d..ffcf811ef 100644
--- a/modules/point/lang/zh-CN.php
+++ b/modules/point/lang/zh-CN.php
@@ -25,7 +25,7 @@ $lang->point_link_group = '用户组绑定';
$lang->point_group_reset_and_add = '初始化已有用户组重新设置';
$lang->point_group_add_only = '只应用到新用户组';
$lang->about_point_link_group = '即级别绑定用户组。当级别达到指定级别时,会员所属用户组将自动更新为与其相对应的用户组。';
-$lang->about_module_point = '可以分别对各模块进行积分设置,没有被设置的模块将使用默认值。 所有积分在相反动作下恢复原始值。即:发表新帖后再删除得到的积分为0分。';
+$lang->about_module_point = '可以分别对各模块进行积分设置,没有被设置的模块将使用默认值。';
$lang->point_signup = '注册';
$lang->point_insert_document = '发表新帖';
$lang->point_delete_document = '删除主题';
diff --git a/modules/point/lang/zh-TW.php b/modules/point/lang/zh-TW.php
index baec73b50..4409ba508 100644
--- a/modules/point/lang/zh-TW.php
+++ b/modules/point/lang/zh-TW.php
@@ -25,7 +25,7 @@ $lang->point_link_group = '自動升級';
$lang->point_group_reset_and_add = '重新調整與新增群組';
$lang->point_group_add_only = '只限新群組';
$lang->about_point_link_group = '即群組隨等級變化。當等級達到指定等級時,會員所屬群組將自動更新成相對應的群組。';
-$lang->about_module_point = '可以分別對各模組進行點數設置,沒有設置的模組將使用預設值。 所有動作在反向操作下將恢復原始值。即:發表主題後再刪除得到的點數為零。';
+$lang->about_module_point = '可以分別對各模組進行點數設置,沒有設置的模組將使用預設值。';
$lang->point_signup = '註冊';
$lang->point_insert_document = '發表主題';
$lang->point_delete_document = '刪除主題';
From e2124ed1c060139415f70ca107e22467e167f48e Mon Sep 17 00:00:00 2001
From: Kijin Sung
Date: Wed, 22 Oct 2025 22:53:45 +0900
Subject: [PATCH 06/16] Refactor checkCSRF() to use Sec-Fetch-Site and Origin
headers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 최근 브라우저에서 지원하는 헤더를 사용하여 더 정확하게 체크
- Sec-Fetch-Site, Origin 헤더를 지원하지 않는 경우 기존처럼 Referer 체크
- CSRF 토큰은 더이상 기본 방법보다 보안이 더 뛰어나다고 말하기 힘듬
---
common/framework/Security.php | 68 +++++++++++++++++----------
modules/admin/lang/en.php | 2 +-
modules/admin/lang/ko.php | 2 +-
tests/unit/framework/SecurityTest.php | 64 ++++++++++++++++++++-----
4 files changed, 97 insertions(+), 39 deletions(-)
diff --git a/common/framework/Security.php b/common/framework/Security.php
index 200cf24d2..d5e0802cd 100644
--- a/common/framework/Security.php
+++ b/common/framework/Security.php
@@ -314,39 +314,57 @@ class Security
*/
public static function checkCSRF(?string $referer = null): bool
{
+ // If CSRF token checking is enabled, check the token header or parameter first.
+ // Tokens are allowed to be missing for unauthenticated users.
$check_csrf_token = config('security.check_csrf_token') ? true : false;
- if ($token = isset($_SERVER['HTTP_X_CSRF_TOKEN']) ? $_SERVER['HTTP_X_CSRF_TOKEN'] : null)
+ if ($check_csrf_token)
{
- return Session::verifyToken((string)$token, '', $check_csrf_token);
+ $token = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? ($_REQUEST['_rx_csrf_token'] ?? null);
+ if ($token)
+ {
+ return Session::verifyToken((string)$token, '', $check_csrf_token);
+ }
+ elseif (Session::getMemberSrl())
+ {
+ trigger_error('CSRF token missing in authenticated POST request: ' . (\Context::get('act') ?: '(no act)'), \E_USER_WARNING);
+ return false;
+ }
}
- elseif ($token = isset($_REQUEST['_rx_csrf_token']) ? $_REQUEST['_rx_csrf_token'] : null)
- {
- return Session::verifyToken((string)$token, '', $check_csrf_token);
- }
- elseif ($_SERVER['REQUEST_METHOD'] === 'GET')
+
+ // Return early for GET and other "safe" requests.
+ if (in_array($_SERVER['REQUEST_METHOD'], ['GET', 'HEAD', 'OPTIONS', 'TRACE']))
{
return false;
}
+
+ // The Sec-Fetch-Site header is the standard way to check whether the request is same-origin.
+ $sec_fetch_site = $_SERVER['HTTP_SEC_FETCH_SITE'] ?? '';
+ if ($sec_fetch_site === 'same-origin' || $sec_fetch_site === 'none')
+ {
+ return true;
+ }
+ if ($sec_fetch_site === 'cross-site')
+ {
+ return false;
+ }
+
+ // If the Sec-Fetch-Site header is not available, check the Origin header.
+ $origin = strval($_SERVER['HTTP_ORIGIN'] ?? '');
+ if ($origin !== '' && $origin !== 'null')
+ {
+ return URL::isInternalURL($origin);
+ }
+
+ // If the Origin header is not available, fall back to the Referer header.
+ // The Referer header is NOT allowed to be empty in this case.
+ $referer = $referer ?? strval($_SERVER['HTTP_REFERER'] ?? '');
+ if ($referer !== '' && $referer !== 'null')
+ {
+ return URL::isInternalURL($referer);
+ }
else
{
- $is_logged = Session::getMemberSrl();
- if ($is_logged)
- {
- trigger_error('CSRF token missing in POST request: ' . (\Context::get('act') ?: '(no act)'), \E_USER_WARNING);
- }
-
- if (!$referer)
- {
- $referer = strval(($_SERVER['HTTP_ORIGIN'] ?? '') ?: ($_SERVER['HTTP_REFERER'] ?? ''));
- }
- if ($referer !== '' && $referer !== 'null' && (!$check_csrf_token || !$is_logged))
- {
- return URL::isInternalURL($referer);
- }
- else
- {
- return false;
- }
+ return false;
}
}
diff --git a/modules/admin/lang/en.php b/modules/admin/lang/en.php
index e5081ebce..ff257d238 100644
--- a/modules/admin/lang/en.php
+++ b/modules/admin/lang/en.php
@@ -187,7 +187,7 @@ $lang->about_use_session_ssl = 'Force the session to be SSL-only. This helps
$lang->use_cookies_ssl = 'Use SSL-only cookies';
$lang->about_use_cookies_ssl = 'Force all cookies to be SSL-only.';
$lang->check_csrf_token = 'Use CSRF tokens';
-$lang->about_check_csrf_token = 'Use CSRF tokens to validate requests. This is more secure but may break some functionality. If not selected, Rhymix will use only the Referer header to defend against CSRF attacks.';
+$lang->about_check_csrf_token = 'Use CSRF tokens to validate requests. This may break some functionality. If not selected, Rhymix will use headers such as Sec-Fetch-Site and Origin to block CSRF attacks.';
$lang->use_nofollow = 'Add nofollow attribute to Links';
$lang->about_use_nofollow = 'Add rel="nofollow" to all links submitted by users in order to reduce the effectiveness of spamming. This does not apply to content submitted by the administrator.';
$lang->use_object_cache = 'Use Cache';
diff --git a/modules/admin/lang/ko.php b/modules/admin/lang/ko.php
index a0af5f684..d9a29fbda 100644
--- a/modules/admin/lang/ko.php
+++ b/modules/admin/lang/ko.php
@@ -188,7 +188,7 @@ $lang->about_use_session_ssl = '세션을 SSL 전용으로 지정하여 SSL이
$lang->use_cookies_ssl = 'SSL 전용 쿠키 사용';
$lang->about_use_cookies_ssl = '세션뿐 아니라 모든 쿠키를 SSL 전용으로 지정합니다. SSL을 항상 사용하도록 설정되어 있는 경우에만 활성화됩니다.';
$lang->check_csrf_token = 'CSRF 토큰 사용';
-$lang->about_check_csrf_token = 'CSRF 토큰을 사용하여 정상적인 요청 여부를 확인합니다. 보안이 향상되지만, 일부 서드파티 자료와 호환되지 않을 수 있습니다. 사용하지 않을 경우 리퍼러 헤더 확인만으로 CSRF 공격에 방어합니다.';
+$lang->about_check_csrf_token = 'CSRF 토큰을 사용하여 정상적인 요청 여부를 확인합니다. 일부 자료와 호환되지 않을 수 있습니다. 사용하지 않을 경우 Sec-Fetch-Site, Origin 등의 헤더를 사용하여 CSRF 공격을 감지합니다.';
$lang->use_nofollow = '링크에 nofollow 추가';
$lang->about_use_nofollow = '사용자들이 작성한 글에 포함된 모든 링크에 rel="nofollow" 속성을 추가하여 스팸으로 인한 사이트 신뢰도 저하를 방지합니다. 관리자가 작성한 글에는 적용되지 않습니다.';
$lang->use_object_cache = '캐시 사용';
diff --git a/tests/unit/framework/SecurityTest.php b/tests/unit/framework/SecurityTest.php
index f637ceb83..4246316be 100644
--- a/tests/unit/framework/SecurityTest.php
+++ b/tests/unit/framework/SecurityTest.php
@@ -106,10 +106,13 @@ class SecurityTest extends \Codeception\Test\Unit
{
$error_reporting = error_reporting(0);
+ config('security.check_csrf_token', true);
+
$_SERVER['REQUEST_METHOD'] = 'GET';
$_SERVER['HTTP_REFERER'] = '';
$_SERVER['HTTP_X_CSRF_TOKEN'] = '';
$this->assertFalse(Rhymix\Framework\Security::checkCSRF());
+
$_SERVER['HTTP_X_CSRF_TOKEN'] = Rhymix\Framework\Session::createToken();
$this->assertTrue(Rhymix\Framework\Security::checkCSRF());
@@ -117,6 +120,7 @@ class SecurityTest extends \Codeception\Test\Unit
$_SERVER['HTTP_REFERER'] = '';
$_SERVER['HTTP_X_CSRF_TOKEN'] = '';
$this->assertFalse(Rhymix\Framework\Security::checkCSRF());
+
$_SERVER['HTTP_X_CSRF_TOKEN'] = Rhymix\Framework\Session::createToken();
$this->assertTrue(Rhymix\Framework\Security::checkCSRF());
@@ -124,27 +128,63 @@ class SecurityTest extends \Codeception\Test\Unit
$_SERVER['HTTP_X_CSRF_TOKEN'] = '';
$this->assertFalse(Rhymix\Framework\Security::checkCSRF());
- $_SERVER['HTTP_REFERER'] = 'http://www.rhymix.org/foo/bar';
- $_SERVER['HTTP_X_CSRF_TOKEN'] = '';
+ $_SERVER['HTTP_X_CSRF_TOKEN'] = Rhymix\Framework\Session::createToken();
$this->assertTrue(Rhymix\Framework\Security::checkCSRF());
+
$_SERVER['HTTP_X_CSRF_TOKEN'] = 'invalid value';
$this->assertFalse(Rhymix\Framework\Security::checkCSRF());
- $_SERVER['HTTP_ORIGIN'] = 'http://www.rhymix.org';
- $_SERVER['HTTP_REFERER'] = 'http://www.foobar.com';
- $_SERVER['HTTP_X_CSRF_TOKEN'] = '';
- $this->assertTrue(Rhymix\Framework\Security::checkCSRF());
- $_SERVER['HTTP_REFERER'] = '';
- $this->assertTrue(Rhymix\Framework\Security::checkCSRF());
- $_SERVER['HTTP_ORIGIN'] = 'http://www.foobar.com';
+ $_SERVER['HTTP_X_CSRF_TOKEN'] = '0';
$this->assertFalse(Rhymix\Framework\Security::checkCSRF());
+
+ config('security.check_csrf_token', false);
+ unset($_SERVER['HTTP_X_CSRF_TOKEN']);
+
+ $_SERVER['HTTP_REFERER'] = 'https://www.rhymix.org/foo/bar';
+ $this->assertTrue(Rhymix\Framework\Security::checkCSRF());
+
+ $_SERVER['HTTP_ORIGIN'] = 'https://www.rhymix.org';
+ $_SERVER['HTTP_REFERER'] = 'https://www.foobar.com';
+ $this->assertTrue(Rhymix\Framework\Security::checkCSRF());
+
+ $_SERVER['HTTP_ORIGIN'] = 'https://www.foobar.com';
+ $this->assertFalse(Rhymix\Framework\Security::checkCSRF());
+
+ $_SERVER['HTTP_SEC_FETCH_SITE'] = 'same-origin';
+ $this->assertTrue(Rhymix\Framework\Security::checkCSRF());
+
+ $_SERVER['HTTP_SEC_FETCH_SITE'] = 'none';
+ $this->assertTrue(Rhymix\Framework\Security::checkCSRF());
+
+ $_SERVER['HTTP_SEC_FETCH_SITE'] = 'invalid value';
+ $this->assertFalse(Rhymix\Framework\Security::checkCSRF());
+
+ unset($_SERVER['HTTP_SEC_FETCH_SITE']);
+
+ $_SERVER['HTTP_ORIGIN'] = '';
+ $_SERVER['HTTP_REFERER'] = '';
+ $this->assertFalse(Rhymix\Framework\Security::checkCSRF());
+
$_SERVER['HTTP_ORIGIN'] = 'null';
+ $_SERVER['HTTP_REFERER'] = '';
$this->assertFalse(Rhymix\Framework\Security::checkCSRF());
- $_SERVER['HTTP_REFERER'] = '';
- $_SERVER['HTTP_X_CSRF_TOKEN'] = '';
- $this->assertTrue(Rhymix\Framework\Security::checkCSRF('http://www.rhymix.org/'));
+ $_SERVER['HTTP_ORIGIN'] = 'null';
+ $_SERVER['HTTP_REFERER'] = 'https://www.rhymix.org';
+ $this->assertTrue(Rhymix\Framework\Security::checkCSRF());
+ $_SERVER['HTTP_ORIGIN'] = '';
+ $_SERVER['HTTP_REFERER'] = 'null';
+ $this->assertFalse(Rhymix\Framework\Security::checkCSRF());
+
+ $_SERVER['HTTP_ORIGIN'] = '';
+ $_SERVER['HTTP_REFERER'] = '';
+ $this->assertTrue(Rhymix\Framework\Security::checkCSRF('https://www.rhymix.org/'));
+
+ $_SERVER['HTTP_SEC_FETCH_SITE'] = 'cross-site';
+ $this->assertFalse(Rhymix\Framework\Security::checkCSRF('https://www.rhymix.org/'));
+
+ unset($_SERVER['HTTP_SEC_FETCH_SITE']);
error_reporting($error_reporting);
}
From ad293fa4d04bf862c0149094c2acf52b19e66140 Mon Sep 17 00:00:00 2001
From: Kijin Sung
Date: Wed, 22 Oct 2025 23:02:15 +0900
Subject: [PATCH 07/16] Add empty paragraph after