diff --git a/CHANGELOG.md b/CHANGELOG.md index b57b9cf..edba3e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `` in RSS feed items, derived from the post date. - `` in sitemap entries for SEO. - In-memory rate limiting on the login endpoint: 10 attempts per 60 seconds per IP. +- `` element in the RSS feed channel. +- Periodic cleanup of stale `login_attempts` entries to prevent unbounded memory growth. ### Changed @@ -22,6 +24,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `admin.session_ttl` was defined but never read; it now controls `Rack::Session::Cookie` expiration via `expire_after`. +- Derived post excerpts now strip HTML tags before stripping markdown markup, + avoiding raw HTML leaks in auto-generated excerpts. ## [0.1.0] — 2026-06-20 diff --git a/lib/volumen/post.rb b/lib/volumen/post.rb index f7775ec..5d84c18 100644 --- a/lib/volumen/post.rb +++ b/lib/volumen/post.rb @@ -112,7 +112,7 @@ module Volumen .find { |para| !para.empty? && !para.start_with?("#") } return "" if paragraph.nil? - stripped = paragraph.gsub(/[*_`>#]/, "").strip + stripped = paragraph.gsub(/<[^>]+>/, "").gsub(/[*_`>#]/, "").strip stripped.length > limit ? "#{stripped[0, limit].rstrip}…" : stripped end end diff --git a/test/post_test.rb b/test/post_test.rb index ddfd5ee..fcf39d5 100644 --- a/test/post_test.rb +++ b/test/post_test.rb @@ -30,6 +30,12 @@ class PostTest < Minitest::Test assert_equal "The body paragraph.", post.excerpt end + def test_derived_excerpt_strips_html_tags + body = "Bold and italic." + post = Volumen::Post.new(metadata: { "slug" => "x" }, body: body) + assert_equal "Bold and italic.", post.excerpt + end + def test_summary_shape post = Volumen::Post.new(metadata: { "slug" => "x", "title" => "X" }, body: "Body") summary = post.summary