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
value | str | The candidate URL, usually from |
allow | sequence of str | Accepted targets. |
default | str | Returned instead of raising when |
Raises
RejectedValueError | If the URL is not allowed and no |
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(".