Security Advisories (1)
CVE-2026-75870 (2026-08-22)

Punk versions before 0.18 for Perl allow session cookie forgery via an empty default HMAC key when a session is declared without a secret. The session keyword freezes its options onto the application as given: it does not require a secret, warn, or refuse to start when one is absent. The cookie read and the write-back both default that key to the empty string, so a declaration with no secret option, or with an undefined or empty one, signs and verifies with a zero-length HMAC-SHA256 key. An attacker who knows the cookie format can then mint one offline carrying any contents the session holds, such as a user identifier or a role. Nothing marks the misconfiguration at runtime: cookies are well formed and sessions round-trip as expected.

NAME

Punk::RateLimit - rate limiting and IP blocking over Hyperman's shared arena

SYNOPSIS

use Punk;

# a loose limit on everything, keyed by client IP
rate_limit limit => 300, window => 60;

# a tighter one on the API, keyed by an API-key header
rate_limit for => '/api', by => 'header:X-Api-Key',
           limit => 60, window => 60, tag => 'api';

# or a custom identity
rate_limit by => sub { my ($c) = @_; $c->session->{user} }, limit => 20;

# block an abuser from a handler; the edge drops it next time
post '/login' => sub {
    my ($c) = @_;
    if (too_many_failures($c)) { $c->block_ip(undef, 3600); }
    ...
};

DESCRIPTION

rate_limit installs a before_dispatch that answers 429 Too Many Requests (with Retry-After and the X-RateLimit-* headers) when a caller exceeds the limit for the rule. The counters live in Hyperman's shared arena, mapped before its workers fork, so a limit is exact across the whole pool rather than per worker. It is by the client IP by default; by => 'header:NAME' keys on a request header, and by => sub { ... } on whatever the coderef returns for a context. for scopes a rule to a path prefix, tag names its counter namespace. Declare it more than once for layered limits.

Blocking is separate and cheaper: $c->block_ip($ip, $ttl) adds an IP to the same arena's denylist, and Hyperman drops it at accept - before a byte is read - on its next connection. $c->unblock_ip($ip) lifts it. Both default $ip to the current request's REMOTE_ADDR. $c->rate_hit($key, $limit, $window) is the raw counter check, returning ($ok, $remaining, $reset).

Everything fails open: with no Hyperman >= ABI v3 under the application the limiter allows every request and blocking is a no-op, so it is never the reason a good request is refused.

SEE ALSO

Punk, Hyperman.