Security headers
gebweb.useSecurity(app, opts) mounts the browser-security response
headers most apps want: a conservative default set plus opt-in CSP
(with per-request nonce expansion) and opt-in HSTS.
A single call wires three things:
- A response-phase middleware that writes the headers.
- A request-phase middleware that mints a fresh CSP nonce.
- A view-context injector that publishes the nonce as
cspNonceso templates can stamp it onto inline<script>/<style>tags.
Defaults
The defaults are conservative for an HTML app:
import gebweb;
let app = gebweb.app([HomeController()]);
gebweb.useSecurity(app, {});
Every response gets:
X-Content-Type-Options: nosniffX-Frame-Options: DENYReferrer-Policy: strict-origin-when-cross-origin
CSP and HSTS stay off unless asked. That keeps dev environments ergonomic; you opt in per environment.
Request body limits
Every app caps request bodies at 10 MB by default. Oversize requests
are rejected with 413 Problem Details before routing, and the cap
is also forwarded to the HTTP server (maxBodyBytes) so oversize
uploads are cut off at the socket instead of buffering. Adjust or
disable it app-wide, or tighten it per route:
gebweb.useMaxBodyBytes(app, 1024 * 1024); /* 1 MB app-wide */
gebweb.useMaxBodyBytes(app, 0); /* disable the cap */
class UploadController {
@Post("/avatar")
@MaxBody(256 * 1024)
func avatar(gebweb.Request req): dict<string, any> { ... }
}
A route @MaxBody can only tighten the app cap: requests larger than
the app-wide limit never reach the route.
Content Security Policy
CSP is described as a dict of camelCase directive names mapped to
lists of source expressions. The framework lowers camelCase to
kebab-case at render time (defaultSrc becomes default-src).
gebweb.useSecurity(app, {
"csp": {
"defaultSrc": ["'self'"],
"scriptSrc": ["'self'", "'nonce'"],
"styleSrc": ["'self'", "'unsafe-inline'"],
"imgSrc": ["'self'", "data:"],
"connectSrc": ["'self'"]
}
});
The literal string 'nonce' inside any directive list expands to
'nonce-<value>' with a fresh value minted per request. The
unexpanded form is what you write; the framework substitutes the
real value before the header is written.
Using the nonce in templates
The current request's nonce is auto-injected into every rendered
template as cspNonce:
<script nonce="{{ cspNonce }}">
console.log("hello from inline script");
</script>
Each request gets a different nonce, so cached HTML rendered with yesterday's nonce won't execute today. CSP plus per-request nonce is the strongest defense against reflected and stored XSS short of banning inline script entirely.
Report-only mode
While tightening a CSP, switch to report-only so violations log but don't break the page:
gebweb.useSecurity(app, {
"csp": { "defaultSrc": ["'self'"] },
"cspReportOnly": true
});
The header name switches to Content-Security-Policy-Report-Only.
Add a report-uri or report-to directive to receive violation
reports.
HSTS
HSTS (Strict-Transport-Security) tells browsers to never load this
host over plain HTTP for the configured duration. It's powerful and
sticky - once a browser remembers the header, it ignores
unencrypted requests to your domain for the full max-age. Don't
enable it in development.
gebweb.useSecurity(app, {
"hsts": {
"maxAge": 31536000,
"includeSubdomains": true,
"preload": false
}
});
Set preload: true only if you intend to submit the domain to the
HSTS preload list and have audited every subdomain.
Overriding the defaults
Suppress any default header by passing an empty string for its key, or set a custom value:
gebweb.useSecurity(app, {
"frameOptions": "SAMEORIGIN",
"referrerPolicy": "no-referrer",
"contentTypeOptions": false
});
Lower-level middleware factory
gebweb.securityHeaders({}) is the underlying middleware factory and
can be used directly when you only want the three default headers
without CSP / HSTS / nonce wiring. Prefer gebweb.useSecurity for
new code: it owns the per-request nonce, publishes cspNonce to
the view context, and centralises the configuration. The factory
is exposed for advanced setups that want to compose the middleware
into a custom pipeline.
Asset responses
Static assets served via gebweb.useStaticAssets short-circuit the
router before middleware runs, so they receive only their own
Content-Type and Cache-Control headers - not the security
headers. That is intentional: assets are the resources CSP guards,
and applying CSP to a JavaScript file's response wouldn't do
anything meaningful. If you serve untrusted user uploads, place
them behind a separate route under a different origin instead of
relying on response headers.