v0.4.3
- Security: hardened HEAD sanitisation (XSS). Dynamic
HEADvalues/callables that interpolate loader data into raw head strings — the documented dynamic-meta-tags recipe — could previously inject markup because attribute values weren't escaped (a"in a<meta content>broke out of the attribute). Head elements are now parsed and rebuilt from a strict tag allowlist with every attribute value HTML-escaped,on*handlers dropped,javascript:/vbscript:/data:URLs neutralised,<meta http-equiv="refresh">and non-head tags rejected, and any markup injected after a breakout discarded. Inline<script>/<style>head content remains supported as trusted author code. Self-closing head tags now serialise canonically as/>. - Security: CSRF exempt paths match on segment boundaries.
csrf.exemptPathspreviously used a bare string prefix, so exempting/api/webhooksalso exempted an adjacently-named sibling like/api/webhooks-admin. Exemptions now match a path exactly or at a/boundary. - Security: oversized no-JS form POSTs fail loud. A form-encoded POST whose body exceeded the CSRF buffer cap (1 MB) was silently truncated and replayed to your handler. Such requests now return
413and ask for the token via theX-CSRF-Tokenheader instead of corrupting the payload. - Fix:
<Script>/<Image>boolean attributes written as strings.defer="false",async="no",priority="0",lazy="false"and similar quoted-falsy forms were coerced toTrue(the opposite of the source). They now coerce correctly. - Fix: custom
csrf.cookieName/csrf.headerNamenow reach the client runtime. Configuring either name made every action POST fail with403— the middleware set and validated the cookie under the configured name whileuseAction/<Form>kept reading a literalpyxle-csrfcookie and sending a literalx-csrf-tokenheader. Non-default names are now injected into the document shell aswindow.__PYXLE_CSRF_COOKIE__/window.__PYXLE_CSRF_HEADER__and the client runtime resolves them with the defaults as fallback, in both dev and production builds. - Fix: no double loader run on hover-then-click navigation. When a
<Link>was prefetched on hover/viewport and then clicked, the click raced a second request and ran the page's@serverloader twice. The click now reuses the in-flight prefetch, and a navigation-sequence token discards a superseded prefetch so a slow one can't render a stale page over a newer click. - Multi-core serving:
pyxle serve --workers N. Production serving can now run N independent server processes on one port (each with its own SSR pool and event loop) — throughput scales with cores, no load balancer or shared state to configure. See Deployment → Multi-core (worker processes). - Sync API endpoints. A plain
def endpoint(request)inpages/api/now runs in Starlette's threadpool instead of failing at request time — blocking database drivers and sync SDKs no longer require manualasyncio.to_threadwrapping. Syncget/post/… methods onHTTPEndpointclasses are threadpooled too, andHTTPEndpointclasses dispatch correctly alongside route hooks. See API Routes → Sync endpoints and blocking calls. - In-memory static asset cache. When serving a production build, small static files (≤1 MB each, 32 MB total per process) are loaded into memory at startup and served without filesystem I/O or threadpool hops — several-fold higher static throughput. Conditional requests (
ETag/If-Modified-Since→ 304) and cache headers behave exactly as before; larger files keep streaming from disk.
Full changelog: https://pyxle.dev/docs/changelog