livelink (GitHub, documentation) turns R code into a URL that runs it in the browser. There is no compute server. A static host still serves the page, webr.r-wasm.org or a localhost preview if you run your own, but nothing at the far end executes R. The reader’s browser does that, and the code travels in the part of the URL that browsers never send anywhere.
Version 0.1.1 is on CRAN, a week after 0.1.0. Three changes account for most of it:
- Braced expressions failed when run from Positron’s editor panel. Typed at the console, the same code worked.
- A decoded link could write outside the directory you named.
- webR links now use msgpack, the form webR’s own share button writes, which fits more code into the same URL.
Nothing already shared stops working. Links made by 0.1.0 still open, and still decode.
Positron names an unsaved buffer differently
Braced expressions failed in Positron. Not everywhere in Positron, which is what made it worth chasing. Put the code in an editor panel, press Run, and it failed outright:
link <- webr_repl_link({
data(mtcars)
plot(mtcars$mpg, mtcars$wt)
})
#> Error in if (grepl("^\\s*#", rescue_me)) ... : argument is of length zeroType the same thing at the console and it worked.
Braces are the nicest way to hand code to livelink. No quotes to balance, no newlines to escape, and your editor keeps highlighting the code. They work because R attaches a source reference to whatever it parses, a note saying where the code came from, so the text can be read back later with the comments still in it.
That note is what differs between the two paths. Console code arrives with its text attached, so there is always something to read back. Editor code arrives with a name instead, and Positron calls an unsaved buffer untitled:Untitled-1. That is not a path. So the reference pointed at nothing readable, livelink asked for the source regardless, and got an error where it expected code. RStudio sends the text along with the reference, which is why the question never came up there.
Fixing that turned up three more ways a reference can outlive its source. A file shorter than the block it was parsed from. An empty { }. A file edited since the expression was read. All four now fall back to rebuilding the code from the expression itself, which loses the comments but never the code.
A link could write outside the folder you asked for
decode_webr_link() and decode_shinylive_link() take a link and write the files it carries to disk. Those file names come from inside the link, which is to say from whoever made it. That much is unavoidable, since decoding a link is how you open one a stranger sent you.
What was avoidable was using the names exactly as given. A link naming a file ../../.Rprofile escaped the directory you asked for and landed on a file R runs every time it starts. Worse, the summary reported success:
decode_webr_link(url, output_dir = "~/projects/decoded")
#> v Successfully decoded 1 file to '~/projects/decoded'The directory was empty. The file had gone somewhere else entirely.
This is Zip Slip, the problem that turns up wherever an archive’s own entry names are trusted. In 0.1.1 a name that would leave output_dir is refused:
decode_webr_link(url, output_dir = "~/projects/decoded")
#> ! Unsafe file name, skipping: '../../.Rprofile'Names holding an ordinary subdirectory, like R/helpers.R, still work, so a multi-file project extracts exactly as before.
Two limits on the scope, since a security note that overstates itself is its own kind of problem. Nothing livelink produced could carry such a name, because every encoder runs names through basename(). And preview_webr_link() was never affected, because it writes nothing at all. The advice in the documentation, look before you extract, turns out to have been the right advice.
Smaller fixes
One theme runs through most of the rest. A link that looked fine when you built it and did nothing when you opened it is a miserable thing to debug from the far side of a URL, so the checks moved earlier. Several file paths handed to a single script, a file that is not valid UTF-8 or holds an embedded NUL, NA, and empty input now stop you where you build the link rather than where somebody else opens it.
The remainder, briefly:
- A binary file in a link is written back as it was, not as text listing its byte values.
shinylive_directory()includes the.cssand.jsan app needs.- Links built against a webR site you host yourself can be read back, which the URL check used to refuse.
- Several error messages name the value at fault instead of failing with
missing value where TRUE/FALSE needed.
The news entry below has all of it, and every line that changed is in the v0.1.0 to v0.1.1 comparison.
Acknowledgements
Thanks to George Stagg for webR and its browser REPL, and to Winston Chang for the Shinylive share-URL feature. livelink writes the formats they built and opens in the runtimes they maintain. The msgpack change in this release is livelink catching up to what webR was already doing.
livelink news file entry for version 0.1.1
Security
decode_webr_link()anddecode_shinylive_link()now refuse file names that escapeoutput_dir, such as../../.Rprofile. Names holding a subdirectory, likeR/helpers.R, are unaffected.
Encoding
- webR links now use msgpack rather than JSON, so a link ends in
mzwhere it used to end injz. This is the same form the webR share button writes. A one-line snippet comes out about 11% shorter, and links you have already shared keep working, since both forms still decode.
Bug fixes
webr_repl_link()now handles a braced expression whose code cannot be read back, which failed in an unsaved buffer in Positron, when the file was shorter than the expression, and when the block was empty. Where the code can be read its comments survive, and where it cannot the code is rebuilt without them.webr_repl_link()andwebr_repl_project()now refuse input that cannot become a script, rather than building a link that will not open. That covers several file paths given to a single script, a file that is not valid UTF-8 or holds an embedded NUL,NA, and empty input.webr_repl_project()now warns whenautorun_filesnames a file webR cannot run on opening, andprint()marks the files that really do run.decode_webr_link()writes a binary file back exactly as it was, rather than as text listing its byte values.decode_webr_link()andpreview_webr_link()now read links built against a webR site you host yourself, and report a malformed link when the ending of the link does not describe the code inside it.shinylive_directory()now includes the.cssand.jsfiles an app needs, leaves out only files that cannot be read as text, and checksapp_file.link.only = TRUEno longer leaves an empty code block above the link.
Error messages
webr_repl_project()andshinylive_project()now explain a missinginputinstead of reporting the name of an internal variable.Errors about an invalid argument now name the value at fault.
NAgiven toversion,panels, ormodefailed withmissing value where TRUE/FALSE needed, an invalidpanelsdid not say which component was wrong, andNULLleft a blankYou provided:line.preview_webr_link()andpreview_shinylive_link()now keep the explanation from the underlying error rather than replacing it.
Documentation
- Every help page now lists what a returned object holds, one entry at a time, rather than describing it only as an object with metadata.
References
- livelink on GitHub
- livelink documentation
- Everything that changed from v0.1.0 to v0.1.1
- The v0.1.0 release post
- Data Science as a Reproducible Link, on how the links work
- webR
- George Stagg, webR and its browser REPL
- Shinylive
- Winston Chang, the Shinylive share-URL feature
- RcppMsgPack, which reads and now writes the msgpack
- Zip Slip, the same problem in archives