Security Advisories (1)
CVE-2026-104380 (2026-10-06)

Punk versions from 0.48 before 0.55 for Perl route Extended CONNECT requests to any GET route without an Origin check in ps_serve_one. On HTTP/2 and HTTP/3 a WebSocket handshake arrives as an Extended CONNECT, which is matched as a GET and so reaches every GET route, API operation and mount. The Origin check runs only when a websocket route matches. On this transport the handler's status is the handshake response, and a 2xx accepts it. A cross-origin page can open a WebSocket to any path and learn from its open or error event whether that path returns 2xx.

NAME

Punk::Auth::Password - password hashing

SYNOPSIS

use Punk::Auth::Password;

my $stored = Punk::Auth::Password::hash($plain);
my $ok     = Punk::Auth::Password::verify($plain, $stored);

if ($ok && Punk::Auth::Password::needs_rehash($stored)) {
    $stored = Punk::Auth::Password::hash($plain);   # upgrade in place
}

my $token  = Punk::Auth::Password::token;           # to mail
my $digest = Punk::Auth::Password::token_digest($token);   # to store

DESCRIPTION

PBKDF2-HMAC-SHA256 in C, over the SHA-256 this distribution already bundles for sessions. No library dependency; salts come from the same entropy source the CSRF tokens use (getentropy where the build found it, /dev/urandom otherwise - a failure croaks rather than degrade).

The stored form is

pbkdf2-sha256$<iterations>$<salt base64>$<key base64>

with a 16-byte salt and a 32-byte derived key. The iteration count travels in the string, so verify always uses the cost a hash was created with and raising the default needs no migration: needs_rehash reports the old rows and a login re-hashes with the plaintext in hand.

Every function is also callable as a class method (Punk::Auth::Password->verify(...)); the invocant is skipped.

FUNCTIONS

hash($plain, $iterations?)

The stored string. Cost is the explicit argument, else the localizable $Punk::Auth::Password::ITERATIONS (default 60_000).

verify($plain, $stored)

True when the password matches. Constant-time over the derived key; an unparseable or undef stored value is simply false, so a user row with no password (an invited or federated-only account) can be handed straight in.

needs_rehash($stored, $iterations?)

True when the stored cost is below the current one, and true for anything unparseable or missing - failing toward a rehash is always safe, because the caller only acts on it after a successful verify.

token

32 random bytes as unpadded url-safe base64 - 43 characters. The plaintext half of a single-use token: mail it, never store it.

token_digest($token)

Lowercase SHA-256 hex of the token - the only form storage sees. Looking a token up by its digest means a leaked table cannot be replayed.

SEE ALSO

Punk::Auth, Punk::Session.

AUTHOR

LNATION <email@lnation.org>

LICENSE AND COPYRIGHT

This software is Copyright (c) 2026 by LNATION <email@lnation.org>.

This is free software, licensed under:

The Artistic License 2.0 (GPL Compatible)