diff --git a/app/Blog/Services/PostContentRenderer.php b/app/Blog/Services/PostContentRenderer.php index ee2e558..c01a381 100644 --- a/app/Blog/Services/PostContentRenderer.php +++ b/app/Blog/Services/PostContentRenderer.php @@ -43,7 +43,10 @@ class PostContentRenderer $html = $this->renderShortcodes($content, $post); // 插件钩子:付费内容过滤、AI 处理等 - return app(\App\Blog\Support\PluginManager::class)->applyFilters('post.rendered', $html, $post); + $html = app(\App\Blog\Support\PluginManager::class)->applyFilters('post.rendered', $html, $post); + + // 兜底:任何情况下不把裸 [paid] 标签输出到页面(如会员插件被停用时) + return preg_replace('/\[(\/?paid)\]/i', '', $html); } /** diff --git a/tests/Feature/MembershipFlowTest.php b/tests/Feature/MembershipFlowTest.php index 50db5b5..83f8907 100644 --- a/tests/Feature/MembershipFlowTest.php +++ b/tests/Feature/MembershipFlowTest.php @@ -218,6 +218,23 @@ class MembershipFlowTest extends TestCase ->assertSee('隐藏付费块'); } + public function test_rendered_paid_content_never_leaks_raw_tags(): void + { + $post = Post::create([ + 'title' => '渲染防泄漏', + 'slug' => 'render-no-leak', + 'content' => "公开\n\n[paid]\n隐藏内容\n[/paid]", + 'content_format' => 'markdown', + 'status' => 'published', + 'published_at' => now(), + ]); + + $html = app(\App\Blog\Services\PostContentRenderer::class)->render($post); + + $this->assertStringNotContainsString('[paid]', $html); + $this->assertStringNotContainsString('[/paid]', $html); + } + public function test_members_only_post_shows_paywall_to_guest(): void { $post = Post::create([