· deepdives  · 7 min read

Security Implications of the File System Access API: What Developers Need to Know

A practical guide for web developers: understand the security risks of the File System Access API, how browsers constrain it, real attack scenarios, and a clear set of best practices to protect user data and maintain trust.

A practical guide for web developers: understand the security risks of the File System Access API, how browsers constrain it, real attack scenarios, and a clear set of best practices to protect user data and maintain trust.

Outcome: After reading this article you’ll be able to design File System Access API flows that minimize risk, preserve user privacy, and keep your app robust against common attacks.

The File System Access API gives powerful capabilities to web apps: open files, save directly to the user’s disk, and even hold persistent file handles. That power is good for productivity apps - but it also raises important security questions. Get the trade-offs right and users will trust you. Get them wrong and the consequences can be data loss, exfiltration, or permanent reputational damage.

Quick primer: what the File System Access API does (and what browsers already protect)

  • The API exposes file and directory pickers: showOpenFilePicker, showSaveFilePicker, and showDirectoryPicker.
  • It returns FileSystemFileHandle and FileSystemDirectoryHandle objects. Those handles let code read from and write to the user’s files.
  • Access is only available in secure contexts (HTTPS). Most file operations require a user gesture (e.g., the file picker) and explicit permission. See the spec and developer docs for details: MDN - File System Access API and the WICG draft: WICG File System Access.

Browsers have mitigations built in: secure contexts, user gestures for pickers, and a permission model (queryPermission, requestPermission) with states like granted, denied, and prompt. But these are not a silver bullet - you must design carefully on the app side.

Practical threat model: what can go wrong

  1. Unauthorized write access (ransomware-like behavior)

    • If a web app obtains write permission and is later compromised (for example by a malicious third-party script), the attacker can modify or delete user files.
  2. Silent data exfiltration

    • A handle stored persistently (e.g., in IndexedDB) can be reused later to read sensitive files without showing the native picker again. A compromised page or rogue script can read and upload content.
  3. Social engineering and deceptive UIs

    • Users may be tricked into granting access to the wrong directory or file (e.g., “choose your profile photo” while the dialog actually lets the page pick any file).
  4. Malicious file contents or code execution

    • Opening or previewing untrusted files (HTML, SVG, PDFs) can result in script execution or content injection if the app renders them unsafely.
  5. Supply-chain and third-party script risks

    • Any script running in your page can use file handles already granted to the origin. Inclusion of a compromised third-party library can be disastrous.
  6. Incomplete user revocation

    • Users might not know how to revoke stored handles; browser UI for managing permissions is not always obvious. That can leave persistent access in place.

Browser behavior worth knowing (gotchas)

  • Permission persistence varies by browser and user settings. A handle stored in IndexedDB can be usable across sessions if the user granted persistent permission.
  • There is no universal programmatic “revokePermission” API that guarantees immediate revocation; users may need to manage permissions via browser settings.
  • The API’s security model assumes careful app authorship. The API attaches power to origins. Any script served from your origin shares that power.

Concrete best practices (what to implement today)

  1. Follow least privilege

    • Only request read when you only need reading. Request write only when you must modify the user’s files.
    • Prefer file pickers over directory picks when possible.
  2. Robust permission checks

    • Always call queryPermission and requestPermission before sensitive operations and handle denied states gracefully.

Example - check and request permission safely:

async function ensurePermission(handle, mode = 'read') {
  const opts = { mode };
  // Check current state
  let perm = await handle.queryPermission(opts);
  if (perm === 'granted') return true;
  // Ask for permission (will show prompt)
  perm = await handle.requestPermission(opts);
  return perm === 'granted';
}
  1. Limit how you store file handles

    • Treat handles like high-privilege credentials.
    • If you store handles in IndexedDB, encrypt or wrap them if you store sensitive metadata alongside them. Don’t store handles you don’t need.
    • Give users a simple UI to list and explicitly revoke stored handles in your app.
  2. Use atomic writes and safe-save patterns

    • Avoid overwriting critical files in-place. Use a temporary file (or folder) for writes, then rename/replace atomically when save completes.
    • The API provides createWritable(); use it and call close() only after successful write.

Example - safe save:

async function safeWrite(handle, data) {
  // Get a writable and write to it, then close only on success
  const writable = await handle.createWritable();
  try {
    await writable.write(data);
    await writable.close();
  } catch (err) {
    try {
      await writable.abort();
    } catch (_) {}
    throw err;
  }
}
  1. Sanitize filenames and metadata

    • Even though the API gives handles, if you accept a filename from users (to create a new file), sanitize it. Remove path separators, control characters, and dangerously long names.
  2. Isolate untrusted content

    • Never render untrusted HTML or SVG directly into your app UI. Use sandboxed iframes (sandbox attribute) or convert files to safe representations (images to canvas bitmaps, documents to sanitized text).
    • For previews, stream and parse content in a worker or dedicated process that cannot access your DOM or optional secrets.
  3. Protect your origin: CSP, SRI, and Trusted Types

    • Use Content Security Policy (CSP) to restrict script sources and reduce XSS risk.
    • Use Subresource Integrity (SRI) and serve third-party libraries from trusted CDNs or self-host them.
    • Use Trusted Types to prevent DOM XSS sinks from accepting attacker-controlled markup.
  4. Minimize or avoid long-lived access

    • Prefer ephemeral flows where the user picks a file each time. If you need permanent handles, explain why and request consent explicitly.
  5. Audit and logging

    • Keep an audit trail of file operations (what file handle ID was used, what operation, timestamp). Keep logs encrypted if they include sensitive metadata.
  6. Educate users with clear UI and consent flows

  • Make permission prompts contextual. Explain why you need access, what you’ll do with the data, and offer granular controls (read-only vs write).
  • Provide easy ways for users to revoke stored access from within your app.

Example: safer flow for a document editor

  • On load: detect API availability. If not available, fallback to legacy upload/download flows.
  • When user chooses “Open”, call showOpenFilePicker and limit accept filters to expected MIME types.
  • Store the handle only if the user explicitly opts-in to “Keep this document synced”.
  • When syncing, check queryPermission and re-request if necessary. If permission is revoked, show a clear message explaining how to reauthorize.
  • For saving: write to a temporary file handle first, then rename/replace.

Handling compromised scripts and third-party risk

  • Treat any script running on your origin as trusted only if you can verify it. That includes analytics, ads, A/B frameworks, and widgets.
  • Reduce attack surface by:
    • Self-hosting critical libraries.
    • Limiting third-party scripts to isolated iframes where possible.
    • Using strict CSP rules to prevent inline scripts and untrusted evals.

If you must include third-party scripts, limit their capabilities. They should never need persistent file handles.

User trust and compliance implications

  • Transparency is mandatory. Under privacy regulations (e.g., GDPR), you must be explicit about what data you read and why.
  • Provide clear controls for consent and data deletion. If you store content server-side (after reading), allow users to export or permanently delete it.
  • Minimizing collection and retention reduces regulatory risk and builds trust.

Trust-building UI checklist:

  • Explain why the file/directory is needed in simple language.
  • Display which permissions are held and let users revoke them.
  • Offer a preview that does not expose the app or user to execution risks.
  • Provide a clear privacy policy and link to it at the point of requesting permission.

Quick defensive checklist for audits and code reviews

  • Feature-detect the API before use and document browser fallbacks.
  • Use queryPermission and requestPermission explicitly, and handle denial paths.
  • Avoid storing more handles than necessary; if stored, protect them.
  • Prevent XSS via CSP, SRI, and Trusted Types.
  • Isolate previews of untrusted content.
  • Implement atomic save and write-abort logic.
  • Provide in-app UI for viewing and revoking stored handles.
  • Log file operations for incident response.

When not to use the File System Access API

  • If files contain highly sensitive data (health records, PII) and you cannot meet encryption and audit standards, prefer server-side controlled flows where possible.
  • If your app relies heavily on third-party scripts you can’t fully control, avoid persistent file handles.

Useful references

Final thought

The File System Access API unlocks native-like capabilities in the browser. That capability translates to responsibility. Design with least privilege, make permission flows explicit, isolate file processing, and harden your origin against XSS and supply-chain risks. Do that and you’ll enjoy the productivity benefits without betraying user trust.

Back to Blog

Related Posts

View All Posts »
Understanding COOP and COEP: The Future of Secure Data Sharing

Understanding COOP and COEP: The Future of Secure Data Sharing

Learn how Cross-Origin-Opener-Policy (COOP) and Cross-Origin-Embedder-Policy (COEP) work, why they matter for modern web security and powerful browser features, and how to implement them safely-step by step with examples, nginx/Express/Apache snippets, and a migration checklist.