NAME

Plack::Middleware::ProofOfWork - Proof-of-Work based bot protection for Plack applications

SYNOPSIS

use Plack::Builder;

builder {
    enable "ProofOfWork",
        difficulty => 4,
        cookie_name => 'pow',
        cookie_duration => 5;
    $app;
};

DESCRIPTION

Plack::Middleware::ProofOfWork implements a Proof-of-Work mechanism to protect against automated requests (bots, scrapers, etc.). Legitimate browsers must solve a computationally intensive task before accessing the application.

The middleware uses SHA-256 hashing and requires clients to find a nonce that results in a hash with a specified number of leading zeros.

CONFIGURATION

difficulty

The number of leading zeros in the hash (default: 4). Now supports fractional values for finer granularity (e.g. 4.5). Each additional zero increases difficulty by a factor of 16.

difficulty => 4    # ~65,000 attempts on average
difficulty => 4.5  # ~185,000 attempts on average
difficulty => 5    # ~1,000,000 attempts on average

Fractional difficulty enables finer gradations between the exponential steps of integer difficulties.

Name of the cookie for the Proof-of-Work token (default: 'pow').

Cookie validity duration in days (default: 5).

bot_patterns

Hash-ref of bot types with DNS verification patterns (default: Googlebot, Applebot, Bingbot).

bot_patterns => {
    'googlebot' => qr/crawl.*google\.com$/,
    'mybot'     => qr/mybot.*example\.com$/,
}

The hash keys are used for User-Agent matching (case-insensitive). The regex values are used for reverse DNS hostname verification.

bot_verification_level

Level of bot verification (default: 2):

0 = No bots allowed (all bots blocked, regardless of verification)
1 = User-Agent only (simple string matching)
2 = Reverse DNS (hostname must match pattern) - DEFAULT
3 = Full DNS roundtrip (reverse + forward DNS must match)

bot_verification_level => 3

Set to 0 to block all bots (including search engines). Level 2 (default) provides good security with reasonable performance. Level 3 provides the most security but may slow down first requests from bots due to additional DNS lookups.

bot_dns_timeout

Timeout in seconds for DNS lookups during bot verification (default: 0.5).

bot_dns_timeout => 1.0

Forward DNS lookup uses 2x this timeout.

timestamp_window

Time window in seconds for timestamp rounding (default: cookie_duration * 86400).

js_file

Path to the JavaScript file for Proof-of-Work calculation. If not specified, the bundled default file is used.

js_file => '/path/to/my/pow.js'

The JavaScript file is automatically loaded from the installation (via File::ShareDir). In v0.05 there is no inline fallback - the file must exist.

html_file

Path to the HTML template file for the challenge page. If not specified, the bundled default file is used.

html_file => '/path/to/my/challenge.html'

The HTML template must contain the placeholder <!-- POW_JAVASCRIPT --> where the JavaScript API and pow.js content will be inserted.

This allows complete customization of the challenge page UI.

css

Custom CSS to inject into the challenge page template. The CSS is inserted at the <!-- POW_CSS --> placeholder in the HTML template's style section.

css => '.spinner { border-color: red; }'

This allows easy styling customization without replacing the entire HTML template.

LOGIC FLOW

The middleware checks Proof-of-Work in the following order:

  1. Check if a PoW cookie exists

  2. If cookie exists: Validate it - Valid → Allow request through - Invalid → Require new PoW (even for bots)

  3. If no cookie: Check if request is from a known bot - Is bot and allow_bots=1 → Allow through - Not a bot or allow_bots=0 → Require PoW

This ensures that even bots with invalid cookies must solve the PoW again.

JAVASCRIPT API

The pow.js script receives the following constants and functions from the middleware:

Provided Constants

const DIFFICULTY        // Number: difficulty (e.g. 4 or 4.5)
const POW_COOKIE_NAME   // String: cookie name
const COOKIE_DURATION   // Number: cookie validity in days

Provided Functions

function getSourceValue()  // Returns: String with the source value
                           // Format: "UserAgent|Timestamp|Language|Host"

This API is inserted as a prefix before the actual pow.js script.

Required Functions in pow.js

The pow.js script MUST implement the following functions:

async function sha256(message)              // SHA-256 hash calculation
function hasLeadingZeros(hash, full, div)   // Validation with fractional difficulty
function setCookie(name, value, days)       // Set cookie
async function computeProofOfWork()         // Main PoW function

See share/pow.js for the reference implementation.

HOW IT WORKS

  1. A client without a valid PoW cookie receives an HTML page with JavaScript.

  2. The JavaScript calculates a Proof-of-Work based on User-Agent, language, host, and timestamp.

  3. After successful calculation, a cookie is set and the page reloads.

  4. The middleware validates the cookie and passes the request through.

SECURITY CONSIDERATIONS

  • Difficulty should be high enough to slow down automated requests, but low enough not to frustrate legitimate users.

  • The middleware uses User-Agent, Accept-Language, and Host as part of the challenge to prevent token reuse across different contexts.

  • Timestamps are rounded to the cookie window to ensure stable challenges.

AUTHOR

Your Name <your@email.com>

LICENSE

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

SEE ALSO

Plack::Middleware, Digest::SHA, File::ShareDir