34 comments

[ 4.2 ms ] story [ 78.3 ms ] thread
Should web pages have this kind of API? We seem to be repeating the mistakes of activeX but this time with JavaScript.
I think a "web app" should definitely be able to have some sort of more properly integrated filesystem API, but a "web page" has no business having access to such a thing. There just hasn't been a line drawn in the sand between the two, so every "web page" has all the capabilities of a "web app" by default.

Personally I wish there would be a meaningful line drawn between the two so that users could have a nice shorthand for allowing web pages to "upgrade" into apps which have access to things like WebGL and filesystem access. Such a thing would only have any meaning to power users and privacy oriented people though, and the general trend in browser design has been to spurn such users in favor of reducing friction for everyone else at all costs.

That line was blurred long ago, and the distinction between an "app" and a "page" is irrelevant today. Even news articles these days sometimes contain interactive elements like WebGL visualizations.

Personally, even though every new API inevitably gets abused, I don't think we should throw the baby out with the bathwater because there are tons of legitimate uses here. (In fact, I'm currently building something as a side project that would benefit greatly from these file system APIs.) My own worry is about complexity creep—specifically, the number of things I'll be expected to know and keep track of as a frontend engineer in another 10 years. But that's probably just me getting older. :)

If web apps are fully sandboxed by default as today, then presenting the user a UI for a web page wanting to upgrade to a (still sandboxed permissionless) web app seems like a waste of the user's attention. Why should the user see a prompt just because a webpage wants to do some WebGL visualization (that doesn't put any of the user's data at risk)? It seems like the perfect recipe to lead to user apathy to permission dialogs and users clicking to allow permissions automatically, because most of the dialogs are for nothing, but then the user may be taught to click through actual important dialogs just as automatically. I'm reminded of when IE used to warn the user about secure connections.
> If web apps are fully sandboxed by default

Are they, though?

If they were, then tracking users via third-party cookies and other resources wouldn't be possible. Nor would it be possible for a web site in my browser to suddenly start taking up all of my CPU/RAM due to a programming error or malicious site such as a crypto-miner. For the relatively little isolation that does happen, sandbox-escape vulnerabilities seem to be getting discovered all the time.

Also, as a technical user, I want more control over what web sites can do with my computer than a non-technical user might.

The more holes you poke in a sandbox, the worse a sandbox it is.

Third-party cookies seem to be on the way out thankfully. I agree that there should be a permission necessary (or at least some much better heuristic) for allowing a webpage to use too much CPU/memory.
> Why should the user see a prompt just because a webpage wants to do some WebGL visualization (that doesn't put any of the user's data at risk)?

Probably because there's no way to say that it "doesn't put any of the user's data at risk". WebGL has been abused for browser fingerprinting which itself puts user's privacy at risk, but it also has a long history of very nasty vulnerabilities and exploits. It's been fully disabled in my browser for years because of the security issues.

But WebGL does put the users data at risk. WebGL is an attack vector for fingerprinting which is data about the user, that gets correlated with other data about the user to stalk them.
Web browsers are not bulletproof. Their sandboxing implementations depend on OS features, which vary. Check a CVE database for your favorite browser and you'll find plenty of historic holes to dig through.

WebGL is one of the biggest fingerprinting vectors on the modern Web platform, and expands browser attack surface significantly. Most webpages should absolutely not have access to privileges like this.

Nearly all of the security and privacy problems we have with the World Wide Web today was because it went from a content-delivery platform (with deliberately limited interactivity) to a fairly complete app-delivery platform.

Javascript isn't the new Java. Web browsers are the new Java.

I would be very much in favor of a way to draw a line between "content" on the web and "apps" delivered by the web. I don't know what form that would take. But it will probably never happen because the FAANGs that run the web these days are actively opposed to any way to deliver content over the web that doesn't also let them include apps to track your activities online.

Maybe some kind of plain text machine-parsable-but-human-readable "protocol" that "applications" could use to specify the "content type" of some "resource" they want to "get"...
We already have this system.

> foobar.com wants to access your location. [Block] [Allow]

> foobar.com wants to access your camera and microphone. [Block] [Allow]

> foobar.com wants to send push notifications. [Block] [Allow]

Ideally these prompts are presented above the line of death, and clicking “Block” prevents future prompts, so you can’t get spammed.

Of course users click on these prompts without caring. Of course websites may try to unnecessarily block access if you dont agree. Of course websites make their own obviously fake prompts so you click “block” and then they present the obviously fake prompt again just to waste your time.

But users already download and open random files and grant them admin privileges, and websites already spam you. The current notification system works and extending it to WebGPU and file systems is natural.

Exactly my thoughts when the pitchforks come out over advances in browser tech. Arguments denouncing such progress eventually leave me feeling like banning personal computers and only allow communication via physical paper would be the only way to satisfy the nay-sayers.
Came here to say this too. Principle of least privilege doesn't exist here, even with the browser prompting for access to files, people are going to make mistakes and upload their entire C: directory by accident. Reminds me of Kazaa where you could essentially browse the contents of a person's hard-drive because they configured the wrong folder.
This API can allow applications that work with files to be made as strongly-sandboxed web apps instead of unsandboxed applications. If the API didn't exist and the user had to download an unsandboxed application to work with a file, then the unsandboxed application will get access to their whole C: drive without the user needing to make any mistakes.
> This API can allow applications that work with files to be made as strongly-sandboxed web apps instead of unsandboxed applications

That's possible today using the browser's existing file-handling APIs. As the author points out, there is a hurdle—and possible annoyance—from the user's perspective, which is that rather than pressing Cmd+S (or whathaveyou) and having that silently "just work" (i.e. as it does any any traditional desktop app), the user is kept in the loop during the entire process, from placement (i.e. which directory to save the file to), to having to explicitly grant the ability to overwrite the existing file if one already exists with that name.* Despite the fact that it's possible, however, few application authors have taken advantage of the opportunity. I don't expect this to change much with file access APIs.

The truth of the matter is that the majority of contemporary web app authors like having the opportunity of being the gatekeepers for the user's data—even when the other party 99% of the time is the user themselves.

What's most likely to happen is that similar numbers of web app authors continue not to use these APIs as we see today, partly because the API is too complicated. Where we will see it used will be instances where the net effect to the user is further loss of control over their data.

I'd be in favor of a drastically simplified API that was narrowly created to address the ergonomics issues of the file overwrite problem, and to encourage browser makers to limit further file access API expansion to applications that are themselves accessed with the `file` URI scheme. That is, rather than navigating to hosted web app and granting it permissions for file access, in addition to saving your data to your local disk, you're given a static copy of the web app, too. You're able to open this from disk and it is this copy to which you are allowed to grant file access permissions. If it's true that developers really do care about these file access APIs for the purpose of enabling users to exercise greater control over their data, then they should have no issue making this concession as a show of good faith.

* A simple workaround to this is to not try to overwrite the original, but instead save a new file that sits beside the old one. The application author can help out here by automatically generating a "tag" included in the suggested file name, so when the system filepicker is called up, the user just presses Enter and is never bothered with the overwrite prompt. Many people end up creating their own ad hoc versioning schemes ("final final final") at some point, anyway, so this constraint—which is on the surface considered more onerous than the status quo for typical desktop apps—can actually lead to a workaround that is much nicer to work with than the status quo. (Food for thought: if we reached a stable equilibrium concerning the conventions on this, OS vendors could bake awareness in somewhere just above the filesystem level and kill of "final final final" completely—and expose all sorts of useful services that would not otherwise be possible with the ad hoc schemes.)

It’s not possible to grant access to C:
(comment deleted)
The spec explicitly allows for browsers to reject paths which are too sensitive or dangerous, even if the user selects it. They explicitly list the entire Downloads directory as one such example (while also saying individual files from said directory are ok).
The gotcha is this (quote):

> Note: What is referred to as the "local file system" in this spec, does not have to strictly refer to the file system on the local device.

So basically a browser can implement this using a KV-Database or similar, nothing requires the browser to actually allow you to (file picker) "pick" files in your home directory or similar especially given that:

> Note: While user agents will typically implement this by persisting the contents of this origin private file system to disk, it is not intended that the contents are easily user accessible. Similarly there is no expectation that files or directories with names matching the names of children of the origin private file system exist.

Also

> The origin private file system is a storage endpoint whose identifier is "fileSystem", types are « "local" », and quota is null.

So this "file system" might exist in a complete "parallel universe" to your normal file system.

Also given that this is not a new storage type it means that if you browser is setup to e.g. clear local storage from a specific origin if that origin wasn't used for a month this might still apply.

So have fun with you "files" having disappeared after some longer holidays (e.g. on Safari, at least the way Apple planed to implement it a while ago).

So while it looks like a file system access API, it might end up not being on depending on browser implementation details.

Also any access goes through a file picker and you can't ".."-navigate up this avoid many problems with security, adding in no file links no fancy operations etc. means this should not be a problem even even if it allows access to you files.

Through in the end it depends a lot on the choice the browser does when implementing it.

Similar the limitations, especially the absence of flush makes it kinda useless to run a database in the browser :(

Through there is `persist` on the storage manager, but I'm not sure how much this helps. Theoretically it could work like fsync or F_FULLSYNC, but practically I'm not sure at all.

You can likely assume fsync is going on once you close a file writer. It's already a super expensive operation, so there would be little reason not to sync. The expensive part is they copy the entirety of the old file to a temp file, let you do changes to the temp file, then copy the temp file to the old file on close.

The implementation creates other problems for DBs though since you can't really do small writes efficiently. One idea I've had would be to implement a virtual paging system, but then you introduce a new layer of abstraction and it's still going to be really slow on NTFS (since it assumes a few large files, not many small ones).

The origin file system is pretty pointless, but it is only one part of the spec. It is definitely intended that the local file system (whether a cloud k/v store or not) at least allows for moving data between completely different websites.

I think you make a good point about Safari's nonsense with browser data. The spec should require implementors to never clear out what they use for "local file system" unless the user explicitly says to, and only for the files selected for deletion. The old APIs like local storage/IndexedDB unfortunately assumed no browser vendor would be as dumb as Safari with their ridiculously short retention policy.

Other APIs for async file I/O are outright painful.
I wonder how this API will affect Electron apps once it's available there. For some apps the only reason you need non-UI code at all is for working with the file system; seems like it would be great to be able to consolidate more logic into the UI process
As a native programmer who meddles with the horrors of thread safety and & and &mut, and occasionally dabbles in high-level (JS/Dart) asynchronity, async functions fill me with much of the same fear and caution. await looks like merely a nonblocking function call, but means arbitrary code executes and can and will mutate arbitrary state under your feet. Shared state across coroutines is nearly as dangerous as shared state across threads, and (I conjecture) far more pervasive.

In the code in question (https://web.archive.org/web/20220113153505/https://web.dev/f..., now changed in https://web.dev/file-system-access/#drag-and-drop-integratio...):

  elem.addEventListener('drop', async (e) => {
    // Prevent navigation.
    e.preventDefault();
    // Process all of the items.
    for (const item of e.dataTransfer.items) {
      // Careful: `kind` will be 'file' for both file
      // _and_ directory entries.
      if (item.kind === 'file') {
        const entry = await item.getAsFileSystemHandle();
        if (entry.kind === 'directory') {
          handleDirectoryEntry(entry);
        } else {
          handleFileEntry(entry);
        }
      }
    }
  });
I probably wouldn't have guessed that `e.dataTransfer.items` gets cleared at the first await (since I'm not a proficient web developer), but I would've been extremely wary of this code in general. Additionally (not tied to async-await but race conditions in general), is `item.getAsFileSystemHandle()` a TOCTTOU vulnerability where the type of an item can change between folders and files and symlinks etc., while this code is running?

Rust's & vs. &mut system largely eliminates shared state hazards in both threading and asynchronity (&mut is exclusive/unaliased and can't be mutated by other threads or event loop jobs, and & is difficult and unidiomatic to mutate), though it doesn't solve async cancellation errors (https://carllerche.com/2021/06/17/six-ways-to-make-async-rus..., discussed at https://news.ycombinator.com/item?id=27542504), or filesystem TOCTTOU (https://blog.rust-lang.org/2022/01/20/cve-2022-21658.html as well as user code).

Qt event loop reentrancy is fun(tm) as well. It looks like a blocking call, but spawns a nested event loop which can do anything (but rarely enough to lull you into a false sense of complacency), resulting in segfaults like https://github.com/Nheko-Reborn/nheko/issues/656 (workaround at https://github.com/Nheko-Reborn/nheko/commit/570d00b000bd558..., I didn't look into it). And Qt lacks "easy" await syntax and a framework based on calling red functions (though I didn't look into C++20 coroutines yet, perhaps https://www.qt.io/blog/asynchronous-apis-in-qt-6 or ...

> Shared state across coroutines is nearly as dangerous as shared state across threads, and (I conjecture) far more pervasive.

The danger is roughly equal, conceptually shared concurrent state has all of the same corruption risks regardless of additional concurrency considerations (fault tolerance, CAP, resource management, etc). The latter is probably harder to quantify, but I suspect the answer very closely hews to the age of the overall codebase and the exposure of contributors to different approaches.

The reasons I suspect that:

1. The event loop/cooperative concurrency model isn’t restricted to JS, but it’s so heavily weighted towards JS by volume that those trends will dominate any quantitative change.

2. The convergence of rest/spread syntax, async/await syntax, and React: all of these arrived or became widely adopted around the same time, and promoted a more functional style even if it wasn’t recognized or called that.

3. Older codebases, even if they aggressively adapt to those idiomatic changes, retain and even go on producing more shared concurrent state because the risk is hard to know and untangling it is hard to prioritize. (I live this every day in projects I’ve inherited, and I’d expect the same with projects of similar age if they’d lived this long.)

I would bet these temporal assumptions roughly line up for other environments with similar concurrency models and similar evolutionary paths, depending mostly on how rapidly they churn.

> Google’s documentation and the specification itself contained this bug in example code.

Off-topic, but Google's documentation for _everything_ contains subtle bugs such as this.

Nice article! It’s a great API, hope to see the kinks ironed out.

I am using it to stream PNG frames in real-time into a user’s file system, for web-based media tools. This allows the user to generate their own HD video files offline at maximum quality, bringing static web a little closer to pro level media tooling.

One downside aside from browser support is that two permissions must be requested for this: one to select the directory to save to, and then again to give write access.

I find the way we are slowly converging on Chromium-Powered-Browser as the standard operating system to be both funny and frustrating.

I'm really dissatisfied with Electron apps in general and using an an operating system designed to run on top of other operating systems feels wrong.

But, I do really like it when things portably just work.

Modern operating systems failed to provide proper sandboxing for apps. May be because of their legacy or may be because it was never their focus. Imagine opening Apple iOS to all apps without restrictions. Vulnerabilities will be infinite. Android formally allows side-loading but do you really dare to install untrusted apk? I do not. I'd be super cautious about that. I don't trust Android security model. But I'm not super cautions to visit some site. I trust browser security model. And don't even talk about Linux or Windows or macOS, sandbox is just not used by majority of apps and users are not even aware about it. macOS probably the most advanced in that regard, but the first thing I do is install macports which have access to /usr/local, where's your sandbox now?

So even without portability, chromium OS is superior when it comes to sandboxing. Users are much safer using web apps rather than native apps.

And portability is, of course, extremely important aspect as well.

Chromium OS is a good future. We just need bare metal performance.

It's not terrible, but it still has a lot of warts. I wrote a saver plugin for Tiddlywiki that uses this API for a self modifying HTML file. https://slaymaker1907.github.io/tiddlywiki/plugin-library.ht...

One major annoyance is that you can't just show the file picker for "security reasons", it has to be user prompted. This is a useless precaution because you can just add an onclick handler to the document body. It inconveniences non-malware developers while hardly troubling malicious sites at all.

Another goodie is that resolving a path is extremely expensive with no opportunity for caching from the OS. You have to parse paths yourself and individually walk each directory.

I've also been working on an equivalent API to the fs module in Node lately, but really such an API should have been the #1 priority example for using this API. It would have immediately highlighted how difficult the path problem is. OS/browser engine nerds deal with file descriptors, but most applications work with paths.

It also would be great if Firefox would stop being so hostile to this API. The API has problems, but they aren't inherent to the goals of this API. I also find polyfills for this API to be ridiculous. You really can't polyfill this in a meaningful way for the browser because it is so groundbreaking. Having a non-Google implementation of the API would be way more helpful than polyfills.