From f5d7280d692b53d2ba7febcc065154ef56d48593 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 8 Sep 2026 07:02:22 +0000 Subject: [PATCH 1/4] Initial plan From 843757c3b20f905cd6cd103961247d5875c9ac59 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 8 Sep 2026 07:07:31 +0000 Subject: [PATCH 2/4] Validate format name against known formatters to prevent code injection Co-authored-by: davorg <24642+davorg@users.noreply.github.com> --- lib/XML/Feed.pm | 9 ++++++++ lib/XML/Feed/Entry.pm | 4 ++++ t/32-format-injection.t | 49 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 t/32-format-injection.t diff --git a/lib/XML/Feed.pm b/lib/XML/Feed.pm index d284e21..22e7409 100644 --- a/lib/XML/Feed.pm +++ b/lib/XML/Feed.pm @@ -16,8 +16,13 @@ use Module::Pluggable search_path => "XML::Feed::Format", our $VERSION = 'v1.0.0'; our $MULTIPLE_ENCLOSURES = 0; our @formatters; +our %formatters; BEGIN { @formatters = __PACKAGE__->formatters; + for my $formatter (@formatters) { + (my $name = $formatter) =~ s/^XML::Feed::Format:://; + $formatters{$name} = 1; + } } sub _parse_args { @@ -57,6 +62,8 @@ sub new { $args = $class->_parse_args(pop @_); } + Carp::croak("Unsupported format $format: not a recognised feed format") + unless $formatters{$format}; my $format_class = 'XML::Feed::Format::' . $format; eval "use $format_class"; Carp::croak("Unsupported format $format: $@") if $@; @@ -114,6 +121,8 @@ sub parse { } my $format_class = join '::', __PACKAGE__, "Format", $format; + return $class->error("Unsupported format $format: not a recognised feed format") + unless $formatters{$format}; eval "use $format_class"; return $class->error("Unsupported format $format: $@") if $@; bless $feed, $format_class; diff --git a/lib/XML/Feed/Entry.pm b/lib/XML/Feed/Entry.pm index 23e06d6..7829980 100644 --- a/lib/XML/Feed/Entry.pm +++ b/lib/XML/Feed/Entry.pm @@ -11,6 +11,8 @@ use Scalar::Util qw( blessed ); use Carp; +use XML::Feed (); + sub wrap { my $class = shift; my($item) = @_; @@ -23,6 +25,8 @@ sub new { my $class = shift; my($format) = @_; $format //= 'Atom'; + Carp::croak("Unsupported format $format: not a recognised feed format") + unless $XML::Feed::formatters{$format}; my $format_class = 'XML::Feed::Format::' . $format; eval "use $format_class"; Carp::croak("Unsupported format $format: $@") if $@; diff --git a/t/32-format-injection.t b/t/32-format-injection.t new file mode 100644 index 0000000..d03dfa7 --- /dev/null +++ b/t/32-format-injection.t @@ -0,0 +1,49 @@ +use strict; +use warnings; +use Test::More tests => 11; + +use XML::Feed; +use XML::Feed::Entry; + +# %formatters should be populated with the known, safe format names. +ok exists $XML::Feed::formatters{Atom}, 'Atom is a known formatter'; +ok exists $XML::Feed::formatters{RSS}, 'RSS is a known formatter'; + +my $marker = '/tmp/xml-feed-format-injection-test'; +unlink $marker; + +my $malicious = qq{Atom; system('touch $marker')}; + +eval { + XML::Feed->new($malicious); +}; +like $@, qr{Unsupported format \Q$malicious\E:}, 'XML::Feed->new rejects an unrecognised format'; +ok !-e $marker, 'XML::Feed->new did not execute injected code'; + +eval { + XML::Feed::Entry->new($malicious); +}; +like $@, qr{Unsupported format \Q$malicious\E:}, 'XML::Feed::Entry->new rejects an unrecognised format'; +ok !-e $marker, 'XML::Feed::Entry->new did not execute injected code'; + +{ + my $result = XML::Feed->parse(\<<'XML', $malicious); + +Test +XML + ok !defined($result), 'XML::Feed->parse rejects an unrecognised format'; + like( XML::Feed->errstr, qr{Unsupported format \Q$malicious\E:}, 'XML::Feed->parse error message' ); + ok !-e $marker, 'XML::Feed->parse did not execute injected code'; +} + +unlink $marker; + +eval { + XML::Feed->new('Nofeed;semicolon'); +}; +like $@, qr{Unsupported format Nofeed;semicolon:}, 'XML::Feed->new rejects format names with a semicolon'; + +eval { + XML::Feed::Entry->new('Nofeed;semicolon'); +}; +like $@, qr{Unsupported format Nofeed;semicolon:}, 'XML::Feed::Entry->new rejects format names with a semicolon'; From 3509de4c766b9a64bf6638aa87127f229c1bce54 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 8 Sep 2026 07:13:49 +0000 Subject: [PATCH 3/4] Bump version to v1.0.1 and update ChangeLog Co-authored-by: davorg <24642+davorg@users.noreply.github.com> --- ChangeLog.md | 5 +++++ lib/XML/Feed.pm | 2 +- lib/XML/Feed/Content.pm | 2 +- lib/XML/Feed/Enclosure.pm | 2 +- lib/XML/Feed/Entry.pm | 2 +- lib/XML/Feed/Entry/Format/Atom.pm | 2 +- lib/XML/Feed/Entry/Format/RSS.pm | 2 +- lib/XML/Feed/Format/Atom.pm | 2 +- lib/XML/Feed/Format/RSS.pm | 2 +- lib/XML/Feed/Util.pm | 2 +- 10 files changed, 14 insertions(+), 9 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index d784805..59a6ef1 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,9 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +## [v1.0.1] - 2026-09-08 + ### Fixed * Typo in Pod (thanks @stefanhaerter) +* Validate the `format` argument against a list of known formatters before + using it to build and load a class name, preventing code injection via a + malicious format string ## [v1.0.0] - 2025-11-17 diff --git a/lib/XML/Feed.pm b/lib/XML/Feed.pm index 22e7409..6de826a 100644 --- a/lib/XML/Feed.pm +++ b/lib/XML/Feed.pm @@ -13,7 +13,7 @@ use Module::Pluggable search_path => "XML::Feed::Format", require => 1, sub_name => 'formatters'; -our $VERSION = 'v1.0.0'; +our $VERSION = 'v1.0.1'; our $MULTIPLE_ENCLOSURES = 0; our @formatters; our %formatters; diff --git a/lib/XML/Feed/Content.pm b/lib/XML/Feed/Content.pm index a4b000a..305ba9c 100644 --- a/lib/XML/Feed/Content.pm +++ b/lib/XML/Feed/Content.pm @@ -2,7 +2,7 @@ package XML::Feed::Content; use strict; use warnings; -our $VERSION = '0.65'; +our $VERSION = '1.0.1'; use base qw( Class::ErrorHandler ); diff --git a/lib/XML/Feed/Enclosure.pm b/lib/XML/Feed/Enclosure.pm index 3c69a35..3f6545a 100644 --- a/lib/XML/Feed/Enclosure.pm +++ b/lib/XML/Feed/Enclosure.pm @@ -2,7 +2,7 @@ package XML::Feed::Enclosure; use strict; use warnings; -our $VERSION = '0.65'; +our $VERSION = '1.0.1'; use base qw( Class::ErrorHandler ); diff --git a/lib/XML/Feed/Entry.pm b/lib/XML/Feed/Entry.pm index 7829980..584d5c4 100644 --- a/lib/XML/Feed/Entry.pm +++ b/lib/XML/Feed/Entry.pm @@ -3,7 +3,7 @@ use strict; use warnings; use v5.10; -our $VERSION = '0.65'; +our $VERSION = '1.0.1'; use base qw( Class::ErrorHandler ); diff --git a/lib/XML/Feed/Entry/Format/Atom.pm b/lib/XML/Feed/Entry/Format/Atom.pm index 92b80d8..d059ee6 100644 --- a/lib/XML/Feed/Entry/Format/Atom.pm +++ b/lib/XML/Feed/Entry/Format/Atom.pm @@ -3,7 +3,7 @@ use strict; use warnings; use v5.10; -our $VERSION = '0.65'; +our $VERSION = '1.0.1'; use base qw( XML::Feed::Entry ); use XML::Feed::Content; diff --git a/lib/XML/Feed/Entry/Format/RSS.pm b/lib/XML/Feed/Entry/Format/RSS.pm index 2901abb..403c2fa 100644 --- a/lib/XML/Feed/Entry/Format/RSS.pm +++ b/lib/XML/Feed/Entry/Format/RSS.pm @@ -3,7 +3,7 @@ use strict; use warnings; use v5.10; -our $VERSION = '0.65'; +our $VERSION = '1.0.1'; sub format { 'RSS ' . $_[0]->{'_version'} } diff --git a/lib/XML/Feed/Format/Atom.pm b/lib/XML/Feed/Format/Atom.pm index 89a7f73..846a25d 100644 --- a/lib/XML/Feed/Format/Atom.pm +++ b/lib/XML/Feed/Format/Atom.pm @@ -3,7 +3,7 @@ use strict; use warnings; use v5.10; -our $VERSION = '0.65'; +our $VERSION = '1.0.1'; use base qw( XML::Feed ); use XML::Atom::Feed; diff --git a/lib/XML/Feed/Format/RSS.pm b/lib/XML/Feed/Format/RSS.pm index 3fabaa2..a2a0d56 100644 --- a/lib/XML/Feed/Format/RSS.pm +++ b/lib/XML/Feed/Format/RSS.pm @@ -3,7 +3,7 @@ use strict; use warnings; use v5.10; -our $VERSION = '0.65'; +our $VERSION = '1.0.1'; use base qw( XML::Feed ); use DateTime::Format::Mail; diff --git a/lib/XML/Feed/Util.pm b/lib/XML/Feed/Util.pm index 38ded02..ef7fbfc 100644 --- a/lib/XML/Feed/Util.pm +++ b/lib/XML/Feed/Util.pm @@ -2,7 +2,7 @@ package XML::Feed::Util; use strict; use warnings; -our $VERSION = '0.65'; +our $VERSION = '1.0.1'; use base qw( Exporter ); use DateTime::Format::Flexible; From eaf0f20f27614de36dcec84a8b4c560fcd808c3c Mon Sep 17 00:00:00 2001 From: Dave Cross Date: Tue, 8 Sep 2026 08:15:22 +0100 Subject: [PATCH 4/4] Same format for all version numbers --- lib/XML/Feed.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/XML/Feed.pm b/lib/XML/Feed.pm index 6de826a..4e9c756 100644 --- a/lib/XML/Feed.pm +++ b/lib/XML/Feed.pm @@ -13,7 +13,7 @@ use Module::Pluggable search_path => "XML::Feed::Format", require => 1, sub_name => 'formatters'; -our $VERSION = 'v1.0.1'; +our $VERSION = '1.0.1'; our $MULTIPLE_ENCLOSURES = 0; our @formatters; our %formatters;