Changelog

All versions and downloads Jump to Added | Changed | Fixed | Security

Unreleased [Not released yet]

Part 11 of 11. Previous part

Security (99)

  • AWS Lambda WebSocket: a handler that sends before the code answers $connect can hold 1 MiB at most. Past it, send fails with Gone. Each send also gives the deadline timer a turn. Before, a loop of sends grew memory until Lambda stopped the invocation.
  • WISP_USER: the check for root reads the user id in /proc/self/status. Before, it used the owner of /proc/self, which is root for a process that cannot be dumped (a binary that the user cannot read). The server then started itself again without end. When Wisp cannot read the file or its Uid: line, the server stops. Before, it logged a line and went on as root. On a system that is not Linux, a server with WISP_USER set stops too.
  • src/hooks.rs refuses a knob of a page or a layout: SIGNED_IN, TIMEOUT, BODY_LIMIT, CACHE, CACHE_PUBLIC, CACHE_STALE, CACHE_TAGS, PRERENDER, COMPRESS, SSR, CSR, RUNTIME, CSRF, EARLY_HINTS, PRECACHE and TRAILING_SLASH. Before, const SIGNED_IN: bool = true; there built without an error and did nothing, so every page stayed open. Check in before with cx.signed_in()?. The CSRF message says to set it in the +server.rs whose requests it lets through.
  • Build plugins refuse an input file name with a backslash or a control character (a newline). Before, such a name could make the build delete a file of the app (Cargo.toml) in a later build. With ext, a name with backslashes could also write a file outside out.
  • A PUT or PATCH of a #[derive(Rest)] row gives the #[json(skip)] fields of a list item only to the old item that equals it in all shown members. Before, it gave them by position, so a delete or a reorder of list items moved a secret to another item. The match uses the typed value (1.0 equals 1, a member left out equals its default). An item that changed or is new gets the default. A PUT or PATCH of a list with too many changed items to match is a 413 (code too_many), not a reset.
  • An image resize that needs more than 320 MB of scratch memory is now a 400 with not an image Wisp can resize, before Wisp decodes the picture. The scratch is 16 bytes for each pixel of the source width by the new height. Before, a PNG of 3841 by 10413 pixels (a few KB) at w=3840 needed 640 MB of scratch. Many such requests at once could stop the server for lack of memory.
  • One rule decides whether a host names this machine. localhost, a name under .localhost, 127.0.0.0/8 and [::1] count, with at most : and 1 to 5 digits after them. The dev endpoints, the dev OAuth callback, the plain-http cookie check and the dev base of /sitemap.xml, /robots.txt, /feed.xml and wisp::alternates use it. The OAuth endpoint check and the libSQL token check take only localhost, 127.0.0.1 and [::1]. Before, each place had its own rule. The libSQL driver took http://localhost:1@db.example/ with a token, and the host of that URL is db.example. The cookie check took localhost:1@evil.example, .localhost, [0:0:0:0:0:0:0:1], [::1]evil and 127.0.0.1:80:81 as this machine. Now these hosts are not local, so their cookies get Secure. The dev endpoints took localhost., .localhost, [127.0.0.1] and [0:0:0:0:0:0:0:1]. The dev OAuth callback took a@b.localhost, and the OAuth endpoint check took http://127.0.0.1234/. A name under .localhost has only labels of letters, digits and -. A label is not empty and has no - at its start or end. The wisp dev events stream takes only localhost, 127.0.0.1 and [::1] with this port rule. Before, it took localhost:123456. The wisp dev front and wisp preview --static use the rule for a name, and also take any IP address. In dev, the base of a host under .localhost or in 127.0.0.0/8 now uses http, not https. LOCALHOST:3000 now uses http, and localhost:abc now uses https.
  • WISP_ALLOWED_HOSTS now holds on the edge targets (node, deno, bun, cloudflare). Before, only the native server checked the Host of a request, and an edge build answered every Host. On cloudflare, the files of static/ come from Cloudflare and skip the check. The pages, vercel, netlify and azure hosts also serve those files before the app. Not tested on those hosts.
  • On the edge, a page or a trailing-slash redirect with a cors rule is not const: each request enters the wasm. Before, the bridge kept the first answer for the path and gave it to each later request. So the access-control-allow-origin of one site went to requests from other sites, and to requests with no origin. A first request with no origin also made the path answer allowed sites with no access-control-allow-origin. This applied to Workers, Pages, Deno.serve, Bun.serve, Netlify Functions, Netlify Edge and Vercel Edge.
  • On the edge, a page or a trailing-slash redirect with a headers rule that has a cookie or header condition is not const: each request enters the wasm. Before, the bridge kept the first answer for the path. So the header for one visitor's cookie went to each later request, or a later request with the cookie did not get it. This applied to the same hosts.
  • A CSP directive that is 'none' stays 'none'. Wisp adds no nonce, asset host (WISP_ASSET_BASE) or wisp dev source (https://esm.sh, the reload events) to it. Before, wisp::csp("style-src 'none'") became style-src 'none' 'nonce-...'. A browser ignores 'none' next to another source, so the directive allowed that source.
  • WISP_ASSET_BASE (assets) with a ; or a , is a build error. Before, https://a.example;sandbox added a directive to the CSP header. The error now lists ; and , with the other characters that it refuses.
  • The upstash, vercel-blob and s3: stores send their token over https only, as libSQL does. Plain http is for localhost, 127.0.0.1 and [::1]. Else the first call answers a 500: a token goes over https only (plain http is for localhost). s3: checks it only with a session token. Before, these stores sent the token in clear text to any http:// host.
  • Plain http to an s3: host that is not this machine sends the request bodies and the signatures in clear text. Use https.
  • WISP_LISTEN=unix:<path> in .env works like the same variable in the process. Before, Wisp read only the process, ignored a value in .env and listened on TCP at HOST and PORT, so an app that expected a proxy on a socket could listen on a public port.
  • The export refuses to write a file through a symlink or junction in the output folder, as the sweep already refused to delete through one. Before, the write followed the link and wrote the file outside the folder.
  • The export refuses to write a path with a control character (a line break, for example) and fails with the export refuses to write "<path>": the path has a control character. The sweep ignores a line of the export list that has one. Before, a page that named an asset such as /_app/c/x%0Ay.js put two lines in the list, and the next export deleted a file of yours named y.js. A list that an older export wrote with such a path holds two ordinary lines. The sweep cannot tell them apart from other lines.
  • style=${s}, a style pair of a spread and the first paint of style={:s} refuse a declaration whose name is behavior, -moz-binding or -ms-behavior, in any case. This is a precaution: they are old engine features that load code from a URL. A dev build logs wisp: style:NAME=VALUE is not a plain CSS declaration. Wisp skipped it. Before, such a declaration stayed when its value was plain.
  • A quoted style attribute fails the build for a ${} in the name of a declaration: style="-moz-${x}:v", style="${a}${b}:v" and style="a:b;${c}". The error is a ${} in a style attribute must come after name:. A property name must be text in the file. Write style="color:${value}" or style=${declarations}. Before, each hole had its own check, and two values that each passed it could write the name -moz-binding together. A hole after name: is as before. The build also refuses a hole after a comment with no end in the same value, and after an entity other than &amp;, &lt; and &gt;, because it cannot tell where the name starts.
  • live.js refuses, in a style attribute that the page sets (style={:s} as a string or an object, a quoted value with {:…}, and a spread), each declaration whose name is behavior, -moz-binding or -ms-behavior in any case, or has a \ or a /* in its name (a CSS escape or a comment). This is a precaution. It reads each ; as the end of a declaration, so it can refuse a part of a quoted value that looks like such a declaration. It keeps every other part as it is, also a data URL with a ; in a quote. The server's first paint refuses a value with ;, so it leaves such a data URL out. live.js is 107 bytes larger when minified and gzipped (12_381 to 12_488). Before, only the first paint of the server refused these names.

Source: CHANGELOG.md