Liking cljdoc? Tell your friends :D

dev.arkaitz.web-base.security

Web security measures that are neither authentication nor authorisation (SPEC §15): response headers added when absent, the host's Content Security Policy with a per-request nonce, opt-in trust of a reverse proxy's headers, and CSRF protection through ring-anti-forgery's synchroniser token.

Written down so it is not rediscovered: htmx's hx-on, hx-vals js: and trigger filters need unsafe-eval or the hx-csp extension; a strict policy means doing without them, which the demo does.

Web security measures that are neither authentication nor authorisation
(SPEC §15): response headers added when absent, the host's Content Security
Policy with a per-request nonce, opt-in trust of a reverse proxy's headers,
and CSRF protection through ring-anti-forgery's synchroniser token.

Written down so it is not rediscovered: htmx's `hx-on`, `hx-vals js:` and
trigger filters need `unsafe-eval` or the `hx-csp` extension; a strict
policy means doing without them, which the demo does.
raw docstring

body-failureclj

(body-failure e)

The request body's own failure somewhere in e's cause chain, as data — {:status 413 :wb/limit :body} past its limit, {:status s :lost class-name} when the client did not send it — or nil. Marked where the body is read, so a failure of anything else that merely looks like Jetty's (an upstream client, a driver) is never mistaken for the client's.

The request body's own failure somewhere in `e`'s cause chain, as data — `{:status
413 :wb/limit :body}` past its limit, `{:status s :lost class-name}` when the client did not send it —
or nil. Marked where the body is read, so a failure of anything else that merely looks
like Jetty's (an upstream client, a driver) is never mistaken for the client's.
sourceraw docstring

check-rotated-renderclj

(check-rotated-render request document)

Logs, as rotate-token does, a document rendered for request — the one rotate-token returned — that carries the token from before the rotation: content built lazily by the handler reads the request it closed over, and only once the page is turned into HTML, after rotate-token looked. It finds the token as a form or an attribute writes it; one re-encoded on its way into the page, inside a script's JSON say, goes unseen.

Logs, as `rotate-token` does, a document rendered for `request` — the one
`rotate-token` returned — that carries the token from before the rotation: content
built lazily by the handler reads the request it closed over, and only once the page
is turned into HTML, after `rotate-token` looked. It finds the token as a form or an
attribute writes it; one re-encoded on its way into the page, inside a script's JSON
say, goes unseen.
sourceraw docstring

csrf-fieldclj

(csrf-field request)

The hidden input a classic form needs; htmx requests carry the header instead. Nothing when the request carries no token — under :csrf false there is nothing to send, and an empty field would only earn a 403.

The hidden input a classic form needs; htmx requests carry the header
instead. Nothing when the request carries no token — under `:csrf false`
there is nothing to send, and an empty field would only earn a 403.
sourceraw docstring

csrf-headerclj

source

csrf-metaclj

(csrf-meta request)

The <meta> a script reads the CSRF token from (since 0.22.0), for a page that calls the server from a script with no htmx attribute or form the shell would see: name csrf-token, the token as content, and the header to send it in as data-header, so the script spells neither. Reading it marks the token used, as a form does, so the shell also sends it from <body>. Nil when the session holds no token — an anonymous visitor's, which CSRF checks by origin instead — or under :csrf false, so no empty token is ever sent:

const m = document.querySelector('meta[name="csrf-token"]'); if (m) headers[m.dataset.header] = m.content;

The `<meta>` a script reads the CSRF token from (since 0.22.0), for a page that calls
the server from a script with no htmx attribute or form the shell would see: `name`
`csrf-token`, the token as `content`, and the header to send it in as `data-header`,
so the script spells neither. Reading it marks the token used, as a form does, so the
shell also sends it from `<body>`. Nil when the session holds no token — an anonymous
visitor's, which CSRF checks by origin instead — or under `:csrf false`, so no empty
token is ever sent:

  const m = document.querySelector('meta[name="csrf-token"]');
  if (m) headers[m.dataset.header] = m.content;
sourceraw docstring

csrf-meta-nameclj

The name of the <meta> csrf-meta renders, for a script to find it by.

The `name` of the `<meta>` `csrf-meta` renders, for a script to find it by.
sourceraw docstring

csrf-tokenclj

(csrf-token request)

The request's token, for the host's own markup, when its session holds one or a rotation is minting it; nil for an anonymous session, which wrap-csrf checks by origin instead (since 0.21.0).

The request's token, for the host's own markup, when its session holds one or a
rotation is minting it; nil for an anonymous session, which `wrap-csrf` checks by
origin instead (since 0.21.0).
sourceraw docstring

default-headersclj

source

default-max-body-bytesclj

The largest request body the base reads when the host names no other: 200 000 bytes, Jetty's own form limit — which never applied here, because Ring reads the body itself (wrap-params), so a single anonymous POST could fill the heap (measured: 1 GB raised it by 2 GB; four at once, an OutOfMemoryError).

The largest request body the base reads when the host names no other: 200 000 bytes,
Jetty's own form limit — which never applied here, because Ring reads the body itself
(`wrap-params`), so a single anonymous POST could fill the heap (measured: 1 GB raised
it by 2 GB; four at once, an OutOfMemoryError).
sourceraw docstring

privateclj

(private response)

response with Cache-Control: no-store unless it already says how it may be cached. For what is somebody's own — a signed-in page, a page carrying a session's CSRF token — which a shared cache must never hand to the next visitor, nor a shared computer keep.

The base already applies it to every response to a request with a subject or a session (gate/wrap-subject), so a host calls it only for what is somebody's and reached with neither: a file behind a credential in its URL, followed from a mail.

`response` with `Cache-Control: no-store` unless it already says how it may be cached.
For what is somebody's own — a signed-in page, a page carrying a session's CSRF token —
which a shared cache must never hand to the next visitor, nor a shared computer keep.

The base already applies it to every response to a request with a subject or a
session (`gate/wrap-subject`), so a host calls it only for what is somebody's and
reached with neither: a file behind a credential in its URL, followed from a mail.
sourceraw docstring

rotate-tokenclj

(rotate-token request)

request carrying a fresh CSRF token for a response that rotates the session, and the token recorded so the rotated session keeps exactly it. The render step calls it before turning such a response into HTML, so a login page's forms carry the token its new session holds. The request unchanged when it went through no CSRF, or carries no token. A token read before this — a handler whose own content called csrf-field — cannot follow the rotation, and is logged by name: here when it was read already, by check-rotated-render when lazy content reads it only as the page is rendered.

`request` carrying a fresh CSRF token for a response that rotates the session, and
the token recorded so the rotated session keeps exactly it. The render step calls it
before turning such a response into HTML, so a login page's forms carry the token its
new session holds. The request unchanged when it went through no CSRF, or carries no
token. A token read before this — a handler whose own content called `csrf-field` —
cannot follow the rotation, and is logged by name: here when it was read already, by
`check-rotated-render` when lazy content reads it only as the page is rendered.
sourceraw docstring

wrap-body-limitclj

(wrap-body-limit handler limit-for render)

Refuses a request body larger than (limit-for request) bytes with a 413 whose datum says :wb/limit :body: at once, unread, when its declared length says so; otherwise as it is read, whoever reads it. render answers the refusal made here, before anything else runs.

Refuses a request body larger than `(limit-for request)` bytes with a 413 whose datum
says `:wb/limit :body`: at once, unread, when its declared length says so; otherwise as
it is read, whoever reads it. `render` answers the refusal made here, before anything
else runs.
sourceraw docstring

wrap-csrfclj

(wrap-csrf handler render-error)

ring-anti-forgery inside the session. Every request not GET/HEAD/OPTIONS is checked, in one of two ways (since 0.21.0):

  • a session that holds a token — every signed-in one — needs it, read from the __anti-forgery-token form field or the X-CSRF-Token header, which the shell makes htmx send (the library also honours X-XSRF-Token);
  • a session that holds none — an anonymous visitor's, cart or not — needs the browser to say the request came from this site's own pages (from-this-origin?). A token is born only when the session rotates, at sign-in, so an anonymous page with a form writes no session row: a crawler costs nothing. A refusal is the base's own 403 datum, so an htmx swap receives a fragment and a navigation a page.

Reading :anti-forgery-token or ring-anti-forgery's dynamic var directly, or reading the token after the handler returned (a body built lazily later), gives a token that is never stored: the form built with it earns a 403.

ring-anti-forgery inside the session. Every request not GET/HEAD/OPTIONS is
checked, in one of two ways (since 0.21.0):
- a session that holds a token — every signed-in one — needs it, read from the
  `__anti-forgery-token` form field or the `X-CSRF-Token` header, which the shell
  makes htmx send (the library also honours `X-XSRF-Token`);
- a session that holds none — an anonymous visitor's, cart or not — needs the
  browser to say the request came from this site's own pages (`from-this-origin?`).
A token is born only when the session rotates, at sign-in, so an anonymous page with
a form writes no session row: a crawler costs nothing. A refusal is the base's own
403 datum, so an htmx swap receives a fragment and a navigation a page.

Reading `:anti-forgery-token` or ring-anti-forgery's dynamic var directly, or reading
the token after the handler returned (a body built lazily later), gives a token that
is never stored: the form built with it earns a 403.
sourceraw docstring

wrap-headersclj

(wrap-headers handler {:keys [csp] :as config})

Outer middleware: every response — routed, static, error — gets the security headers it lacks; a header the handler set is kept. Every request gets :wb/nonce; when the host configures :csp, {nonce} in it is replaced by that request's nonce and the policy is sent. :frame-options is DENY by default, a string to change it, false to omit it.

X-Frame-Options is for a browser without CSP. Where a CSP says frame-ancestors, it governs and X-Frame-Options is ignored (CSP Level 3), so relaxing framing through :frame-options alone sends both and changes nothing: change frame-ancestors too.

Outer middleware: every response — routed, static, error — gets the
security headers it lacks; a header the handler set is kept. Every request
gets `:wb/nonce`; when the host configures `:csp`, `{nonce}` in it is
replaced by that request's nonce and the policy is sent. `:frame-options`
is `DENY` by default, a string to change it, `false` to omit it.

`X-Frame-Options` is for a browser without CSP. Where a CSP says `frame-ancestors`, it
governs and `X-Frame-Options` is ignored (CSP Level 3), so relaxing framing through
`:frame-options` alone sends both and changes nothing: change `frame-ancestors` too.
sourceraw docstring

wrap-proxyclj

(wrap-proxy handler hops)

Trusts X-Forwarded-For and X-Forwarded-Proto for :remote-addr and :scheme, behind hops proxies the host runs, each APPENDING what it saw — nginx's $proxy_add_x_forwarded_for, AWS's load balancers, Heroku's and Fly's routers all do. The entry hops from the right is the address the outermost of them saw; everything to its left the client could have written. A header with fewer entries than hops leaves the socket's address, and a request that did not come through the proxies is never trusted beyond it. X-Forwarded-Proto is taken from its last entry whatever the count: proxies overwrite it rather than append, so it holds one value, the nearest proxy's. Only behind proxies the host controls: anyone else can send these headers.

Trusts `X-Forwarded-For` and `X-Forwarded-Proto` for `:remote-addr` and `:scheme`,
behind `hops` proxies the host runs, each APPENDING what it saw — nginx's
`$proxy_add_x_forwarded_for`, AWS's load balancers, Heroku's and Fly's routers all do.
The entry `hops` from the right is the address the outermost of them saw; everything
to its left the client could have written. A header with fewer entries than `hops`
leaves the socket's address, and a request that did not come through the proxies is
never trusted beyond it. `X-Forwarded-Proto` is taken from its last entry whatever the
count: proxies overwrite it rather than append, so it holds one value, the nearest
proxy's. Only behind proxies the host controls: anyone else can send these headers.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close