VYPR
Unrated severityNVD Advisory· Published May 26, 2026

CVE-2026-46740

CVE-2026-46740

Description

Mojolicious::Plugin::Statsd versions through 0.04 for Perl allowed metric injections.

The metric names and set values were not checked for newlines, colons or pipes. Metrics generated from untrusted sources could inject additional statsd metrics.

Version 0.06 changes the module from being a statsd client to using a separate statsd client. It defaults to using a version of Net::Statsd::Tiny that fixes a similar issue (CVE-2026-46720).

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Mojolicious::Plugin::Statsd through 0.04 fails to sanitize metric names and values, allowing injection of arbitrary statsd metrics from untrusted input.

Vulnerability

Mojolicious::Plugin::Statsd versions 0.04 and earlier for Perl failed to validate metric names and set values for newline, colon, or pipe characters. This allowed an attacker to inject additional statsd metrics when the plugin is used to send metrics derived from untrusted sources. The issue is fixed in version 0.06, which delegates protocol handling to Net::Statsd::Tiny (which addresses a similar injection vector in CVE-2026-46720) [1][2].

Exploitation

An attacker who can control metric names or values processed by the plugin can inject arbitrary statsd protocol data. The input may contain newlines, colons, or pipes to craft additional metric lines. No authentication or special network position is required beyond access to an endpoint or data source that the plugin consumes. The attacker merely supplies crafted input that is used as a metric name or value [1][2].

Impact

Successful exploitation allows the attacker to send arbitrary statsd metrics to the configured statsd server. This can lead to: misleading monitoring data, triggering alerts, masking malicious activity, or consumption of system resources by flooding metrics. The impact is primarily on the integrity and availability of monitoring systems, not on the confidentiality of the application itself [1][2].

Mitigation

Upgrade to Mojolicious::Plugin::Statsd version 0.06 or later, which uses Net::Statsd::Tiny as the underlying protocol handler. The patch was committed on 2026-05-21. No workaround is available for applications that must use the affected versions [1][2].

AI Insight generated on May 26, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2

Patches

1
f049156982a2

Use Net::Statsd::Tiny as underlying protocol handler

https://github.com/robrwo/perl-mojolicious-plugin-statsdRobert RothenbergMay 21, 2026via nvd-ref
6 files changed · +70 80
  • Changes+7 0 modified
    @@ -1,6 +1,13 @@
     Changelog for Mojolicious-Plugin-Statsd
     
     {{$NEXT}}
    +  [Security]
    +  - Fixed metric injection CVE-2026-46740
    +
    +  [Enhancements]
    +  - Use Net::Statsd::Tiny for handling the statsd protocol
    +  - Added the client attribute for choosing any statsd client
    +
       [Documentation]
      - New maintainer Robert Rothenberg <perl@rhizomnic.com>
      - Updated copyright year.
    
  • cpanfile+1 1 modified
    @@ -18,9 +18,9 @@ on 'test' => sub {
       requires "IPC::Open3" => "0";
       requires "Module::Metadata" => "1.000015";
       requires "Mojolicious::Lite" => "0";
    +  requires "Net::Statsd::Tiny" => "v0.4.0";
       requires "Test::Mojo" => "0";
       requires "Test::More" => "0";
    -  requires "Test::Warnings" => "0";
       requires "strict" => "0";
       requires "warnings" => "0";
     };
    
  • lib/Mojolicious/Plugin/Statsd/Adapter/Statsd.pm+33 56 modified
    @@ -2,85 +2,62 @@ package Mojolicious::Plugin::Statsd::Adapter::Statsd;
     
     use Mojo::Base -base;
     
    +use Mojo::Loader qw(load_class);
    +
     our $VERSION = '0.05';
     
     use IO::Socket::INET ();
     
    -has socket => sub {
    -  my $self = shift;
    +has client =>  sub {
    +    my $self = shift;
    +    my ($host, $port) = split ':', $self->addr;
    +    load_class "Net::Statsd::Tiny";
    +    return Net::Statsd::Tiny->new(
    +        socket => $self->socket,
    +    );
    +};
     
    -  IO::Socket::INET->new(
    -    Proto    => 'udp',
    -    PeerAddr => $self->addr,
    -    Blocking => 0,
    -  ) or die "Can't open write socket for stats: $@";
    +has socket => sub {
    +    my $self = shift;
    +    IO::Socket::INET->new(
    +        Proto    => 'udp',
    +        PeerAddr => $self->addr,
    +        Blocking => 0,
    +    ) or die "Can't open write socket for stats: $@";
     };
     
     has addr => sub {
       $ENV{STATSD_ADDR} // '127.0.0.1:8125';
     };
     
    -sub timing {
    -  my ($self, $names, $time, $sample_rate) = @_;
    -
    -  if (($sample_rate //= 1) < 1) {
    -    return unless rand() <= $sample_rate;
    -  }
    -
    -  $self->_send(
    -    map { _sampled($_, $sample_rate) }
    -    map { sprintf '%s:%d|ms', $_, $time }
    -    @$names
    -  );
    -}
    -
     sub counter {
    -  my ($self, $counters, $delta, $sample_rate) = @_;
    -
    -  if (($sample_rate //= 1) < 1) {
    -    return unless rand() <= $sample_rate;
    -  }
    +    my ( $self, $names, $value, $rate ) = @_;
    +    $self->client->counter($_, $value, $rate // 1) for @$names;
    +    return -1;
    +}
     
    -  $self->_send(
    -     map { _sampled($_, $sample_rate) }
    -     map { sprintf '%s:%d|c', $_, $delta }
    -     @$counters
    -  );
    +sub timing {
    +    my ( $self, $names, $value, $rate ) = @_;
    +    $self->client->timing($_, $value, $rate // 1) for @$names;
    +    return -1;
     }
     
     sub gauge {
    -  my ($self, $gauges, $value) = @_;
    -
    -  $self->_send(
    -    map { sprintf '%s:%s|g', $_, $value }
    -    @$gauges
    -  );
    +    my ( $self, $names, $value ) = @_;
    +    $self->client->gauge($_, $value) for @$names;
    +    return -1;
     }
     
     sub set_add {
    -  my ($self, $sets, @values) = @_;
    -
    -  $self->_send(
    -    map {
    -      my $set = $_;
    -      map { sprintf '%s:%s|s', $set, $_ } @values
    -    } @$sets
    -  );
    -}
    -
    -sub _sampled { $_[1] == 1 ? $_[0] : ($_[0] . '|@' . $_[1]) }
    -
    -sub _send {
    -  my ($self) = shift;
    -  my $payload = join("\012", @_);
    -
    -  ($self->socket->send($payload) // -1) == length($payload)
    -    or warn "stats: UDP packet may have been truncated ($!)";
    +    my ( $self, $names, @values ) = @_;
    +    for my $value (@values) {
    +        $self->client->set_add($_, $value) for @$names;
    +    }
    +    return -1;
     }
     
     1;
     
    -
     =begin :prelude
     
     =for stopwords UDP statsd addr
    
  • lib/Mojolicious/Plugin/Statsd.pm+6 0 modified
    @@ -147,6 +147,12 @@ Mojolicious::Plugin::Statsd
     
     Mojolicious::Plugin::Statsd supports the following options.
     
    +=head2 client
    +
    +This is the underlying statsd client. It defaults to an instance of
    +L<Net::Statsd::Tiny> but any class with compatible methods can be
    +used.
    +
     =head2 adapter
     
       # Mojolicious::Lite
    
  • README.md+5 0 modified
    @@ -44,6 +44,11 @@ throwing your metrics at statsd.
     
     Changes for version 0.05 (2026-05-21)
     
    +- Security
    +    - Fixed metric injection CVE-2026-46740
    +- Enhancements
    +    - Use Net::Statsd::Tiny for handling the statsd protocol
    +    - Added the client attribute for choosing any statsd client
     - Documentation
     - New maintainer Robert Rothenberg <perl@rhizomnic.com>
     - Updated copyright year.
    
  • t/25-adapter-statsd.t+18 23 modified
    @@ -1,25 +1,23 @@
     use Mojo::Base -strict;
     
     use Test::More;
    -use Test::Warnings qw(warning);
     
     use Mojolicious::Plugin::Statsd::Adapter::Statsd;
    +use Net::Statsd::Tiny v0.4.0;
     
     {
       package Mock::Socket;
       use Mojo::Base -base;
     
    -  our $truncate_send = 0;
       has buffer => sub { [] };
     
       sub send {
    -    my ($self, $data) = @_;
    -    push @{$self->buffer}, $data;
    -    return length($data) unless $truncate_send;
    +      my ($self, $data) = @_;
    +      push @{$self->buffer}, $data;
       }
     
       sub pop {
    -    pop @{(shift)->buffer};
    +    shift @{(shift)->buffer};
       }
     }
     
    @@ -32,42 +30,39 @@ my $statsd = new_ok
     can_ok $statsd => qw(timing counter);
     
     ok $statsd->counter(['test1'], 1), 'bumped test1 by 1';
    -is $sock->pop, 'test1:1|c', 'recorded 1 hit for test1';
    +
    +is $sock->pop, "test1:1|c\n", 'recorded 1 hit for test1';
     
     ok $statsd->counter(['test2'], 1, 0.99) || 1, 'bumped test2 by 1, sampled';
    -is $sock->pop // 'test2:1|c|@0.99', 'test2:1|c|@0.99', 'recorded 1 hit for test2';
    +is $sock->pop // "test2:1|c|@0.99\n", "test2:1|c|@0.99\n", 'recorded 1 hit for test2';
     
     ok $statsd->counter(['test1', 'test3'], 1),
       'bumped test1 and test3 by 1';
    -ok $sock->pop eq "test1:1|c\012test3:1|c",
    -  'recorded hits for test1 and test3';
    +is $sock->pop, "test1:1|c\n",
    +  'recorded hits for test1 ...';
    +is $sock->pop, "test3:1|c\n",
    +  '... and test3';
     
     ok $statsd->timing(['test4'], 1000),
       'timing test4 for 1000ms';
    -is $sock->pop, 'test4:1000|ms',
    +is $sock->pop, "test4:1000|ms\n",
       'recorded timing of 1000 for test4';
     
    -{
    -  local $Mock::Socket::truncate_send = 1;
    -  like
    -    warning { $statsd->counter(['test5'], 1) },
    -    qr/truncated/,
    -    'warned about possible truncated packet';
    -}
    -
     ok $statsd->gauge(['test6'], 42),
       'gauge test6 at 42';
    -is $sock->pop, 'test6:42|g',
    +is $sock->pop, "test6:42|g\n",
        'recorded gauge at 42 for test6';
     
     ok $statsd->gauge(['test7'], -42),
       'gauge test7 at -42';
    -is $sock->pop, 'test7:-42|g',
    +is $sock->pop, "test7:-42|g\n",
        'recorded gauge change -42 for test7';
     
     ok $statsd->set_add(['test8'], qw/a b c/),
       'add to test8 a b c';
    -is $sock->pop, "test8:a|s\012test8:b|s\012test8:c|s",
    -  'recorded set adds for a b c on test8';
    +is $sock->pop, "test8:a|s\n", 'recordset set add for a';
    +is $sock->pop, "test8:b|s\n", 'recordset set add for b';
    +is $sock->pop, "test8:c|s\n", 'recordset set add for c';
    +
     
     done_testing();
    

Vulnerability mechanics

Root cause

"Missing input validation in metric name and value fields allows injection of newlines, colons, and pipes to forge arbitrary statsd metrics."

Attack vector

An attacker who can control metric names or values (for example, by injecting data through an application that uses this plugin to log user-driven events) can embed newline, colon, or pipe characters into those strings. Because the old code did not validate or escape these characters, a single crafted metric could break out of its intended statsd packet and inject arbitrary additional statsd metrics [ref_id=1]. The payload is sent over UDP to the statsd daemon, so no authentication is required beyond the ability to supply the metric input.

Affected code

The vulnerable code resides in `lib/Mojolicious/Plugin/Statsd/Adapter/Statsd.pm` (versions through 0.04). The `counter`, `timing`, `gauge`, and `set_add` methods directly interpolated user-supplied metric names and values into statsd protocol strings via `sprintf`, and the `_send` method joined these strings with newlines and sent them over UDP without any sanitization [patch_id=2594146].

What the fix does

The patch replaces the manual statsd protocol formatting in `Mojolicious::Plugin::Statsd::Adapter::Statsd` with delegation to `Net::Statsd::Tiny` (v0.4.0 or later) [patch_id=2594146]. The new `client` attribute loads `Net::Statsd::Tiny`, and each metric method (`counter`, `timing`, `gauge`, `set_add`) now calls the corresponding method on that client object instead of building raw strings. `Net::Statsd::Tiny` handles input sanitization internally, blocking newlines, colons, and pipes that could be used for injection [ref_id=1]. The `_send` and `_sampled` helper methods are removed entirely.

Preconditions

  • inputThe attacker must be able to supply metric names or values that are passed to the plugin's counter, timing, gauge, or set_add methods.
  • authNo authentication is required beyond the ability to trigger the vulnerable code path.

Generated on May 26, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

3

News mentions

0

No linked articles in our index yet.