Unreleased [Not released yet]
Part 11 of 11. Previous part
Security (99)
- AWS Lambda WebSocket: a handler that sends before the code answers
$connectcan hold 1 MiB at most. Past it,sendfails withGone. 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 itsUid:line, the server stops. Before, it logged a line and went on as root. On a system that is not Linux, a server withWISP_USERset stops too.src/hooks.rsrefuses 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,PRECACHEandTRAILING_SLASH. Before,const SIGNED_IN: bool = true;there built without an error and did nothing, so every page stayed open. Check inbeforewithcx.signed_in()?. TheCSRFmessage says to set it in the+server.rswhose 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. Withext, a name with backslashes could also write a file outsideout. - 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.0equals1, 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 (codetoo_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) atw=3840needed 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/8and[::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.xmlandwisp::alternatesuse it. The OAuth endpoint check and the libSQL token check take onlylocalhost,127.0.0.1and[::1]. Before, each place had its own rule. The libSQL driver tookhttp://localhost:1@db.example/with a token, and the host of that URL isdb.example. The cookie check tooklocalhost:1@evil.example,.localhost,[0:0:0:0:0:0:0:1],[::1]eviland127.0.0.1:80:81as this machine. Now these hosts are not local, so their cookies getSecure. The dev endpoints tooklocalhost.,.localhost,[127.0.0.1]and[0:0:0:0:0:0:0:1]. The dev OAuth callback tooka@b.localhost, and the OAuth endpoint check tookhttp://127.0.0.1234/. A name under.localhosthas only labels of letters, digits and-. A label is not empty and has no-at its start or end. Thewisp devevents stream takes onlylocalhost,127.0.0.1and[::1]with this port rule. Before, it tooklocalhost:123456. Thewisp devfront andwisp preview --staticuse the rule for a name, and also take any IP address. In dev, the base of a host under.localhostor in127.0.0.0/8now useshttp, nothttps.LOCALHOST:3000now useshttp, andlocalhost:abcnow useshttps. WISP_ALLOWED_HOSTSnow holds on the edge targets (node,deno,bun,cloudflare). Before, only the native server checked theHostof a request, and an edge build answered everyHost. Oncloudflare, the files ofstatic/come from Cloudflare and skip the check. Thepages,vercel,netlifyandazurehosts also serve those files before the app. Not tested on those hosts.- On the edge, a page or a trailing-slash redirect with a
corsrule is notconst: each request enters the wasm. Before, the bridge kept the first answer for the path and gave it to each later request. So theaccess-control-allow-originof one site went to requests from other sites, and to requests with noorigin. A first request with nooriginalso made the path answer allowed sites with noaccess-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
headersrule that has a cookie or header condition is notconst: 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) orwisp devsource (https://esm.sh, the reload events) to it. Before,wisp::csp("style-src 'none'")becamestyle-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;sandboxadded a directive to the CSP header. The error now lists;and,with the other characters that it refuses.- The
upstash,vercel-blobands3:stores send their token over https only, as libSQL does. Plain http is forlocalhost,127.0.0.1and[::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 anyhttp://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.envworks like the same variable in the process. Before, Wisp read only the process, ignored a value in.envand listened on TCP atHOSTandPORT, 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.jsput two lines in the list, and the next export deleted a file of yours namedy.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}, astylepair of a spread and the first paint ofstyle={:s}refuse a declaration whose name isbehavior,-moz-bindingor-ms-behavior, in any case. This is a precaution: they are old engine features that load code from a URL. A dev build logswisp: style:NAME=VALUE is not a plain CSS declaration. Wisp skipped it. Before, such a declaration stayed when its value was plain.- A quoted
styleattribute fails the build for a${}in the name of a declaration:style="-moz-${x}:v",style="${a}${b}:v"andstyle="a:b;${c}". The error isa${}in astyleattribute must come aftername:. 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-bindingtogether. A hole aftername: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&,<and>, because it cannot tell where the name starts. live.jsrefuses, in astyleattribute that the page sets (style={:s}as a string or an object, a quoted value with{:…}, and a spread), each declaration whose name isbehavior,-moz-bindingor-ms-behaviorin 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.jsis 107 bytes larger when minified and gzipped (12_381 to 12_488). Before, only the first paint of the server refused these names.