Ushka

API reference

ushka.guard.safe

Explicit sanitisers: the only sanctioned way to turn Untrusted into trusted.

Each function validates or transforms its input for one specific use and returns a plain, trusted value. When the input cannot be made safe they raise RejectedValueError (a ValueError), or return default when one is given. A web framework should answer a RejectedValueError with 400 Bad Request: it means the client sent something unacceptable, not that the server broke.

Usage:

from ushka.guard import safe

return redirect(safe.url(request.query.get("next"), allow=["/"], default="/"))

class RejectedValueError(ValueError)

The client-supplied value could not be made safe for its intended use.

def plain(value: str) -> str

Returns an exact str copy of value, dropping any taint mark.

Internal building block; application code should use a validating sanitiser or trust() so that the reason is visible in review.

def html(value: Any) -> str

Escapes value for HTML text and quoted attributes.

Returns a markupsafe.Markup when MarkupSafe is installed, so Jinja2 and other HTML builders will not escape it a second time.

def url(value: Any, allow: Sequence[str] = ('/',), default: Any = _MISSING) -> str

Validates a redirect target.

Parameters

valuestr

The candidate URL, usually from ?next=.

allowsequence of str

Accepted targets. "/" accepts same-site paths (/account), which is the safe default. An origin such as "https://pay.example.com" accepts absolute URLs on exactly that scheme and host.

defaultstr

Returned instead of raising when value is rejected or missing.

Raises

RejectedValueError

If the URL is not allowed and no default was given.

def path(value: Any, root: str | Path, default: Any = _MISSING) -> Path

Resolves value inside root, rejecting anything that escapes it.

Symlinks are resolved before the check, so a link pointing outside root is rejected too. The returned path may not exist.

def integer(value: Any, min: int | None = None, max: int | None = None, default: Any = _MISSING) -> int

Parses a base-10 integer, optionally bounded. bool is rejected.

def choice(value: Any, options: Iterable[T], default: Any = _MISSING) -> T

Returns the element of options equal to value.

The returned object is your option, not the client's string, so it is trusted by construction. Ideal for sort orders, column names, enums.

def trust(value: str, reason: str) -> str

Marks value as trusted without validating it.

reason is mandatory and should say why this is safe, so the decision is visible in code review and easy to audit with grep "trust(".