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;