NAME

Punk::Headers - security response headers

SYNOPSIS

package MyApp;
use Punk;

headers;                       # the safe default set

headers 'Content-Security-Policy'   => "default-src 'self'",
        'Strict-Transport-Security' => 'max-age=31536000',
        'X-Frame-Options'           => 'DENY';

headers 'X-Frame-Options' => undef;   # keep the rest, drop this one

DESCRIPTION

Adds a frozen set of response headers to everything the application sends, from inside the C dispatcher.

The decoration happens on the way out of the dispatcher rather than in an after-dispatch hook, for the reason Punk::CORS gives: 404 and 405 never build a context, and a browser reads a Content-Security-Policy off an error page exactly as it would off a real one. CORS preflight replies are covered too.

Set-if-absent

A header the response already carries wins, case-insensitively. A handler (or a hook, or CORS) that sets its own X-Frame-Options for one route has said something more specific than the application-wide policy, and the policy does not repeat or contradict it.

The defaults

The bare keyword ships only headers that are safe on any application:

X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin

Content-Security-Policy and Strict-Transport-Security are deliberately not defaults. A CSP invented by a framework breaks every inline script and style it has never seen, and HSTS is a commitment to serve HTTPS for as long as its max-age promises - months, on any value worth sending. Both belong to the application, spelled out.

THE KEYWORD

headers;                        # the default set
headers %pairs;                 # defaults, overridden and extended
headers \%pairs;                # the same, as a hashref
headers 0;                      # off

Keys are literal header names, values are literal header strings. A value of undef removes that header from the default set. A reference value croaks at keyword time. headers 0 turns the feature off.

It also reads a headers: block from config/punk.yml; headers: true is the bare form.

SCOPED POLICIES

my $admin = under '/admin' => $guard;
$admin->headers('X-Frame-Options' => 'DENY',
                'Referrer-Policy' => undef);

An under scope takes the same pairs and applies them only to requests under its prefix - ahead of the application-wide policy, so a scope can tighten a header, add one, or (with undef) drop one for its subtree. Scopes nest, and the longest prefix wins a name. Because the policy rides the response path rather than the route, a 404 under the prefix carries it too.

Order of precedence for one header name, most specific first: whatever the response already carries (a handler's own $c->header), the longest-prefix scope that mentions it, then the application-wide policy.

SEE ALSO

Punk, Punk::CORS, Punk::CSRF.

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)